터널은 올라가 있고 서비스는 듣고 있는데 사무실 LAN은 여전히 아무것도 돌려받지 못한다. VPN이 깨진 것이 아니다. LAN 쪽에서 보면 노트북의 터널 주소는 무언가가 LAN에 도달 방법을 알려주기 전까지 존재하지 않으며, 응답은 요청이 나간 것과 같은 길로 돌아와야 한다. 누군가 돌아오는 경로를 보기 전에 완벽히 멀쩡한 터널을 세 번이나 다시 만드는 사람들을 나는 보았다.

망가진 것은 터널이 아니다

양쪽 끝은 사설 대역에 앉아 있고, 이는 정상적인 경우이며 설정 오류가 아니다. 10.8.0.6 같은 주소는 터널 안에서는 실제로 존재하고 다른 어디에도 없다. 그래서 LAN이 그쪽으로 무언가를 보낼 이유가 없다. 라우팅을 건드리기 전에 서비스가 터널 안에서 도달 가능한지부터 확인하라. 클라이언트에서 ss -ltn 는 리스너가 127.0.0.1, LAN 인터페이스, 터널 주소 중 어디에 묶여 있는지 보여준다. loopback에만 묶인 데몬은 자기 자신 말고는 아무에게도 응답하지 않으며, 어떤 라우트나 DNAT, 포트 포워딩도 그것을 바꾸지 못한다.

포트 80이나 443의 프록시는 다른 가시성을 지닌 다른 도구다. 이것은 패킷이 아니라 요청을 옮기므로, 원시 데이터베이스 연결에는 잘못된 계층이고, 그것에 손을 뻗는 것이 누군가 무언가를 측정하기 전에 이 문제를 세 도구 깊이로 끌고 가는 길이다.

라우트의 형태: 풀이 어디 있는지 LAN에 알려라

LAN 게이트웨이의 정적 라우트는 LAN과 터널 풀이 겹치지 않을 때 가장 깨끗한 답이다. 풀을 터널을 종료하는 머신으로 향하게 하라:

ip route add 10.8.0.0/24 via 192.168.1.12
route add -p 10.8.0.0 mask 255.255.255.0 192.168.1.12   # Windows, persistent

한 방향만 고치기 때문에 이 변경의 절반짜리 버전은 진전처럼 보인다. 터널 쪽에도 짝이 되는 절반이 필요하며, 그렇지 않으면 응답은 클라이언트를 자신의 기본 라우트로 떠나 도중에 죽는다:

push "route 192.168.1.0 255.255.255.0"

서버 뒤가 아니라 한 클라이언트 뒤에 있는 서브넷이라면 서버에는 시스템 라우트와, 그 클라이언트 파일의 iroute 항목(client-config-dir 아래)이 모두 필요하다. iroute 이 없으면 OpenVPN 은 접두사가 존재한다는 것은 알지만 어느 클라이언트가 그것을 소유하는지는 모른다. 패킷은 도착하고 데몬에는 그것을 맞출 대상이 없다.

여기 이름 붙일 만한 신화가 있다. 라우트는 주소를 도달 가능하게 만들지만, 상대편을 응답할 마음이 들게 하지는 않는다. 사이트 간 피어는 그 위에 층을 하나 더 얹는다. 원격 게이트웨이는 자기 정책에 나열된 접두사 안의 출발지만 받아들이고, 가정용 풀에서 오는 노트북은 그 안에 없다.

NAT의 형태: 안쪽으로의 DNAT와 모두가 건너뛰는 SNAT 줄

LAN 게이트웨이를 건드릴 수 없거나 정확히 포트 하나만 공개하려면, VPN 호스트에서의 변환이 LAN에 라우트를 전혀 두지 않고 동작한다. 포트를 공개하고, 전달하고, 터널로 들어가는 길에서 출발지를 변환하라:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.8.0.6:3000
iptables -A FORWARD -i eth0 -o tun0 -p tcp -d 10.8.0.6 --dport 3000 -j ACCEPT
iptables -t nat -A POSTROUTING -o tun0 -d 10.8.0.6 -p tcp --dport 3000 -j SNAT --to-source 10.8.0.1

