scutil --dns는 설정 패널에 입력한 단 하나의 DNS 서버가 아니라, macOS가 실제로 참조하는 리졸버 구성을 출력합니다. 출력이 대부분의 예상보다 긴 이유는 시스템이 기본 경로, 범위가 지정된 인터페이스, 도메인별 전달을 위해 별도의 리졸버 블록을 유지하기 때문입니다. 출력 맨 위의 블록이 반드시 질의에 응답하는 블록은 아닙니다. 사내에서 확인되던 사설 이름이 VPN이 올라오는 순간 실패한다면, 불일치는 매칭 규칙과 경로에 있고 서버 주소에는 없습니다. 먼저 순서를 읽고, 그다음 실제 조회로 답을 증명하세요.

주소보다 블록 순서를 먼저 읽기

각 항목은 resolver #N로 시작하며 nameserver[0], 때로는 if_index, flags 줄, reach를 나열하고, 블록이 단순한 기본값이 아니면 domain와 search domain 목록도 보여 줍니다. 도메인이 없는 1번 리졸버가 공개 이름을 처리하는 기본값입니다. 플래그에 Scoped를 지닌 블록은 if_index가 지정한 인터페이스에 묶이고, Supplemental로 표시된 것은 그 아래 나열된 도메인에만 적용됩니다.

선택은 접미사 일치입니다. intranet.corp.example 같은 이름에서는 domain 항목이 가장 길게 일치하는 접미사인 리졸버를 macOS가 우선하므로, domain : corp.example가 있는 블록은 출력에서 아주 아래에 있어도 기본값을 이깁니다. 같은 길이의 도메인 사이의 동점은 order가 해소합니다. 첫눈에는 이 이상 거의 필요하지 않으니, 블록 하나를 처음부터 끝까지 읽고 싶을 때는 전체 출력, 블록 순서를 익히려면 앞부분 몇 줄, 필드 줄 옆에 블록 머리글을 붙여 보려면 걸러 낸 한 차례면 충분합니다. 범위 지정 블록은 자체 제목 아래 나타나며 위 목록의 연속처럼 읽히므로, 출력을 인용할 때는 항상 머리글을 시야에 두세요.

scutil --dns
scutil --dns | sed -n '1,45p'
scutil --dns | grep -nE '^resolver #|^DNS configuration'
scutil --dns | grep -nE 'resolver #[0-9]+|if_index|domain|flags|nameserver'

if_index를 터널 인터페이스와 맞추기

인덱스는 그 뒤에 있는 인터페이스를 지목할 수 있을 때만 쓸모가 있습니다. scutil --nwi는 시스템이 보유한 네트워크 정보를 나열하며 기본 인터페이스와 도달 가능성을 포함하고, ifconfig -l는 이름을 알려 주는데 utun0부터 utun9까지가 클라이언트가 보통 만드는 터널 후보입니다. Apple은 자체 점검 예로 scutil --dns를 배포 가이드에서 사용하며, Network Extension이 선택한 도메인을 선택한 리졸버로 라우팅할 수 있는 방식을 문서화하며, 범위 지정 블록이 드러내는 것이 바로 그 메커니즘입니다.

문제를 결정하는 점검은 리졸버가 터널이 설치한 경로 위에 있는지입니다:

scutil --nwi
ifconfig utun4
route -n get 10.20.0.53
netstat -rn | grep -m5 utun

route -n get가 리졸버 항목과 맞지 않는 게이트웨이와 인터페이스를 돌려준다면 주소는 완전히 올바르면서도 도달할 수 없을 수 있습니다. 회사 서브넷만 실어 나르는 분할 터널은 그 바깥의 DNS 서버로 향하는 질의를 버리고, 실패는 시간 초과로 나타나므로 구성 출력은 계속 깨끗해 보입니다.

시스템 리졸버에 먼저, 그다음 서버에 질의하기

세 도구는 서로 다른 세 질문에 답하며, 이를 혼동하면 이 분야의 잘못된 결론 대부분이 생깁니다. dscacheutil -q host -a name는 일반 애플리케이션이 쓰는 것과 같은 시스템 리졸버를 거치므로 범위 지정과 보조 블록이 적용됩니다. 서버 인자가 없는 dig는 /etc/resolv.conf를 읽는데, 이는 macOS의 범위 지정 해석에서 진실의 원천이 아니며 도메인별 규칙을 완전히 건너뜁니다. dig @address는 정확히 한 서버에만 도달하고 선택에 관해서는 아무것도 알려 주지 않습니다.

