WireGuard DNS DNAT: 응답 경로를 계속 작동하게 하기
DNAT는 목적지를 다시 쓰며 출발지는 건드리지 않는다. 규칙을 추가하기 전에 업스트림 리졸버가 어떻게 응답할 수 있는지 결정하고, 각 일치 범위를 좁게 유지한다.
WireGuard 클라이언트의 DNS 쿼리를 다른 리졸버로 보내는 일은 규칙 하나처럼 보이지만 세 가지 결정처럼 동작한다. 목적지를 다시 써야 하고, 패킷을 전달해야 하며, 응답은 게이트웨이가 만든 변환 상태를 거쳐 돌아올 길을 찾아야 한다. 세 번째를 건너뛰면 쿼리는 깔끔하게 떠나지만 클라이언트는 영원히 기다린다.
10.77.0.0/24 안의 승인된 클라이언트가 wg0로 들어와 광고된 주소 10.77.0.1를 질의하고 203.0.113.53의 리졸버가 필요한 예를 들어 보자. 업스트림 주소는 문서용 자리표시자이며 실제 동작하는 서비스가 아니다.
쿼리 리다이렉션은 목적지만 바꾼다
PREROUTING의 목적지 NAT 규칙은 라우팅 결정이 내려지기 전에 주소를 다시 쓰므로, 패킷은 이후 예전 목적지 대신 새 목적지로 라우팅된다. 클라이언트의 출발지 주소는 그 작업으로 바뀌지 않는다. 그래서 완전해 보이는 규칙도 침묵을 낳을 수 있다. 요청은 이제 클라이언트의 사설 범위를 들어본 적 없는 리졸버로 간다. 게이트웨이는 필드 하나를 변환하고 두 번째 문제를 물려받았다.
두 DNS 전송 모두 일치하는 규칙이 필요하다. 응답이 잘리면 클라이언트가 TCP로 되돌아가기 때문이다.
아래 규칙은 수신 인터페이스 하나, 클라이언트 범위 하나, 원래 목적지 주소 하나를 선택한다. 게이트웨이의 다른 무엇도 영향을 받지 않으며, 게이트웨이 자체의 쿼리는 전혀 일치하지 않는다.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p udp --dport 53 -j DNAT --to-destination 203.0.113.53:53
iptables -t nat -A PREROUTING -i wg0 -s 10.77.0.0/24 -d 10.77.0.1 -p tcp --dport 53 -j DNAT --to-destination 203.0.113.53:53
응답 경로는 별도의 결정이다
응답이 돌아오게 하는 배치는 두 가지이며, 하나를 일찍 고르면 디버깅 세션 하나를 아낀다. 업스트림 리졸버가 이 게이트웨이를 거쳐 10.77.0.0/24로 돌아가는 경로를 갖거나, 게이트웨이가 송신 측에서 출발지를 변환해 응답이 업스트림이 이미 아는 주소로 도착하게 한다. 둘 다 동작한다. 업스트림에 대한 통제와 맞는 것은 하나뿐이다.
라우팅을 먼저 시도할 가치가 있다. 클라이언트 주소를 리졸버에게 보이게 유지하고 두 번째 변환 계층을 피하기 때문이다. 업스트림이 통제 밖에 있다면 특정 송신 인터페이스에서 출발지를 변환하는 것이 현실적인 선택이다.
그 변환을 리다이렉션만큼 좁게 제한하라. 그렇지 않으면 규칙은 우연히 일치하는 모든 것에 대한 범용 NAT가 된다.
iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p udp --dport 53 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.77.0.0/24 -d 203.0.113.53 -o eth0 -p tcp --dport 53 -j MASQUERADE
연결 추적이 매핑을 쌍으로 유지하므로, 돌아올 때 응답은 클라이언트의 원래 튜플로 다시 쓰인다. 송신 규칙이 없으면 리졸버는 도달할 수 없는 사설 주소로 응답을 보내고, 클라이언트는 오류 대신 시간 초과를 본다.
규칙은 작업이 보이는 것보다 좁게 쓰라
포워딩 권한도 같은 범위에 속한다. 머신의 모든 DNAT 연결을 받아들이는 대신 클라이언트에서 리졸버로 가는 흐름과 conntrack이 이미 인식하는 반환 트래픽을 허용하라. 포괄적 허용은 DNS와 무관한 포트 포워딩까지 승인한다.
게이트웨이 자체의 리졸버는 다른 코드 경로다. 로컬에서 발행한 쿼리는 OUTPUT을 지나므로, 게이트웨이 셸에서 테스트하면 PREROUTING 규칙도 포워딩 정책도 연습하지 않는다. 그래서 로컬 테스트는 통과하는데 모든 클라이언트가 실패할 수 있고, 그 반대도 마찬가지로 흔하다.
여기서는 포트 443의 HTTPS 기반 DNS도, 포트 853의 TLS 기반 DNS도 다루지 않으며 IPv6도 다루지 않는다. IPv6 리졸버를 설정한 클라이언트는 이 배치 전체를 기꺼이 무시할 것이다.
새 쿼리가 증명하는 것
연결 추적은 이전 매핑을 기억하므로, 규칙 변경 전에 보낸 쿼리가 예전 상태에서 응답되어 새 규칙이 고장 난 것처럼 보일 수 있다. 새 출발지 포트로 시작하고, 클라이언트가 응답을 캐시하는 곳에서는 새 이름으로 시작하라.
dig @10.77.0.1 example.com A +short
conntrack -L -p udp --dport 53 | head
첫 명령은 클라이언트 경로가 애초에 해석되는지를 보여주고, 두 번째는 게이트웨이가 실제로 설치한 변환을 튜플의 양방향과 함께 보여준다. 올바른 리졸버를 가리키지만 응답 카운터가 멈춰 있는 매핑은 필터 문제가 아니라 반환 경로 문제다.
무언가를 리다이렉트하기 전에 리졸버가 기대하는 이름을 아는지 확인하라. 공개 이름에는 응답하지만 사설 영역에는 응답하지 않는 대체는 동작하는 조회를 고장 난 터널처럼 보이는 NXDOMAIN으로 바꾼다. 클라이언트 경로가 개입하기 전에 게이트웨이에서 리졸버를 직접 테스트하라. 그리고 iptables 확장 참조는 이 규칙들이 의존하는 DNAT와 masquerade 의미론을 문서화한다.