내부 호스트 이름이 dig에서는 확인되지만 VPN의 curl에서는 실패하는 이유
dig는 응답하지만 curl은 호스트를 확인할 수 없다고 보고합니다. 두 조회 체인을 비교한 뒤 nsswitch, 링크별 DNS 또는 잘못된 검색 접미사를 고치십시오.
노트북이 VPN에 연결되어 있고 dig service.internal.example이 10.x 주소로 응답하는데도 curl은 여전히 Could not resolve host로 끝납니다. 터널이 문제가 아닙니다. dig 쿼리를 아무 불평 없이 전달했습니다. 차이는 누가 질문하느냐입니다. dig는 사용자가 지정한 한 대의 서버로 쿼리 하나를 보내고, curl은 운영 체제의 조회 체인을 거칩니다. 그 체인에는 어느 서버에 물을지에 대한 자체 규칙이 있습니다.
같은 이름에 대한 두 번의 조회
dig는 자체 stub resolver입니다. /etc/resolv.conf를 읽고 네임서버를 고르고 A 또는 AAAA 쿼리 하나를 보내 응답을 출력합니다. nsswitch를 완전히 건너뛰며 systemd-resolved나 NetworkManager가 그 파일 밖에 두는 링크별 상태를 전혀 모릅니다. 그래서 dig의 응답이 맞아도 응용 프로그램이 무엇을 보게 될지는 알려 주지 못합니다.
응용 프로그램은 getaddrinfo를 호출하고, getaddrinfo는 더 긴 체인을 지납니다. 먼저 nsswitch의 모듈 순서, 다음으로 이름을 다루는 모듈, 마지막으로 현재 링크 구성에서 만든 resolver입니다. systemd 머신에서는 쿼리가 127.0.0.53으로 가고, systemd-resolved가 tun 인터페이스에 붙은 라우팅 도메인을 기준으로 링크마다 상위 서버를 고릅니다.
두 명령이 추측 없이 그 계층을 갈라 줍니다:
getent ahosts service.internal.example
resolvectl query service.internal.example
getent는 curl과 같은 경로를 쓰므로, dig가 동작하는데 getent가 실패하면 결함은 패킷 라우팅이 아니라 조회 정책에 있습니다. resolvectl query는 응답을 준 인터페이스와 상위 서버도 출력하므로 터널 링크가 이겼는지 물리 링크가 이겼는지 보여 줍니다.
응용 프로그램이 실제로 쓰는 resolver 읽기
/etc/resolv.conf는 대부분의 데스크톱에서 /etc/resolv.conf가 생성되며, VPN 클라이언트가 그것과 상호 작용하는 방식은 네임서버를 앞에 추가하거나, 파일을 덮어쓰거나, 자기 서버를 NetworkManager에 넘겨 데몬이 다시 쓰게 하는 세 가지입니다. 링크별 DNS를 유지하는 것은 세 번째 방식뿐인데, 서버가 하나의 전역 목록으로 평평해지지 않고 인터페이스에 붙어 있기 때문입니다.
NetworkManager 시스템에서는 결과가 /etc/NetworkManager/NetworkManager.conf의 backend에 달려 있습니다:
[main]
dns=systemd-resolved
dns=default이면 클라이언트의 서버가 이기는 순서대로 resolv.conf에 합쳐지고, 사설 조회가 여전히 공용 resolver로 갈 수 있습니다. 링크를 이해하는 backend, systemd-resolved 또는 dnsmasq을 쓰면 터널의 서버는 터널에만 묶이고 그 라우팅 도메인에 맞는 이름만 그리로 갑니다. 이 범위가 바로 실무에서 split DNS가 뜻하는 것이며, 클라이언트가 지원한다고 주장할 때 가장 자주 빠지는 부분입니다.
무엇이든 바꾸기 전에 상태를 읽으십시오:
resolvectl status
resolvectl dns tun0
tun 인터페이스에 DNS 서버와 사설 접미사를 위한 라우팅 도메인이 함께 나열되고 ~internal.example로 표시되기를 원합니다. 링크에 서버가 없으면 클라이언트가 아무것도 밀어 넣지 않은 것입니다. 서버는 있는데 도메인이 없으면 조회가 전부 그리로 가거나 전혀 가지 않는다는 뜻이고, 클라이언트에 따라 다르며, 둘 다 split DNS가 아닙니다.
nsswitch가 조회를 너무 일찍 끝내는 지점
일반적인 데스크톱 줄은 이렇게 생겼습니다:
hosts: files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns myhostname
mdns4_minimal은 멀티캐스트 DNS를 다루며 .local로 끝나는 모든 이름에 대해 권리를 주장합니다. api.infra.svc.cluster.local 같은 Kubernetes 서비스도 .local로 끝납니다. mDNS 모듈이 NOTFOUND를 보고하면 [NOTFOUND=return] 동작은 dns 모듈로 넘어가지 않고 조회 전체를 끝내므로, 이름은 VPN resolver로 전혀 가지 않습니다. dig는 그 파일을 완전히 우회하며, 두 도구가 서로 다른 결론에 이르는 이유가 바로 여기에 있습니다.
이것이 이 문제에서 가장 날카로운 대비이며, 결국 VPN보다 오래된 구성 파일의 단어 하나로 귀결됩니다. 동작을 [NOTFOUND=continue]로 바꾸면 프린터와 로컬 장치를 위한 mDNS는 남기고 DNS가 이름을 보게 합니다. 줄을 hosts: files dns로 바꾸는 것도 되지만 더 거칩니다: 실제로 쓰는 프린터의 .local 확인을 포기하게 됩니다.
정확히 하나의 네트워크에만 사는 이름
사설 영역은 정확히 한 곳에서만 권위를 가집니다. ip-10-0-4-31.eu-west-1.compute.internal 같은 AWS 이름은 해당 리전의 VPC resolver를 통해 응답되며, VPN 끝점이 리소스와 같은 리전에 있는 동안만 그렇습니다. 프랑크푸르트 끝점에 연결해 아일랜드의 compute.internal 이름을 물으면 로컬 설정으로는 아무것도 해결되지 않습니다: 그 레코드를 가진 영역은 거기서 닿을 수 없기 때문입니다.
검색 도메인은 반대 그림을 만듭니다. 맨 이름 service은 resolver가 적용된다고 보는 접미사로 채워지고, VPN 링크가 있으면 로컬 도메인이 회사 도메인을 이길 수 있습니다. 끝에 점을 붙여 물으면 추측이 사라지고, getent ahosts service.internal.example.가 되는데 짧은 이름이 안 된다면 고칠 것은 터널 쪽 검색 도메인이고 서버 주소가 아닙니다.
관리되는 클라이언트는 시스템 resolver 위에 세 번째 계층을 더합니다. Windows VPN 프로필은 자체 이름 확인 정책과 앱별 규칙, 터널이 내려가 있어도 적용되는 NRPT 표를 실을 수 있습니다. 자체 DNS 캐시를 가진 응용 프로그램은 .NET과 Java 런타임에서 흔하며, 체인이 고쳐진 뒤에도 오래된 실패를 오래 유지합니다. Apple 클라이언트는 split DNS를 resolv.conf가 아니라 DNS 설정의 일치 도메인으로 구성하고, Chromium이나 Firefox는 보안 DNS 모드에서 공용 이름에 대해 시스템 resolver를 우회할 수 있습니다. Microsoft의 VPN 이름 확인 참고 자료는 그 정책 계층의 Windows 쪽을 설명합니다.
어느 계층을 고칠지 판단하기
고칠 것을 차례로 시도하지 말고 증상에서 계층으로 나아가십시오:
| Symptom | What it means | Check first |
|---|---|---|
| dig answers, getent fails | lookup chain, not the packet path | nsswitch line and resolvectl status |
| Timeout rather than NXDOMAIN | private resolver unreachable through the tunnel | resolvectl dns tun0, route to that address |
| NXDOMAIN | an authority answered and does not know the name | zone owner, endpoint region |
| FQDN works, short name fails | search suffix mismatch | resolvectl domain on the tun link |
| getent works, app fails | app cache or its own resolver | restart the app, check secure DNS |
두 줄이 무게의 대부분을 집니다. 시간 초과는 도달성 문제입니다: 사설 resolver가 터널로 응답하지 않으니 그 주소로 가는 경로와 링크의 DNS 서버가 생각하는 그 서버인지 확인하십시오. NXDOMAIN은 권위 문제입니다: 무언가 응답했는데 그 이름을 모른다는 뜻이므로 영역, 리전, 또는 이 쿼리를 받지 말았어야 할 resolver를 가리킵니다.
마지막 줄은 사람들이 놓치는 줄입니다. 두 반쪽이 모두 성공처럼 보이기 때문입니다. 짧은 이름이 FQDN과 다르게 확인되는 것은 접미사 불일치이며, 터널을 다시 연결해도 클라이언트가 보내는 접미사는 바뀌지 않습니다.
구성 파일을 고치기 전에 상태를 기록해 두면 그 수정이 효과가 있었는지 말할 수 있습니다:
resolvectl status > before.txt
getent ahosts service.internal.example >> before.txt
점검을 이 순서로 실행하고 처음 실패하는 곳에서 멈추십시오. getent와 resolvectl query가 일치해야 합니다: getent가 실패하는데 dig가 응답하면 조회 체인에 머물고 라우팅은 건드리지 마십시오. resolvectl status는 tun 인터페이스에 DNS 서버와 라우팅 도메인을 보여 줘야 합니다. nsswitch는 시험 중인 접미사에 대해 NOTFOUND로 일찍 돌아오면 안 됩니다. 이름은 끝점이 연결된 네트워크에서 권위가 있어야 하며 끝에 점을 붙여 검증해야 합니다. 응용 프로그램은 자체 캐시나 보안 DNS 재정의를 들고 있으면 안 됩니다.
실패한 첫 계층만 고친 뒤 같은 명령을 다시 실행하십시오. 두 가지를 한꺼번에 바꾸면 어느 쪽이 효과가 있었는지 말할 수 없고, 다음 장애는 조사를 처음부터 다시 시작하게 됩니다.