순서대로 실행하는 여섯 가지 점검이 VM이 자기 호스트의 VPN 게이트웨이가 될 수 있는지 결정한다. 하이퍼바이저는 이 중 어느 것도 대신 하지 않는다: 터널은 게스트가, 라우트는 호스트가 가지며, 둘 사이의 네트워크는 평범한 라우팅이다. 이 목록을 놓지 않는 이유는 한 단계를 건너뛰면 늘 같은 증상이 나오기 때문이다: 호스트는 게스트에게 ping을 잘 보내는데 내부의 무엇에도 닿지 못하는 침묵이다.

이유부터 시작하자. 기업 클라이언트가 게스트 안에만 설치되거나 정책이 거기에 묶어 두는 상황에서도 호스트에서 내부 빌드 서버로는 가야 한다. 호스트에 자체 클라이언트를 둘 수 있다면 그쪽을 택하자: 호스트의 터널 하나는 두 개의 네트워크에 NAT를 더한 구성보다 언제나 낫다.

점검 1: 호스트에는 주소를 줄 수 있는 링크가 필요하다

호스트는 게스트의 두 번째 어댑터와 같은 프리픽스에 있는 인터페이스를 가져야 한다. VirtualBox의 host-only, VMware Workstation의 host-only나 사용자 정의 VMnet, QEMU/KVM의 내부 브리지나 tap 장치, Hyper-V의 Internal 스위치가 모두 해당한다. 게스트 192.168.56.2, 호스트 192.168.56.1, 프리픽스 192.168.56.0/24 가 내가 실험실에서 쓰는 배치다.

QEMU의 사용자 모드 네트워킹(-netdev user, 기본 SLIRP 백엔드)과 VirtualBox NAT는 이 점검에서 완전히 실패한다. 게스트 패킷은 호스트의 프록시 프로세스에 닿을 뿐이므로 호스트는 게스트 주소를 실은 장치를 본 적이 없고 라우트를 향할 대상도 없다. Hyper-V의 Default Switch도 같은 이야기다. 호스트에서 ping 192.168.56.2 가 실패한다면 나중에 추가한 라우트로는 고칠 수 없으니 멈춰라.

점검 2: 게스트가 포워딩하고 변환한다

호스트에서 오는 패킷은 host-only 측 출발지 주소와 내부 목적지로 도착하므로 게스트는 그것을 포워딩한 뒤 자기 터널 주소 뒤에 숨겨야 하고, Linux 게스트라면 포워딩과 FORWARD accept 한 쌍, 터널 쪽으로의 source NAT를 뜻한다.

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.56.0/24 -o tun0 -j MASQUERADE

MASQUERADE 규칙이 바로 사람들이 빠뜨리는 부분이다. 기업 라우터는 192.168.56.0/24 가 존재하는지 거의 모르고, 이 규칙이 있으면 내부 서버는 게스트의 터널 주소를 보고 응답하며 그 응답은 게스트로 돌아오는 길을 찾는다.

Windows 게스트는 더 어설프다. SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 아래의 IPEnableRouter=1 는 변환 없이 포워딩을 켜므로 기업 라우팅이 이미 host-only 프리픽스로 되돌아 있는 곳에서만 작동한다. Internet Connection Sharing은 변환하지만 공유 어댑터를 192.168.137.1 로 다시 번호 매기므로, 호스트 라우트를 쓰기 전에 실제로 받은 주소를 읽어야 한다.

점검 3: 호스트 라우트는 터널이 아니라 게스트를 가리킨다

호스트에 필요한 프리픽스만 게스트를 거쳐 보내고, Linux에서는 프리픽스마다 라우트 하나로 충분하며 깔끔하게 되돌릴 수 있다.

sudo ip route add 10.20.0.0/16 via 192.168.56.2
ip route get 10.20.30.40

Windows에서는 route add 10.20.0.0 mask 255.255.0.0 192.168.56.2 가 같은 역할을 한다. 시험하는 동안에는 -p 를 빼 두어 잘못된 라우트가 재부팅 때 사라지게 하고 경로가 동작하면 추가하며, 호스트의 기본 라우트는 호스트의 모든 응용 프로그램이 터널을 써야 하는 경우가 아니면 지우지 말고, 정말 전체 터널이 요구 사항이라면 /1 라우트 두 개를 추가하고 하이퍼바이저 네트워크로 가는 직접 라우트는 남긴다.

sudo ip route add 0.0.0.0/1 via 192.168.56.2
sudo ip route add 128.0.0.0/1 via 192.168.56.2

