LabHub
배우기 러닝패스 코스

Network Fundamentals — Hands-on in a Linux VM

Paths and Names — Routing Tables and the Layers of DNS

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

라우팅은 "이 목적지는 어느 인터페이스로, 누구를 거쳐" 를 최장 접두사 일치로 고르는 것이고, 리눅스는 ip_forward 를 켜야 남의 패킷을 넘긴다. 이름 해석은 /etc/hosts → 스텁 → 상류 서버의 여러 층이고, dig 는 그중 마지막 층만 본다.

Layer map: 한 홉이 한 대역을 모르는 것 · 돌아오는 길. · 포트 53 충돌. · 검색 도메인이 붙는다.

왜 이게 필요했나

서브넷 밖으로 나가는 패킷은 게이트웨이에게 맡겨진다. 그 게이트웨이는 다시 자기 라우팅 테이블을 보고 다음 게이트웨이를 고른다. 이 연쇄가 인터넷이다. 각 홉이 아는 것은 "이 대역은 저쪽" 뿐이고, 전체 경로를 아는 장치는 없다. 그래서 라우팅 사고는 한 홉이 한 대역을 모르는 것에서 난다 — 가는 길은 있는데 돌아오는 길이 없는 경우가 특히 흔하고, 증상은 그냥 timeout 이라 구별되지 않는다.

이름도 마찬가지로 층이 있다. 옛날엔 /etc/resolv.conf 에 서버 주소가 있고 그것으로 끝이었다. 지금 우분투는 systemd-resolved127.0.0.53 에서 스텁으로 듣고, 링크마다 다른 상류 서버로 넘기며, ~domain 라우팅 규칙으로 특정 도메인만 특정 서버에 보낼 수 있다. VPN 이 회사 도메인만 회사 DNS 로 보내는 것이 이 장치다. 편리하지만, 어느 층이 답했는지 모르면 "dig 는 되는데 curl 은 안 된다" 를 설명할 수 없다.

어떻게 동작하나

라우팅 테이블에 default via 10.20.1.110.99.0.0/24 via 10.20.2.10 이 있으면 10.99.0.5 는 뒤의 줄로 간다. 더 긴 프리픽스가 이기고, 적힌 순서는 상관없다. ip route get 10.99.0.5 는 이 판정을 커널에게 직접 물어 답을 받는다 — 사람이 테이블을 읽고 추론하는 것보다 정확하다.

목적지 10.99.0.5 에 대해 default 와 10.0.0.0/8 과 10.99.0.0/24 세 줄이 모두 일치하지만, 맞는 비트가 각각 0 과 8 과 24 라서 맨 아래 적힌 24비트 줄이 이긴다. 적힌 순서는 상관이 없다

리눅스는 기본으로 라우터가 아니다. 자기 주소가 아닌 패킷은 버린다. net.ipv4.ip_forward=1 이 그것을 바꾸고, 네임스페이스마다 값이 따로다. 도커 호스트·쿠버네티스 노드·VPN 서버가 전부 이 값이 1 이다.

이름 해석은 애플리케이션이 libc 의 getaddrinfo 를 부르면서 시작한다. /etc/nsswitch.confhosts: files dns 순서대로 /etc/hosts 를 먼저 보고, 없으면 resolv.conf 의 스텁에 묻고, 스텁은 링크 설정을 보고 상류로 넘긴다. dig 는 이 층을 다 건너뛰고 서버에 직접 묻는다. 그래서 서버가 무엇을 답하는지는 dig, 애플리케이션이 무엇을 보는지는 getent hosts 로 본다.

현장에서 만나는 모습

돌아오는 길. 새 대역을 붙이고 라우터에 경로를 넣었다. ping 이 안 된다. 상대 쪽 라우터가 새 대역으로 돌아오는 경로를 모른다. 요청은 도착했고 응답이 길을 잃었다. 양쪽에서 ip route get 을 해 보면 한쪽만 default 로 엉뚱한 곳을 가리킨다.

포트 53 충돌. dnsmasq 를 깔았는데 안 뜬다. ss -ulpn 'sport = :53' 을 보면 systemd-resolve 가 127.0.0.53 을 잡고 있다. 해결은 dnsmasq 에 listen-addressbind-interfaces 로 자기 주소만 잡게 하는 것이다.

이름 해석이 느리거나 이상할 때

라우팅이 맞는데도 느린 경우, 원인이 이름 해석 쪽인 일이 자주 있다. 층이 여럿이라 어디서 시간이 가는지 보이지 않기 때문이다.

검색 도메인이 붙는다. resolv.confsearch 목록에 도메인이 여럿 적혀 있으면, 짧은 이름을 물을 때 그 목록을 하나씩 붙여 가며 차례로 시도한다. 없는 이름일수록 시도 횟수가 많아지고, 서버가 늦게 답하면 그만큼 곱해진다. 쿠버네티스 파드 안에서 외부 도메인 조회가 느린 고전적인 원인이 이것이고, 그래서 마지막에 점을 붙여 절대 이름으로 묻는 것이 해법이 된다.

IPv6 를 함께 묻는다. getaddrinfo 는 A 와 AAAA 를 둘 다 물어보는데, 한쪽 질의가 유실되면 타임아웃까지 기다렸다가 재시도한다. 그래서 "가끔 5초쯤 멈춘다" 는 증상이 나온다. 5초는 대개 리졸버의 기본 타임아웃 값이라, 딱 떨어지는 지연은 타임아웃을 의심하라는 신호로 읽으면 된다.

캐시가 낡았다. 주소를 바꿨는데 옛 값이 계속 나오면 어느 층의 캐시인지 가려야 한다. 애플리케이션 자체가 결과를 들고 있을 수도 있고(자바 런타임이 대표적으로 오래 캐시한다), 스텁 리졸버가 들고 있을 수도 있고, 상류 서버가 TTL 만큼 들고 있을 수도 있다. TTL 을 미리 줄여 두고 나서 주소를 바꾸는 것이 정석이고, 바꾸는 날 TTL 을 줄이면 이미 늦었다.

진단 순서는 앞의 원칙과 같다. getent hosts 로 애플리케이션이 실제로 보는 답을 먼저 확인하고, 그것이 이상하면 /etc/hosts 와 스텁 설정을 보고, 서버 자체를 의심할 때만 dig 로 직접 묻는다. dig 부터 치면 층을 건너뛰므로, 애플리케이션이 겪는 문제를 재현하지 못한 채 "서버는 정상" 이라는 결론만 얻게 된다.

다음 실습에서 할 것

네임스페이스 셋으로 호스트–라우터–호스트를 세워 기본 경로·ip_forward·정적 경로를 넣고 traceroute 로 홉을 센다. 그다음 dnsmasq 로 내 DNS 서버를 세우고 resolved 의 라우팅 도메인에 물려, /etc/hosts 가 DNS 를 이기는 것을 getentdig 로 나란히 확인한다.