VPN 네임스페이스 안의 앱은 LAN에서 도달할 수 없다
네트워크 네임스페이스는 한 프로그램에 자체 터널과 자체 루프백을 준다. 호스트 대신 socat 홉이나 veth 쌍으로 그 포트만 게시하라.
서비스는 머신에서 돌아가고 터널도 올라와 있는데, 책상 위 브라우저는 여전히 응답을 기다린다. 보통의 의미에서 고장 난 것은 없다. 애플리케이션은 네트워크 네임스페이스 안에서 수신 대기하고, 그 스택은 LAN에 보이지 않는다. 루프백이 네임스페이스에 속하므로 다른 머신의 클라이언트에는 포트에 닿는 주소가 없다. 의도한 홉 하나, 또는 veth 쌍 하나면 호스트의 나머지를 옮기지 않고 해결된다.
LAN 주소에서 아무것도 응답하지 않는 이유
네임스페이스는 완전한 네트워크 스택이다. 인터페이스, 라우트, 방화벽 규칙, 그리고 사설 루프백을 갖는다. ip netns exec로 실행한 프로세스는 그 스택만 보므로, 바인딩한 주소는 네임스페이스 안에서만 해석된다. 호스트 자체의 127.0.0.1은 전혀 다른 루프백이다.
호스트에서 curl이 실패하고 네임스페이스 안에서 같은 명령이 성공하는 이유가 그것이다. 애플리케이션은 정상이고, 그 주소가 호스트 스택에 없을 뿐이다.
socat 홉으로 포트 하나 게시하기
socat은 사용자 공간에서 두 스택을 잇는다. TCP 포트 하나만 넘기면 될 때는 이것으로 충분하다. 호스트 쪽은 LAN에서 수신 대기하고, 수락한 연결마다 네임스페이스 안에서 두 번째 socat을 띄워 앱의 루프백에 연결한다. 안쪽 프로세스는 로컬 클라이언트와 똑같이 앱에 도달한다. 네임스페이스 라우팅은 아무것도 바뀌지 않으므로 앱은 터널을 유일한 출구로 계속 쓴다.
socat TCP-LISTEN:9100,reuseaddr,fork EXEC:'ip netns exec vpn socat STDIO tcp-connect:127.0.0.1:9100',nofork
바깥 프로세스에는 root 또는 네임스페이스에 들어갈 권한이 필요하다. VPN이 끊기면 앱은 경로를 잃고 게시한 포트는 닫힌 채 실패한다. 감옥에서 원하는 동작이 바로 그것이다.
포트가 둘 이상 지나면 veth 쌍을 쓴다
여러 포트나 프로토콜이 지나야 하거나 다른 호스트가 연결을 시작해야 한다면 veth 쌍이 정답이다. 한쪽 끝은 호스트에 남고 다른 쪽은 네임스페이스로 옮겨지며, 작은 /30이 양쪽에 안정적인 주소를 준다. 그다음 호스트 라우트가 네임스페이스 주소를 쌍 너머로 보낸다.
ip link add veth0 type veth peer name veth1
ip link set veth1 netns vpn
ip addr add 10.77.0.1/30 dev veth0
ip link set veth0 up
ip netns exec vpn ip addr add 10.77.0.2/30 dev veth1
ip netns exec vpn ip link set veth1 up
감옥을 깨뜨릴 생각이 아니라면 호스트를 거치는 기본 라우트를 추가하지 마라. 터널이 출구로 남아야 하므로 쌍 너머로는 게시한 서비스 주소만 도달할 수 있다. 네임스페이스에 자체 리졸버 파일도 주어라. ip netns exec는 /etc/netns/vpn/resolv.conf를 읽으며, 그것이 없으면 감옥 안의 이름 해석은 비어 있다.
호스트가 아니라 다른 머신에서 시험하라
시험은 두 번째 머신에서 실행하라. 호스트는 LAN에 대해 아무것도 말해 주지 않는 경로로 로컬 리스너에 닿을 수 있다. 먼저 앱이 네임스페이스 안에서 실제로 무엇에 바인딩되었는지 확인하고, 그다음 외부에서 게시한 주소로 접속하라. 거기서 모든 주소에 바인딩해도 와일드카드는 여전히 네임스페이스에 속하고 LAN은 그것을 볼 수 없으므로 감옥은 넓어지지 않는다.
ip netns exec vpn ss -lntp
호스트에서의 연결은 되고 LAN에서의 연결이 안 된다면, 빠진 조각은 바인드 주소가 아니라 홉이나 라우트다. 가동 중인 tun 장치를 옮기는 대신 네임스페이스 안에서 OpenVPN을 시작하라. 인터페이스를 옮기면 내려가고, 그 뒤 외부에서 고칠 수 없는 라우트가 지워질 수 있다. 복구 명령은 감옥 안이 아니라 콘솔에 두어라.