Windows 호스트에서는 인터페이스 메트릭도 확인한다. host-only 어댑터가 꺼져 있는 동안 추가한 라우트는 테이블에 남아도 트래픽을 나르지 않으며, 메트릭이 더 낮은 두 번째 어댑터가 같은 프리픽스 조회에서 이길 수 있다.

점검 4: DNS는 터널의 기본값이 아니라 라우트를 따른다

라우트는 패킷을 옮기고, 리졸버는 어떤 이름이 패킷을 만드는지 정한다. 호스트 리졸버를 교체하는 대신 기업 리졸버를 기업 영역으로 한정하고, 새 라우트가 이미 닿는 DNS 서버를 가리키게 한다(호스트에서 systemd-resolved를 쓴다면 다음과 같다).

resolvectl dns eth1 10.20.0.53
resolvectl domain eth1 '~corp.example.com'

Windows에서는 이름 확인 정책 테이블 항목이 같은 일을 하므로 Add-DnsClientNrptRule -Namespace ".corp.example.com" -NameServers 10.20.0.53 를 쓰면 된다. 게스트가 host-only 주소에서 자체 리졸버를 돌린다면 loopback이 아니라 192.168.56.2 에서 응답하는지 확인한 뒤 그 주소를 쓴다.

그다음 느린 페이지를 DNS 탓으로 돌리기 전에 MTU를 확인한다. 터널 인터페이스는 호스트의 1500바이트 인터페이스보다 작으므로 DF 비트를 켠 최대 크기 세그먼트를 보내는 호스트는 게스트 안 어딘가에서 그것을 잃는다. 전형적인 증상은 ping 는 통하는데 내부 웹 앱이 TLS 핸드셰이크 뒤에서 멈추는 것이므로 게스트의 포워딩 경로에서 MSS를 조인다.

sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

점검 5: 양쪽 끝에서 검증한다

호스트에서 내부 주소로 traceroute 나 tracert 를 치면 첫 홉에 물리 게이트웨이가 아니라 192.168.56.2 가 나와야 한다. 게스트에서는 같은 시도가 나가는 모습을 본다. tcpdump -ni any host 10.20.30.40 는 host-only 쪽에서 포워딩된 요청과 터널에서 변환된 요청을 보여 주므로, 나갔다가 돌아왔는데도 호스트가 조용하다면 돌아오는 경로를 의심한다.

그 대부분은 두 가지 실패로 설명된다. NAT 규칙이 없어 게스트의 터널이 host-only 프리픽스로 돌아가는 라우트를 갖지 않거나, 재시작 뒤 게스트가 더는 갖지 않는 주소를 호스트 라우트가 가리키는 경우다. 지난 세션의 메모를 믿지 말고 ip -br addr 와 ip route get 로 주소를 다시 읽는다.

점검 6: 재부팅과 하이퍼바이저 업그레이드를 견디게 만든다

양쪽 절반을 모두 영구화한다. 호스트에서는 netplan이나 NetworkManager 라우트 설정, Windows에서는 route add ... -p, 그리고 /etc/sysctl.d/ 에 net.ipv4.ip_forward=1 를 두고 게스트에서 다시 읽히는 방화벽 규칙도 준비하며, 설정이 확인되면 다음 기업 클라이언트 업데이트가 오기 전에 게스트 스냅숏을 뜬다.

host-only 네트워크는 하이퍼바이저가 어댑터를 다시 만들 때 번호가 다시 매겨지고, 192.168.56.0/24 위에 세운 모든 라우트와 NAT 규칙이 조용히 무효가 된다. 업그레이드 뒤에는 처음 세 점검을 순서대로 다시 실행하면 일 분이면 되고, 호스트에는 라우트가 남았는데 게스트가 새 서브넷으로 옮겨 간 경우를 잡아낸다.

IPv6는 따로 한 번 더 봐야 한다. ip6tables 규칙, AAAA 레코드, 두 번째 호스트 라우트 체계는 IPv4 설정을 따라오지 않으며, IPv6로 닿는 기업 서비스는 IPv4가 동작하는 동안 계속 고장 난 것처럼 보인다.

통과든 실패든 순서대로 보면, 호스트가 게스트의 host-only 주소로 ping할 수 있고, 게스트가 터널 쪽으로 포워딩과 MASQUERADE를 하며, ip route get 가 내부 프리픽스의 게이트웨이로 게스트를 돌려주고, 기업 이름이 한정된 리졸버로 해석되며, traceroute 가 게스트를 먼저 보여 주고, 같은 여섯 가지 답이 재부팅 뒤에도 남는다.

관련 글