📚 네트워크 · 패킷 분석 › 01. TCP/IP 구조 이해 — 47편
이전 글: 46. 시퀀스 번호·ACK·윈도우 — TCP가 데이터를 빠짐없이 전달하는 방법 · 다음 글: 48. DHCP DORA 과정 — IP를 받는 4단계
참고(리눅스 시스템 기초): Linux 네트워크 구조 — 네트워크 설정 파일 위치 개요
사용자가 도메인을 입력한 순간부터 TCP 연결이 시작되기 전까지, 가장 먼저 일어나는 네트워크 행위가 이름 해석(Name Resolution) 입니다. 거의 모든 통신이 DNS를 거치기 때문에, DNS 흐름은 관제에서 "이 단말이 어디로 가려고 했는가"를 보여 주는 가장 이른 증거입니다.
그런데 이름 해석은 DNS 서버에만 묻는 단순한 과정이 아닙니다.
/etc/hosts 파일이 DNS보다 먼저 답할 수 있습니다. 이 경우 네트워크에 DNS 패킷이 전혀 남지 않습니다.즉 "DNS 로그에 없으니 접속하지 않았다"는 판단은 틀릴 수 있습니다. 이 글은 도메인이 IP가 되는 경로를 단계별로 나누고, 각 단계에서 무엇을 확인할 수 있는지 정리합니다. DNS 레코드 종류와 DNS 악용 탐지는 02. 포트 · 프로토콜 분석 시리즈의 07. DNS 레코드와 질의 유형·08. DNS 악용 유형에서 다룹니다.
| 요소 | 위치 | 역할 |
|---|---|---|
| 애플리케이션 | 클라이언트 | getaddrinfo() 같은 라이브러리 함수로 이름 해석 요청 |
| Stub Resolver | 클라이언트 (glibc 등) | 설정에 따라 hosts 파일·DNS 서버에 묻는 최소 기능 리졸버, 재귀 요청(RD=1) 전송 |
/etc/nsswitch.conf | 클라이언트 | hosts: 줄에서 조회 순서 결정 (예: files → dns) |
/etc/hosts | 클라이언트 | 수동 이름-IP 매핑, 보통 DNS보다 먼저 조회 |
| 로컬 캐시 | 클라이언트 (systemd-resolved 등) | 최근 결과 재사용 |
| 재귀 리졸버 (Recursive Resolver) | 사내 DNS, ISP, 공용 DNS | 클라이언트 대신 루트부터 차례로 물어 최종 답을 찾고 캐시 |
| 루트 서버 | 인터넷 | TLD 서버 위치 안내 (13개 이름의 서버, 애니캐스트로 다수 배치) |
| TLD 서버 | 인터넷 | .com, .kr 등 하위 도메인의 권한 서버 위치 안내 |
| 권한 서버 (Authoritative) | 도메인 소유자·호스팅 | 해당 도메인의 실제 답을 가진 서버 (응답에 AA 플래그) |
모든 DNS 응답 레코드에는 TTL(초) 이 있습니다. 리졸버는 TTL 동안 결과를 캐시하고, 같은 질문에는 외부에 묻지 않고 캐시로 답합니다. 캐시에서 답할 때는 남은 TTL이 줄어든 값으로 전달됩니다.
| RCODE | 이름 | 의미 |
|---|---|---|
| 0 | NOERROR | 정상 처리 (답이 비어 있을 수도 있음 — 이름은 있지만 요청한 유형의 레코드가 없음) |
| 2 | SERVFAIL | 리졸버가 답을 얻지 못함 (권한 서버 장애, 검증 실패 등) |
| 3 | NXDOMAIN | 그런 이름이 존재하지 않음 |
| 5 | REFUSED | 서버가 처리를 거부 (허용되지 않은 클라이언트의 재귀 요청 등) |
클라이언트(192.168.10.20)가 www.example.com에 접속할 때, 캐시가 모두 비어 있다고 가정한 흐름입니다.

