CGNAT가 포트를 막을 때 VPS를 통해 홈 서버를 공개하기
브라우저에서 집 안의 기계까지의 경로: 회선이 CGNAT 뒤에 있음을 확인하고, DNS를 VPS로 향하게 하고, 한 포트에 DNAT를 걸고, 돌아오는 경로를 고친 뒤 외부에서 검증한다.
나는 바깥에서 시작했다. 사용자가 하듯이 휴대폰 브라우저에서 모바일 데이터로 이름을 입력하고, 연결이 시간 초과까지 매달려 있는 것을 지켜봤다. 이름은 해석되었으니 DNS는 정상이었다. 집 공유기의 포트 포워딩 페이지도 막다른 길이었는데, WAN 주소가 100.64.31.42로 표시되었고, 그것은 통신사가 인터넷에서 나에게 라우팅해 줄 수 있는 주소가 아니다. 그 숫자가 전체 고장을 설명한다. 회선은 통신사급 NAT 뒤에 있고, 터널의 내 쪽 공인 주소는 통신사 소유다. 다음은 멈춰 있던 탭에서 응답하는 서비스까지 내가 걸어온 경로다.
클라이언트에서 시작해 안쪽으로 들어가기
어떤 규칙을 바꾸기 전에 사슬이 어디서 끊기는지 증명하라. 노트북에서 dig +short files.example.net 을 실행해 VPS 주소를 돌려받았고, 이로써 내 기기의 오래된 DNS 문제는 배제되었다. 이어서 curl -v --connect-timeout 5 files.example.net:5022 은 거부되지 않고 시간 초과가 되었다. 이 차이는 중요하다. 시간 초과는 보통 패킷이 도중에 버려진다는 뜻이고, 거부는 무언가가 응답하며 아니라고 말했다는 뜻이다.
안쪽으로 다음 단계는 VPS 자체였다. ss -ltnp 은 5022에서 듣고 있는 것을 보여주지 않았는데, DNAT 규칙이 생기기 전에는 당연한 일이다. 같은 기기에서 wg show (OpenVPN이면 ip -br a show tun0)으로 집 쪽 피어가 살아 있고 핸드셰이크가 최신임을 확인했다. 피어가 죽어 있으면 어떤 NAT 규칙도 도움이 되지 않고, 집 공유기 VPN 페이지의 초록불도 아무 의미가 없다.
집 회선이 정말 CGNAT 뒤에 있는지 확인하기
공유기가 WAN 인터페이스에서 보고하는 주소를 외부 서비스가 보는 주소와 비교하라. 100.64.0.0/10, 즉 100.64.x.x 부터 100.127.x.x 까지는 RFC 6598의 공유 주소 대역이며 인터넷에서 도달할 수 없다. 공유기가 평범한 사설 주소를 주장하는데 공인 조회가 무관한 값을 돌려줄 때도 결론은 같다. CGNAT 회선은 다음 임대까지만 유지되는 공인 주소를 받을 수도 있어서, 포트 포워딩이 한 시간쯤 동작하는 것처럼 보이다가 조용히 죽는다.
일부 통신사는 진짜 공인 주소를 주면서도 가정용 회선에서 들어오는 80과 443을 막는다. 서비스가 웹뿐이고 WAN 주소가 정말 공인이라면 중계를 만들기 전에 지원팀에 물어보라. 아니라면 아래 설계가 모든 포트에 똑같이 적용되며, 공유기 쪽 실험은 멈추는 편이 낫다. 통신사 망의 변환 장비가 연결이 당신의 WAN 인터페이스에 닿기 전에 끝내버리기 때문이다.
이름을 VPS 주소로 향하게 하기
DNS는 트래픽을 전달하지 않는다. 따라서 서비스의 A 레코드에는 VPS의 공인 주소가 들어가야 한다. 192.168.1.20을 가리키는 A 레코드는 이미 VPN 안에 있는 클라이언트에게만 쓸모가 있고, 인터넷에서는 아무도 라우팅할 수 없는 주소로 해석된다. VPS 자체의 관리 접근에는 별도 이름을 두어, 어떤 레코드가 어느 호스트를 가리키는지 헷갈리지 않게 한다.
VPS와 집 기기가 모두 HTTP를 제공한다면 각자 이름을 주고 누가 응답할지는 VPS가 정하게 하라. 둘을 한 이름으로 합치고 프록시가 올바른 백엔드를 고르기를 기대하는 것은, 오후 내내 잘못된 호스트를 디버깅하는 지름길이다.
포트 하나만 DNAT, SSH는 22에 두기
직접적인 방법은 VPS에서의 목적지 NAT다. 실행 중인 커널의 포워딩을 켜고, 규칙에는 집 서버의 터널 주소를 넣고 LAN 주소는 넣지 않는다.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp -d 198.51.100.10 --dport 5022 -j DNAT --to-destination 10.8.0.2
VPS 자체로 가는 SSH는 22에 두고, 다른 포트 예컨대 5022를 집 기기로 넘겨라. 22를 꺼진 기기로 넘기면 그것을 고칠 수 있는 유일한 경로를 잃는다. 같은 논리가 프록시에도 적용된다. VPS의 관리 포트는 중계 규칙 밖에 두어라.
규칙이 동작하면 영구화하라. Debian이나 Ubuntu에서는 apt install iptables-persistent 과 이어서 netfilter-persistent save 이 /etc/iptables/rules.v4에 기록한다. firewalld 호스트에서는 존에 영구 포트 포워딩을 두는 것이 같은 역할이며, 적용 전에 firewall-cmd --reload 이 필요하다. 메모리에만 사는 규칙은 통신사의 첫 유지보수 재시작에서 사라진다. 대개 좋지 않은 시각이다.
돌아오는 경로를 고치거나, 필요 없게 만들기
DNAT은 목적지만 다시 쓴다. 집 서버는 원래 클라이언트 주소에서 오는 연결로 보게 되고, 기본 경로가 집 공유기를 향하면 응답은 집 WAN으로 나가 통신사의 변환 장비에서 버려진다. 이 비대칭이 DNAT 규칙이 완벽해 보인 뒤에 내가 만난 두 번째 고장이다.
두 가지 수정이 효과가 있다. 집 기기에 정책 경로를 주어 터널에서 온 트래픽이 터널로 돌아가게 하라.
ip rule add from 10.8.0.2 table 100
ip route add default dev wg0 table 100
또는 VPS가 출발지를 다시 쓰게 해 응답이 도달할 수 있는 주소를 갖게 하고, 나갈 때 conntrack이 되돌리게 하라.
iptables -t nat -A POSTROUTING -d 10.8.0.2 -p tcp --dport 5022 -j SNAT --to-source 10.8.0.1
SNAT 방식은 진짜 클라이언트 주소를 집 서버에서 숨기므로 애플리케이션 로그에는 상대로 10.8.0.1이 남는다. 원래 주소를 애플리케이션 계층에서 넘기거나, 클라이언트별 기록을 잃는 것을 받아들여라. 다만 사고 중에 발견하는 대신 의식적으로 결정하라.
바깥에서 경로 전체 확인하기
VPS도 집도 아닌 네트워크에서 검증하라. 모바일 데이터의 휴대폰이나 다른 곳의 저렴한 셸이면 된다. nc -vz files.example.net 5022 은 연결 성공을 보고해야 하고, openssl s_client -connect files.example.net:443 -servername files.example.net 는 공인 이름의 인증서를 보여줘야 한다. 그다음 통신사의 보안 그룹이나 클라우드 방화벽을 확인하라. 패킷을 한 번도 보지 못하는 DNAT 규칙은 보통 호스트 한 계층 위에서 막힌다.
마지막으로 터널이 끊겼을 때 무슨 일이 일어날지 정하라. DNAT 규칙은 연결을 계속 받아 조용히 죽은 터널로 밀어 넣고, 그것은 망가진 서비스와 똑같이 보인다. 피어가 죽어 있는 동안 그 포트를 거부하는 상태 점검을 VPS에 두면 몇 줄이면 되고 한 시간의 추측을 아낀다. 그 뒤로는 규칙 편집을 멈추고 하루 지켜본 다음 믿어라.