dscacheutil -q host -a name intranet.corp.example
dig intranet.corp.example
dig @10.20.0.53 intranet.corp.example
nslookup intranet.corp.example

첫 명령이 응답하고 둘째가 NXDOMAIN을 반환하면 찾던 분리가 확인된 것이고 아무것도 고장 나지 않았습니다. 첫째가 시간 초과인데 dig @10.20.0.53가 응답한다면 리졸버는 동작하지만 터널이 고른 경로로는 도달할 수 없으므로, 다음 단계는 DNS 설정을 고치는 것이 아니라 route -n get입니다. 사설 이름은 티켓이나 공유 메모에 붙여 넣는 어떤 것에도 남기지 마세요.

출력이 보여 줄 수 없는 것

리졸버 목록 위나 옆에는 여러 계층이 자리하며, 각각은 출력이 전혀 언급하지 않는 곳으로 질의를 보낼 수 있습니다. 보안 DNS를 켠 브라우저는 자체 암호화 채널로 해석할 수 있으므로 dscacheutil가 시간 초과되는 동안 페이지가 로드되는 것은 모순이 아닙니다. 보안 소프트웨어가 설치한 DNS 프록시는 Network Extension 제공자로 동작하며 질의를 스스로 전달하고, scutil --dns는 구성된 내용을 보여 줄 뿐 프록시가 최종적으로 사용한 상위 주소를 보여 주지는 않습니다. 검색 도메인은 세 번째 주름을 더하는데, 자격 없는 이름에는 나열된 접미사가 순서대로 붙으므로, 응답한 블록은 전혀 의심하지 못한 블록일 수 있습니다.

오래된 응답은 가장 싸게 배제할 수 있고 가장 자주 건너뛰는 항목입니다:

sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
scutil --dns | head -20

플러시 후 출력이 달라지면 상태가 캐시되어 있었고 이전 출력은 더 이상 존재하지 않는 터널을 설명한 것입니다. 달라지지 않으면 구성은 그대로이므로 질문은 경로와 애플리케이션 정책으로 옮겨 갑니다.

전과 후를 위한 짧은 실행 절차

정책이 허용하는 곳에서만 기준선을 캡처하고 파일은 동기화 폴더 밖에 두세요. 아래 순서는 제가 클라이언트 머신에서 실행하는 것입니다——기준선을 캡처한 뒤 연결하고, 다시 캡처한 뒤 비교하고 조회합니다.

scutil --dns > ~/dns-before.txt
# connect the VPN, then:
scutil --dns > ~/dns-after.txt
diff -u ~/dns-before.txt ~/dns-after.txt
dscacheutil -q host -a name intranet.corp.example

차이는 줄이 아니라 블록 단위로 읽으세요. 터널 인터페이스 인덱스와 사설 접미사용 domain 항목을 지닌 새 범위 지정 리졸버가 기대하는 형태입니다. 공개 주소를 가리키는 새 기본 리졸버는 클라이언트 프로필 소유자에게 올릴 만한 발견입니다. 그런 다음 셸에서만이 아니라 실제로 실패한 애플리케이션 안에서 조회를 반복하고, 결과 옆에 route -n get의 인터페이스를 적어 두세요.

변경을 되돌리려면 연결을 끊고 기준선 캡처를 반복하세요. 범위 지정 블록이 사라졌다면 클라이언트가 스스로 정리한 것입니다. 블록이 더 이상 없는 인터페이스에 대해 여전히 나열되어 있다면, 상태가 오래된 것이고 누군가 프로필을 건드리기 전에 플러시와 재연결이 가장 부드러운 수정입니다.

이 순서로 네 가지 점검을 합니다: 리졸버 블록이 이름의 가장 긴 접미사와 일치하는지, 그 블록이 터널이 설치한 인터페이스에 묶여 있는지, 그 네임서버로 가는 경로가 터널을 쓰는지, 그리고 dig가 아니라 시스템 리졸버가 답을 돌려주는지입니다. 처음 실패한 점검이 DNS 정책, 터널 경로, 애플리케이션 중 무엇을 바꿔야 하는지 알려 줍니다. 넷 다 통과하는데 이름이 여전히 실패한다면 문제는 DNS 위에 있고, 다음에 볼 것은 그 앞에서 53번 포트에 응답하는 모든 것입니다.

관련 글