그림 1. stub 리졸버 → 재귀 리졸버 → 루트·TLD·권한 서버 순서의 이름 해석
[애플리케이션] curl http://www.example.com
↓ getaddrinfo("www.example.com")
[Stub Resolver] /etc/nsswitch.conf hosts: files dns
↓ ① files → /etc/hosts 조회 ── 있으면 여기서 종료 (네트워크 패킷 없음)
↓ ② dns → 로컬 캐시(systemd-resolved 등) ── 있으면 종료
↓ ③ /etc/resolv.conf 의 nameserver 로 재귀 질의 (UDP 53, RD=1)
[재귀 리졸버 192.168.10.53] 캐시 확인 → 없음
↓ ④ 루트 서버에 질의 → "com 은 TLD 서버에게" (Referral)
↓ ⑤ .com TLD 서버에 질의 → "example.com 은 이 NS에게" (Referral)
↓ ⑥ 권한 서버에 질의 → "www.example.com A 203.0.113.80, TTL 3600" (AA=1)
↓ ⑦ 결과를 TTL 동안 캐시
[재귀 리졸버] → 클라이언트에 응답 (RA=1)
↓
[Stub Resolver] → 애플리케이션에 IP 반환 → TCP 연결 시작 (08편 Handshake)
실제로는 리졸버가 루트·TLD 정보를 이미 캐시하고 있어 ④·⑤가 생략되는 경우가 대부분입니다. 또한 IPv4(A)와 IPv6(AAAA) 주소를 동시에 질의하는 것이 일반적이어서, 패킷에는 질의 두 개가 나란히 보입니다.
전송 계층은 기본적으로 UDP 53이며, 응답이 커서 잘리면(TC 플래그) TCP 53으로 다시 질의합니다. 포트·전송 방식의 세부 내용은 02. 포트 · 프로토콜 분석 시리즈에서 다룹니다.
| 항목 | Ubuntu (최근 버전) | Rocky Linux |
|---|---|---|
| 로컬 리졸버 | systemd-resolved 기본 사용 | 기본은 NetworkManager가 resolv.conf 관리 (systemd-resolved 기본 비활성) |
/etc/resolv.conf | nameserver 127.0.0.53 (스텁) 을 가리키는 심볼릭 링크 | 실제 DNS 서버 IP 기록 |
| 실제 상위 DNS 확인 | resolvectl status | cat /etc/resolv.conf, nmcli dev show ens33 |
dig 패키지 | dnsutils (또는 bind9-dnsutils) | bind-utils |
Ubuntu에서 /etc/resolv.conf만 보면 DNS 서버가 127.0.0.53으로 보여 혼동하기 쉽습니다. 실제로 외부로 질의하는 상위 서버는 resolvectl status로 확인해야 합니다.
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu), 인터페이스 이름은 ens33 예시입니다.
# Rocky Linux
sudo dnf install -y bind-utils tcpdump
# Ubuntu
sudo apt update && sudo apt install -y dnsutils tcpdump
grep '^hosts' /etc/nsswitch.conf # 조회 순서
cat /etc/hosts
ls -l /etc/resolv.conf # Ubuntu는 심볼릭 링크 확인
cat /etc/resolv.conf
# Ubuntu (systemd-resolved)
resolvectl status ens33 # 이 인터페이스가 실제로 쓰는 DNS 서버
# Rocky (NetworkManager)
nmcli dev show ens33 | grep DNS
getent는 nsswitch 순서를 그대로 따르고, dig는 hosts 파일과 nsswitch를 무시하고 DNS 서버에 직접 묻습니다. 이 차이를 직접 확인합니다.
# 테스트용 hosts 항목 추가 (문서용 주소 사용, 실습 후 반드시 삭제)
echo '192.0.2.99 lab-test.example.com' | sudo tee -a /etc/hosts
getent hosts lab-test.example.com # → 192.0.2.99 (hosts 파일에서 응답)
dig +short lab-test.example.com # → hosts 무시, DNS 결과(없으면 빈 출력)
# 실습 후 제거
sudo sed -i '/lab-test.example.com/d' /etc/hosts
# 같은 질의를 간격을 두고 반복 → TTL 값 감소 확인
dig www.example.com A +noall +answer
sleep 5
dig www.example.com A +noall +answer
# Ubuntu: 로컬 캐시 통계 / 비우기
resolvectl statistics
sudo resolvectl flush-caches
# 루트부터 반복 질의를 직접 수행 (리졸버 대신 dig가 따라감)
dig +trace www.example.com
# 특정 서버에 재귀 없이 질의 → 위임(Referral) 응답 확인
dig @a.root-servers.net www.example.com A +norecurse
+trace는 외부 루트·TLD 서버에 직접 질의하므로, 사내망처럼 외부 53번 포트가 막힌 환경에서는 실패할 수 있습니다. 이것 자체가 "내부 리졸버만 쓰도록 통제된 망"이라는 확인이 됩니다.
sudo tcpdump -i ens33 -nn 'port 53'
# 다른 터미널에서
getent ahosts www.example.com # A·AAAA 질의 동시 발생 확인
📷 [실습 화면 삽입]
getent hosts와dig +short결과가 다르게 나온 화면 — hosts 파일이 DNS보다 우선한다는 증거
📷 [실습 화면 삽입]
dig +trace출력에서 루트 → .com → 권한 서버 순서로 위임되는 구간
dig www.example.com 출력 형식 예시(값은 환경마다 다름):
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41520
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
www.example.com. 2873 IN A 203.0.113.80
;; Query time: 3 msec
;; SERVER: 192.168.10.53#53(192.168.10.53) (UDP)
| 항목 | 해석 |
|---|---|
status: NOERROR | RCODE 0 |
flags: qr rd ra | 응답(qr), 재귀 요청(rd), 재귀 가능(ra). aa가 없으므로 캐시·재귀 결과 |
TTL 2873 | 원래 TTL에서 캐시 경과만큼 줄어든 값일 수 있음 |
Query time | 캐시 응답이면 매우 짧고, 재귀 과정이 있었으면 길어짐 |
SERVER | 실제로 질의를 받은 서버 (Ubuntu에서 127.0.0.53이면 로컬 스텁) |
hosts: 순서를 확인하고 설명할 수 있다getent에는 반영되고 dig에는 반영되지 않음을 확인했다dig +trace로 루트 → TLD → 권한 서버 위임 흐름을 확인했다| 도구 | 필터 | 의미 |
|---|---|---|
| Wireshark | dns.flags.response == 0 | 질의만 |
| Wireshark | dns.flags.rcode == 3 | NXDOMAIN 응답 |
| Wireshark | dns.flags.rcode == 2 | SERVFAIL 응답 |
| Wireshark | dns.qry.name contains "example" | 특정 이름 포함 질의 |
| Wireshark | dns.time > 0.5 | 응답까지 0.5초 넘게 걸린 질의 |
| tcpdump | 'port 53 and not host 192.168.10.53' | 지정 리졸버가 아닌 곳으로 가는 DNS |
| 관찰 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
| 단말이 사내 리졸버가 아닌 외부 DNS로 직접 질의 | 정책상 허용된 공용 DNS 사용 | 정책상 내부 리졸버만 허용인데 외부 53 통신 → 설정 변조·우회 가능성 |
/etc/hosts 변경 | 관리자의 테스트·내부 서비스 매핑 | 금융·업데이트·보안 제품 도메인을 임의 IP로 매핑 → 트래픽 가로채기 원리 |
/etc/resolv.conf DNS 서버 변경 | DHCP 갱신, 관리자 변경 | 알 수 없는 외부 IP로 변경, 변경 주체가 일반 프로세스 |
| NXDOMAIN 다수 | 오타, 삭제된 도메인, 검색 도메인 접미사 시도 | 한 단말에서 무작위 형태 이름의 NXDOMAIN 대량 발생 (패턴 분석은 02. 포트 · 프로토콜 분석 시리즈 08편에서 다룸) |
| 도메인 질의 없이 IP로 직접 접속 | 내부 시스템, 하드코딩된 서비스 | 외부 IP로 DNS 없이 바로 연결 (hosts 매핑·하드코딩 여부 확인) |
# 설정 파일 변경 시각 확인
ls -l --time-style=full-iso /etc/hosts /etc/resolv.conf /etc/nsswitch.conf
# 설정 파일 변경 감시 규칙 예시 (auditd 설치·활성 상태 전제)
sudo auditctl -w /etc/hosts -p wa -k dns_config
sudo auditctl -w /etc/resolv.conf -p wa -k dns_config
sudo ausearch -k dns_config
흔적이 남는 곳
| 단계 | 증거 위치 | 한계 |
|---|---|---|
| hosts 파일 응답 | 호스트 파일 자체, 파일 무결성·감사 로그 | 네트워크에는 흔적 없음 |
| 로컬 캐시 응답 | 호스트 캐시(resolvectl statistics) | 개별 질의 로그는 기본적으로 남지 않음 |
| 클라이언트 → 사내 리졸버 | 리졸버 질의 로그(BIND querylog, Windows DNS 로그 등), NSM 도구(Zeek dns.log, Suricata DNS 이벤트) | 리졸버 질의 로그는 부하 때문에 꺼져 있는 경우가 많음 |
| 리졸버 → 외부 | 경계 방화벽·NSM | 출발지가 리졸버 IP라 실제 단말을 알 수 없음 → 리졸버 로그와 시간 기준 연결 필요 |
관제 판단 포인트
한계와 오탐 주의
/etc/hosts나 로컬 캐시에서 답하면 네트워크에 DNS 패킷이 남지 않으므로, "DNS 로그에 없음 = 접속 안 함"이 아닙니다.getent는 nsswitch를 따르고 dig는 DNS에 직접 묻기 때문에, 두 결과 비교로 hosts 파일 영향을 확인할 수 있습니다.