세 번째 줄이 사라지는 줄이다. 건너뛰면 클라이언트는 원래 LAN 주소를 출발지로 보고 자기 기본 라우트로 응답하며, 그 응답은 LAN으로 돌아가는 대신 터널로 들어간다. 연결이 살아남을지는 계획하지 않았던 라우팅에 달린다. SNAT가 있으면 클라이언트는 서버의 터널 주소를 보므로, 응답은 터널을 따라 되돌아가 변환을 되돌릴 수 있는 유일한 호스트에 닿는다.

소비자용 펌웨어는 자체 체인 이름을 쓰고 그 변환을 대신 만들어 주지 않는다. Tomato에서는 인바운드 규칙이 WANPREROUTING 에 들어가고 이어 wanin 같은 필터 체인에 들어가며, 완전해 보이는 규칙 집합도 수동 POSTROUTING 줄이 여전히 필요하다. 그것은 펌웨어 버그가 아니라 인바운드 트래픽만을 위해 쓰인 규칙 집합이다.

올바로 보이는 규칙이 패킷을 여전히 버리는 이유

이 실패의 대부분은 당신이 노려보는 규칙 안에 있지 않다. 다음 순서로 네 가지를 확인하고, 성립하지 않는 첫 지점에서 멈춰라:

  • 체인 순서. iptables -I 는 맨 위에 삽입하고 -A 는 끝에 덧붙인다. 그래서 앞선 허용이나 차단 규칙이 먼저 결정한다.
  • 잘못된 체인. 라우터 자신을 향한 트래픽은 INPUT을 지나고, 포워딩되는 트래픽은 결코 그것을 건드리지 않는다. 서비스 하나를 공개하는 데 둘 다의 규칙이 필요할 수 있다.
  • 방향 플래그. -i tun0 는 클라이언트에서 오는 트래픽과, -o tun0 는 클라이언트로 나가는 트래픽과 일치한다. 플래그를 잘못 준 REJECT는 의도하지 않은 것을 막는다.
  • 출발지 제한. 클라이언트 주소 하나를 받아들이는 포워딩은 그 클라이언트에서는 동작하고 다른 모든 출발지는 한마디 없이 버린다.

패킷 카운터가 영인 규칙은 경로에 아예 들어 있지 않으며, 카운터를 읽는 것이 잘못된 체인 문제와 잘못된 규칙 문제를 가르는 가장 빠른 방법이다.

로컬 포워딩, LAN에서 아무것도 바꿀 수 없을 때

SSH 로컬 포워딩은 질문 전체를 우회한다. LAN의 라우트도, 클라이언트 방화벽의 협조도, 어딘가에 공개한 포트도 필요 없다:

ssh -N -f -L 3000:10.8.0.6:3000 user@vpn-host

그러면 서비스는 터널을 유지하는 머신의 localhost:3000 에 나타나고 다른 어디에도 나타나지 않는다. 이것이 경계다. 워크스테이션 하나, 포트 하나, 그리고 무언가가 다시 시작하지 않으면 포워딩은 세션과 함께 죽는다. 스테이징 데이터베이스를 확인하는 관리자에게는 옳은 도구다. 다른 사람이 닿아야 하는 카메라나 스캐너에는 아니다. 사용자마다 자기 포워딩이 필요하기 때문이다.

또 다른 규칙을 넣기 전에 양방향을 확인하라

변환은 흔적을 남기고, 흔적을 읽는 것이 추측보다 낫다. VPN 호스트에서 conntrack -L 는 변환이 실제로 일어났는지, iptables -t nat -L -n -v 는 카운터가 움직였는지, tcpdump -ni any port 3000 는 어느 출발지 주소가 정말 도착했는지 보여준다. 클라이언트에서 ss -ltn 는 리스너가 터널이 닿을 수 있는 주소에 있음을 확인한다.

그다음 남길 형태를 고르라. 공개한 포트 하나, 전체 풀을 위한 라우트, 관리자 한 명을 위한 포워딩은 서로 다른 세 가지 약속이고, 그것들을 섞는 것이 동작하던 구성을 여섯 달 뒤 아무도 설명할 수 없는 구성으로 만드는 길이다. 어느 주소가 어디서 닿는지 적어 두면, 다음에 이것을 디버깅할 사람은 터널이 아니라 답에서 시작한다.

관련 글