수십 개의 기사, Blob, 튜토리얼을 읽고 심지어 stackoverflow에 대한 질문에 답변한 후에도 호스트에서 게스트 VM으로 포트 전달을 설정하는 방법이라는 문제에 여전히 답이 없습니다.
우선, 영어가 서툴러서 죄송합니다. 명확하게 말하도록 노력하겠습니다.
둘째: 저는 네트워킹에 있어서 완전 초보자입니다. 하지만 동료들을 위해 이 서버를 설정해야 합니다.
우리는 웹 소프트웨어에 대한 여러 테스트 환경을 제공하기 위해 3개의 KVM 가상 머신을 설정하려는 공개 호스팅 Centos 7 서버를 보유하고 있습니다. 내 생각은 각 가상 머신에 전달할 포트 범위를 할당하는 것입니다. 예를 들어 포트 10001:19999는 VM 1의 1:9999로 전달되고 포트 20001:29999는 VM 2의 1:9999로 전달되는 식입니다. .
나는 많은 솔루션을 시도했지만 그 중 아무것도 작동하지 않았습니다. 이것은 내 현재 설정입니다.
#> ifconfig
eno2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet xx.xxx.xx.xxx netmask 255.255.255.0 broadcast xx.xxx.xx.255
ether aa:aa:aa:aa:aa:aa txqueuelen 1000 (Ethernet)
RX packets 10190055 bytes 644136763 (614.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 338010 bytes 27222247 (25.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device memory 0x92b00000-92bfffff
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 2283 bytes 4633913 (4.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2283 bytes 4633913 (4.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
virbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
ether bb:bb:bb:bb:bb:bb txqueuelen 1000 (Ethernet)
RX packets 4448 bytes 566487 (553.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3374 bytes 1243921 (1.1 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vnet0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether cc:cc:cc:cc:cc:cc txqueuelen 1000 (Ethernet)
RX packets 268 bytes 23314 (22.7 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2071 bytes 114034 (111.3 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
#> cat /etc/sysctl.conf
# sysctl settings are defined through files in
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
#
# Vendors settings live in /usr/lib/sysctl.d/.
# To override a whole file, create a new file with the same in
# /etc/sysctl.d/ and put new settings there. To override
# only specific settings, add a file with a lexically later
# name in /etc/sysctl.d/ and put new settings there.
#
# For more information, see sysctl.conf(5) and sysctl.d(5).
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv4.ip_forward = 1
#> cat /etc/libvirt/hooks/qemu
#!/bin/bash
v=$(/sbin/iptables -L FORWARD -n -v | /usr/bin/grep 192.168.122.0/24 | /usr/bin/wc -l)
# avoid duplicate as this hook get called for each VM
[ $v -lt 1 ] && /sbin/iptables -I FORWARD 1 -o virbr0 -m state -s xx.xxx.xx.xxx/32 -d 192.168.122.0/24 --state NEW,RELATED,ESTABLISHED -j ACCEPT
update(){
if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
/sbin/iptables -t nat -D PREROUTING 1 -d $GUEST_IP -p tcp --dport $HOST_PORT -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding"
fi
if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
/sbin/iptables -t nat -I PREROUTING 1 -d $GUEST_IP -p tcp --dport $HOST_PORT -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding"
fi
}
GUEST_PORT=1-9999
if [ "${1}" = "VM1" ]; then
GUEST_IP=192.168.122.101
HOST_PORT=10001:19999
elif [ "${1}" = "VM2" ]; then
GUEST_IP=192.168.122.102
HOST_PORT=20001:29999
fi
update $1 $2
#>virsh net-edit default
<network>
<name>default</name>
<uuid>0db10b13-21c6-45c3-a891-ec46509b2121</uuid>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<mac address='aa:aa:aa:aa:aa:aa'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
<host mac='bb:cc:dd:ee:ff:01' name='VM1' ip='192.168.122.101'/>
<host mac='bb:cc:dd:ee:ff:02' name='VM2' ip='192.168.122.102'/>
</dhcp>
</ip>
</network>
qemu 후크는 제대로 작동하는 것 같고 iptables 규칙은 내가 바라던 대로입니다.
#> iptables -L FORWARD -nv --line-number
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- * virbr0 xx.xxx.xx.xxx 192.168.122.0/24 state NEW,RELATED,ESTABLISHED
2 185 13612 ACCEPT all -- virbr0 * 192.168.122.0/24 0.0.0.0/0
3 0 0 ACCEPT all -- virbr0 virbr0 0.0.0.0/0 0.0.0.0/0
4 186 13704 REJECT all -- * virbr0 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
5 0 0 REJECT all -- virbr0 * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable
6 0 0 ACCEPT all -- * virbr0 xx.xxx.xx.xxx 192.168.122.0/24 state NEW,RELATED,ESTABLISHED
7 0 0 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
#> iptables -t nat -L -n -v
Chain PREROUTING (policy ACCEPT 25580 packets, 2244K bytes)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- * * 0.0.0.0/0 192.168.122.101 tcp dpts:10001:19999 /* VM1 port forwarding */ to:192.168.122.101:1-9999
Chain INPUT (policy ACCEPT 774 packets, 46800 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 578 packets, 44429 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 578 packets, 44429 bytes)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- * * 192.168.122.0/24 224.0.0.0/24
0 0 RETURN all -- * * 192.168.122.0/24 255.255.255.255
4 240 MASQUERADE tcp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
159 12084 MASQUERADE udp -- * * 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
0 0 MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24
하지만 내 컴퓨터에서 VM1에 액세스한다고 가정하고 SSH를 통해 내 가상 머신에 액세스하려고 하면
ssh [email protected]:10022
작동하지 않습니다.
내가 무엇을 놓치고 있나요?
답변1
여러 가지 질문
이 회로도를 읽으면 패킷에서 발생하는 작업 순서를 이해하고 다음 지침을 이해하는 데 도움이 됩니다.
filter/FORWARD
: DNAT 패킷을 올바르게 허용합니다.
현재 이 규칙은 다음과 같습니다.
... /sbin/iptables -I FORWARD 1 -o virbr0 -m state -s xx.xxx.xx.xxx/32 -d 192.168.122.0/24 --state NEW,RELATED,ESTABLISHED -j ACCEPT
일치하지 않음: 전달(라우팅)될 때 패킷은 호스트 주소가 있는 소스를 갖지 않거나 호스트에 의해 방출되지만 PREROUTING 후크를 통과하지 않습니다(그러나 OUTPUT). nat/PREROUTING
소스가 아닌 대상을 변경하세요.
원격 액세스를 허용하려면 허용된 각 원격 소스에 대한 규칙을 지정하거나(호스트의 xx.xxx.xx.xxx/32를 허용된 원격 클라이언트 yy.yyy.yy.yyy의 주소로 대체) 지정하거나 모든 출처를 지정하지 마세요. 원격 클라이언트를 허용하려면 다음을 수행하십시오.
/sbin/iptables -I FORWARD 1 -o virbr0 -d 192.168.122.0/24 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
구체적으로 일치시키려는 경우 패킷은 먼저 대상 xx.xxx.xx.xxx/32(예를 들어 여러 공용 주소가 있고 이 역할 전용 주소가 있는 호스트에 유용함)에 도착한 다음 192.168.122.0/과 일치하는 것으로 변환됩니다. 24 여전히 가능합니다 conntrack
(OP의 state
일치 대체):
/sbin/iptables -I FORWARD 1 -o virbr0 -d 192.168.122.0/24 -m conntrack --ctstate NEW,RELATED,ESTABLISHED --ctorigdst xx.xxx.xx.xxx/32 -j ACCEPT
다른 가능성도 있습니다. 가장 간단한 방법은 이전 규칙에서 이미 실행된 DNAT 변환을 거친 흐름의 패킷 부분을 간단히 받아들이는 것입니다. 이러한 DNAT는 흐름이 허용되는 경우에만 유용하기 때문입니다.
/sbin/iptables -I FORWARD 1 -m conntrack --ctstate DNAT -j ACCEPT
nat/PREROUTING
: 원래 대상은 가상 머신의 주소가 아닙니다.
가상 머신에 직접 액세스할 수 없으므로 클라이언트는 192.168.122.101:10022에 연결을 시도하지 않습니다. 192.168.122.101에 직접 연결할 수 있는 경우 192.168.122.101:22에 연결하고 이 질문을 하지 않아도 됩니다.
클라이언트는 호스트의 단일 공용 IP(또는 대체 공용 IP)에 연결됩니다. 후원자의iptables그런 다음 규칙은 포트를 IP 및 포트로 변환합니다. 따라서 호스트에 xx.xxx.xx.xxx 주소가 있는 경우 nat/PREROUTING
규칙은 VM의 IP 대상과 일치하려고 시도해서는 안 되며 대신 호스트의 IP 대상과 일치하려고 시도해야 합니다. 이런 일이 발생하면 filter/FORWARD
최종 목적지가 표시됩니다(이전 지점에서 언급한 대로).
마지막으로 다음을 사용하지 마십시오.
/sbin/iptables -t nat -I PREROUTING 1 -d $GUEST_IP -p tcp --dport $HOST_PORT -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding"
그러나 예를 들면 다음과 같습니다(복잡함을 피하기 위해 수신 인터페이스도 지정).
/sbin/iptables -t nat -I PREROUTING 1 -i eno2 -d xx.xxx.xx.xxx/32 -p tcp --dport $HOST_PORT -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding"
nat/OUTPUT 및 nat/PREROUTING
호스트에서 로컬로 발생하는 패킷은 전달(라우팅)된 패킷과 다르게 처리됩니다.
OP의 추가 규칙은 에 있습니다 nat/PREROUTING
. PREROUTING
패킷이 수신될 때(그리고 라우팅 결정이 내려지기 전에) 발생합니다. 구체적으로는 nat/PREROUTING
다음에서만 발생합니다.첫 번째연결 스트림 패킷(예: 모든냇후크) 첫 번째 패킷이 전송되지 않고 수신된 경우에만 가능합니다.
에서 얻으려고 할 때외딴시스템(아님라이브러리 가상 머신호스트) OP의 규칙이 올바르게 실행되어야 합니다. 테스트는 항상 최종 사용 사례처럼 수행되어야 합니다. 원격 액세스를 위한 것이라면 테스트는 원격에서 이루어져야 합니다(그럴 수도 있지만 OP에서는 지정하지 않았습니다).
호스트에서 테스트할 때 이는 다릅니다.첫 번째패킷은 수신된 패킷이 아니라 전송된 패킷입니다. 통과하지 않습니다 PREROUTING
. 그러면 가상 머신의 응답은 더 이상 스트림의 첫 번째 패킷이 아닙니다.연결하다status NEW
) 위와 같이 NAT가 Netfilter에 의해 전적으로 처리되므로 이 모든 것을 건너뛰면 결과는 다음과 같습니다.연결하다입구. 이전 다이어그램에서는 다음을 보여줍니다.
"새" 연결에 대해서만 "NAT" 표를 참조하세요.
따라서 호스트의 경우 nat/PREROUTING
이전 지점과 비교하여 변경된 사항은 다음과 같습니다.
/sbin/iptables -t nat -I PREROUTING 1 -i eno2 -d xx.xxx.xx.xxx/32 -p tcp --dport $HOST_PORT -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding"
트리거되지 않음: 변경 효과를 아직 사용할 수 없으므로 대상은 여전히 xx.xxx.xx.xxx입니다. 그것을 바꾸는 것이 바로 이 규칙이다.
에서 수행된 유사한 설정은 nat/PREROUTING
다음에서도 수행되어야 합니다 nat/OUTPUT
.
iptables -t nat -I OUTPUT 1 -d xx.xxx.xx.xxx/32 -p tcp --dport $HOST_PORT -m comment --comment "VM1 port forwarding" -j DNAT --to-destination $GUEST_IP:$GUEST_PORT -m comment --comment "$1 VM port forwarding test from host"
이제 올바르게 일치합니다.
반면에 127.0.0.1 대신 xx.xxx.xx.xxx를 사용하면 포트 10001-19999의 호스트 TCP 서비스에 자체적으로 액세스할 수 없습니다. 호스트가 직접 연결되어 있으므로 -d xx.xxx.xx.xxx/32
OP의 원래 규칙을 대체하는 규칙을 계속 사용할 수 있지만 $GUEST_IP
규칙 세트를 테스트하는 것 외에는 별로 유용하지 않습니다.
하지만 어쨌든, 모든 경우에...
iptables정적 포트 범위 매핑을 수행할 수 없습니다.
... 이제 거의 모든 시도가 실패할 가능성이 높으며 Connection refused
, 그렇지 않은 경우 가상 머신에서 예상되는 서비스가 달성되지 않을 가능성이 높습니다.
... -p tcp --dport 10001:19999 -j DNAT --to 192.168.122.101:1-9999
결과는 마술처럼 포트 값에서 10000을 빼지 않습니다. 사용 가능한 임의의 포트를 선택합니다(예: 이전 포트와 일치하지 않음).연결하다각 고유 스트림에 대한 1-9999 범위의 항목). 따라서 포트 10022는 포트 22로 변환되지 않고 6456과 같이 해당 범위 내의 임의의 값으로 변환되며, 연속적으로 새로운 연결을 시도할 때마다 다른 포트 값이 됩니다.
기반으로 한 솔루션이 없습니다.iptables이를 위해서는 9999개의 규칙(각 포트당 하나씩)을 추가해야 한다는 점을 제외하면. 특히 포트 22의 경우 다음과 같이 작동합니다.
... -p tcp -m tcp --dport 10022 -j DNAT --to 192.168.122.101:22
따라서 실제로 작은 포트 집합을 선택하고 한 번에 하나의 규칙씩 개별 포트 매핑을 수행해야 합니다. VM1이 HTTP 1.x 서버인 경우 포트 22, 포트 80, 포트 443의 세 가지 규칙을 사용할 수 있습니다(그러나 호스트의 HTTP 역방향 프록시가 더 나은 솔루션일 수 있음).
... -p tcp -m tcp --dport 10022 -j DNAT --to 192.168.122.101:22
... -p tcp -m tcp --dport 10080 -j DNAT --to 192.168.122.101:80
... -p tcp -m tcp --dport 10443 -j DNAT --to 192.168.122.101:443
보너스: 정적 포트 범위 매핑을 위한 더 쉬운 방법
보너스로 정적 포트 변환을 수행하는 방법에 대한 설명이 있습니다.CentOS 7에서는 작동하지 않습니다필요한 기능이 부족하기 때문입니다. 그러나 조만간(2024-06-30) CentOS 7을 교체해야 해서...
IP 세트
다음 두 가지 이유로 사용할 수 없습니다.
아니요IP 세트유형에는 두 개의 포트가 있으며 주소 외에 한 포트에서 다른 포트로 매핑될 수 있습니다.
더 중요한 것은,iptables'
DNAT
대상에는 이 하위 시스템을 사용하기 위한 규정이 없습니다.
nftables
시험용nftables1.0.2 및 커널 5.16.x.
이를 위해서는 최신 버전이 필요합니다.nftables그리고 커널. CentOS 7은 적합하지 않습니다.nftables커널 3.13부터 사용 가능합니다. CentOS 7은 커널 3.10을 사용합니다.nftables이미 Red Hat 커널 기능이 백포트되었습니다. 더 새로운 것nftables도구와 커널에 기능이 누락됩니다.
특히 오래된 커널에서는nftables그리고iptables(레거시)은 특히 NAT 후크와 충돌하므로 NAT를 수행하기 위해 함께 사용할 수 없지만(등록에 실패하거나 자동으로 무시됨) 최신 커널에서는 함께 잘 작동합니다.
이 filter/FORWARD
섹션과 일반 MASQUERADE
규칙도 그대로 유지될 수 있습니다.iptables, 가상 머신으로의 포트 변환을 처리하는 NAT만 사용해야 합니다.nftables.
비트별 연산
아마도 가장 덜 까다로운 옵션일 것입니다.nftables및 커널 버전(그러나 CentOS 7의 경우 커널 3.10으로는 충분하지 않음).
nftables더 많은 기능이 있지만 여전히 뺄셈을 할 수 없기 때문에 10000에서 tcp 포트를 뺄 수는 없습니다. 그러나 비트 단위 연산을 매우 잘 수행할 수 있습니다. 따라서 10000개의 포트 범위를 할당하는 대신 범위가 2의 배수로 정렬되면 이 정적 포트 매핑을 달성할 수 있습니다. 10000 범위에 가까우면 8192(65536/8192-1=총 7개의 가상 머신) 또는 16384(총 3개의 가상 머신)를 사용할 수 있습니다.
16384를 사용하겠습니다. 첫 번째 사용 가능한 범위는 16384-32767(0x4000-0x7fff)이고 넷마스크는 16383(0x3fff)입니다. 포트 변환은 대상 포트의 첫 번째 범위(0-16383)에 매핑되므로
|
비트 AND(아래) 외에 비트 OR()를 적용할 필요가 없습니다.&
우선순위 -110은 다음보다 우선순위를 갖는 데 사용됩니다.iptables' 유사한 후크의 경우 우선순위는 -100입니다. NAT의 경우nftablesNAT 후크 불일치,iptables평소와 같이 NAT에 해당하는 후크는 나중에 일치할 기회가 있습니다.
nft add table rangenat nft add chain rangenat prerouting '{ type nat hook prerouting priority -110; }' nft add chain rangenat output '{ type nat hook output priority -110; }'
첫 번째 가상 머신의 경우:
nft add rule rangenat prerouting 'ip daddr xx.xxx.xx.xxx/32 tcp dport 16384-32767 dnat to 192.168.122.101:tcp dport & 0x3fff' nft add rule rangenat output 'ip daddr xx.xxx.xx.xxx/32 tcp dport 16384-32767 dnat to 192.168.122.101:tcp dport & 0x3fff'
이 명령과 마찬가지로 포트 16384+22=16406에서 주소 192.0.2.2의 호스트에 연결된 주소 203.0.113.11의 클라이언트를 사용하여 모델 테스트를 수행합니다(OP의 구문이 잘못되었으므로 포트를 매개변수로 지정해야 함
-p
).ssh -p 16406 [email protected]
결과는 이들에 있습니다.연결하다
conntrack -E
TCP 3방향 핸드셰이크 중에 호스트에 표시되는 항목:[NEW] tcp 6 120 SYN_SENT src=203.0.113.11 dst=192.0.2.2 sport=42458 dport=16406 [UNREPLIED] src=192.168.122.101 dst=203.0.113.11 sport=22 dport=42458 [UPDATE] tcp 6 60 SYN_RECV src=203.0.113.11 dst=192.0.2.2 sport=42458 dport=16406 src=192.168.122.101 dst=203.0.113.11 sport=22 dport=42458 [UPDATE] tcp 6 432000 ESTABLISHED src=203.0.113.11 dst=192.0.2.2 sport=42458 dport=16406 src=192.168.122.101 dst=203.0.113.11 sport=22 dport=42458 [ASSURED]
응답 포트 src(두 번째 부분)가
sport=22
예상대로 나타납니다.-
필요nftables 0.9.4연결을 통한 NAT 매핑(및유형구문) 및 커널 5.6.
nftables'
dnat
문을 사용할 수 있습니다.지도변화를 돕습니다(그 동안iptables'DNAT
대상을 사용할 수 없습니다IP 세트).이전 뼈대를 사용하면 다음과 같습니다.
nft add table rangenat nft add chain rangenat prerouting '{ type nat hook prerouting priority -110; }' nft add chain rangenat output '{ type nat hook output priority -110; }'
지도 추가:
nft add map rangenat port2ipport '{ typeof tcp dport : ip daddr . tcp dport; }'
이러한 일반 규칙은 다음과 같습니다.
nft add rule rangenat prerouting 'ip daddr xx.xxx.xx.xxx/32 dnat ip to tcp dport map @port2ipport nft add rule rangenat output 'ip daddr xx.xxx.xx.xxx/32 dnat ip to tcp dport map @port2ipport
그런 다음 루프를 사용할 때마다 다음과 같이 채웁니다.
nft add element rangenat port2ipport '{ 10001: 192.168.122.101 . 1 }' ... nft add element rangenat port2ipport '{ 10022: 192.168.122.101 . 22 }' ... nft add element rangenat port2ipport '{ 19999: 192.168.122.101 . 9999 }' nft add element rangenat port2ipport '{ 20001: 192.168.122.102 . 1 }' ...
또는 다음과 같이 최소한의 필수 부분만 결합하면 됩니다.
nft add element rangenat port2ipport '{ 10022: 192.168.122.101 . 22, 10080: 192.168.122.101 . 80, 10443: 192.168.122.101 . 443, 20022: 192.168.122.102 . 22, 30022: 192.168.122.103 . 22 }'
해시 값으로 일반적인 조회 시간은지도이는 O(1)이며, 이는 비트 연산 기반의 이전 방법과 유사합니다. 포트당 하나의 규칙을 사용하는 가장 간단한 방법iptables또는nftablesO(n) 조회 시간이 발생하며 이는 수천 개의 규칙에 대한 성능에 영향을 미치기 시작할 수 있습니다.