클라이언트에 리졸버 주소를 주는 일은 쿼리가 어디로 가는지를 정할 뿐, 리졸버가 어떤 출발지를 보게 될지는 정하지 않는다. 라우팅과 포워딩, 주소 변환이 양 끝 사이에서 차례로 개입한다. 출발지 문제를 다루는 절차서는 쿼리를 홉 단위로 따라가며 각 관찰 지점이 실제로 무엇을 보았는지 기록한다. 순서가 중요한 이유는, 관찰보다 먼저 내린 단정이 결국 형식만 좋은 추측이기 때문이다.

증거를 모으기 전에 구성을 적어 둔다

DNS 출발지에 대해 뭔가 알려 줄 수 있는 지점은 네 곳이며, 모두 같은 말을 하지는 않는다. 클라이언트 커널은 어떤 출발지를 쓸지 알고, 포워더는 업스트림으로 무엇을 보냈는지 알며, 변환 장치는 무엇을 다시 썼는지 알고, 수신 리졸버는 무엇이 도착했는지 안다. 무엇이든 모으기 전에 이 가운데 실제로 닿을 수 있는 지점이 어디인지 적어 둔다.

Observation pointWhat it can proveWhat it cannot proveHow to read it
client kernelwhich source address it would use for that resolveranything a device in between rewroteip route get against the resolver address
forwarderthat it sent an upstream query, and from which sourcewhat the client originally asked forits own log and its cache state
translating devicethat a source was rewritten, and to whatwhether the query was legitimateNAT policy and its counters
receiving resolverthe address that actually arrivedwhich client behind that address askeda scoped capture on port 53

사용 중이라고 믿는 리졸버를 포함해 기대도 함께 적는다. 분할 경로는 쿼리를 전혀 다른 곳으로 보낼 수 있고 캐시는 업스트림 교환 없이 응답할 수 있으므로 머릿속에만 둔 가정은 첫 번째 뜻밖의 상황에서 무너진다. 선언한 설계와 관찰한 경로는 같은 기록의 서로 다른 칸에 두고, 둘이 어긋날 때 쫓아갈 만한 질문을 찾은 것으로 본다.

쿼리를 실제로 보낸 프로세스가 무엇인지 확인한다

클라이언트에서 시작해 리졸버 주소와 쿼리 이름, 시각을 기록하고, 어느 프로세스가 보냈는지 확인한다. 아래 첫 줄은 UDP 소켓을 쥐고 있는 프로세스를 지목하며, 그것이 이 소제목이 약속하는 점검이다. 그 프로세스가 시스템 리졸버가 아니라 브라우저 보조 프로세스나 컨테이너 런타임이라면, 나머지 추적은 아무도 묻지 않은 경로를 설명하는 셈이다.

ss -tunp 'dport = :53'
dig @10.20.30.53 host.corp.example +noall +answer
ip route get 10.20.30.53

인터페이스가 여러 개인 호스트에서는 다른 곳을 보기 전에 커널이 그 목적지에 어떤 출발지 주소를 고르는지 먼저 확인한다. 경로가 온전히 통제 아래에 있는 마지막 지점이 거기이고, 리졸버가 보고하는 값과 비교할 값도 그것이다. 어긋난다면 중간의 무언가가 패킷을 다시 썼거나, 서로 다른 쿼리 두 개가 나갔다는 신호다.

테스트용 이름은 내부에 둔다. 내부 이름을 공개 리졸버로 보내면 내부 경로에 대해 아무것도 증명하지 못하고 공개할 필요도 없던 이름을 공개하게 되며, 보안 DNS를 켠 브라우저는 그 소켓 목록에 아예 나타나지 않는데 그 부재 자체가 답이다.

포워더는 두 번째로 별개의 쿼리를 낸다

포워더는 한 번의 교환을 두 번으로 바꾼다. 하류 요청이 도착하면 포워더는 스스로 고른 주소에서 자기 쿼리를 업스트림으로 보낸다. 업스트림 리졸버가 무엇을 보는지는 클라이언트가 아니라 그 설정이 정한다.

dnsmasq 설명서가 읽을 만한 이유가 바로 여기에 있다. 포워더가 업스트림 서버를 어떻게 고르고 수신 쪽을 어떻게 묶는지 적혀 있으며, 그 구성 어디에도 클라이언트 출발지가 업스트림으로 실려 가지 않는다. 그래서 게이트웨이 주소가 찍힌 리졸버 로그는 정상이지 고장의 증거가 아니다. 부재도 똑같이 잘못 읽기 쉬운데, 캐시 적중은 업스트림 쿼리를 전혀 만들지 않고 한 번도 도착하지 않은 쿼리와 똑같이 보이기 때문이다. 두 결론 중 하나를 내리기 전에 포워더 설정과 캐시 상태를 읽어야 하며, 질문은 둘이고 볼 곳도 둘이다.

수신 리졸버에서 출발지를 읽는다

이 문제를 매듭짓는 유일한 관찰은 서버 쪽에서 이뤄진다. 클라이언트 인터페이스 목록에서 끌어낸 것은 리졸버가 실제로 무엇이 도착했는지 보고하기 전까지 가설일 뿐이다.

tcpdump -ni any port 53 and host 10.20.30.53

리졸버 호스트에서 캡처하되 캡처는 짧고 범위를 좁히고, 정책이 허용하는 곳에 보관한다. 관찰된 출발지가 변환된 주소라면 다음 질문은 어느 장비가 변환했는지, 그 정책이 의도한 것인지이다. 캡처는 무엇이 도착했는지 답할 뿐, 정책이 왜 허용했는지는 설명하지 않는다. 기록에 관찰 지점과 홉을 적는 이유는, 게이트웨이에 대한 프라이버시 주장이 경로 전체가 아니라 그 홉에 대한 주장이기 때문이다.

발견을 주소가 아니라 경로로 쓴다

클라이언트와 리졸버, 개입한 포워더, 홉마다 관찰한 출발지, 그리고 그것을 규율하는 정책을 기록한다. 그 기록이 있으면 앞으로의 많은 질문은 추가 캡처 없이 답할 수 있다. 사슬로 그리는 이유는, 주소 목록으로는 어느 홉이 무엇을 낳았는지 가려지기 때문이다.

그림에 날짜와 클라이언트 버전, 설정 리비전을 붙여 나중에 읽는 사람이 아직 유효한지 판단할 수 있게 한다. 전송 출발지는 서비스가 다루는 나머지 모든 것과 분리한다. 게이트웨이 주소가 보인다고 해서 무엇이 더 보관되는지는 알 수 없고, 리졸버의 위치도 어느 클라이언트가 물었는지는 말해 주지 않는다. 이런 주장들을 갈라 두는 것이 검토에서 버틸 수 있는 결론을 만든다.

관련 글