라우팅 실습과 진단
내 컴퓨터의 라우팅 테이블을 읽고 traceroute와 ping, MTR로 실제 경로의 홉과 지연, 손실을 확인합니다. 각 도구가 TTL과 ICMP를 어떻게 쓰는지, 결과에서 무엇까지 말할 수 있는지, 비대칭 경로와 상태 기반 방화벽이 만드는 장애를 함께 정리합니다.
앞의 두 글에서는 라우터가 경로를 고르는 규칙을 보았습니다. 이 글에서는 그 규칙이 실제 네트워크에서 어떻게 드러나는지 내 컴퓨터에서 확인하는 도구를 다룹니다. 네트워크가 느리거나 끊겼을 때 가장 먼저 꺼내는 도구들이지만, 결과를 잘못 읽으면 멀쩡한 라우터를 범인으로 지목하기 쉽습니다. 이 글의 출력은 도구의 형식을 보여 주려고 만든 예이며, 주소는 사설 주소와 문서용 주소(192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24)를 썼습니다.
내 컴퓨터가 고르는 경로부터 확인한다
패킷은 내 컴퓨터를 떠날 때부터 라우팅 표를 따릅니다. Linux에서는 ip route, Windows에서는 route print, macOS에서는 netstat -rn으로 이 표를 볼 수 있습니다. 다음은 Linux에서 본 예입니다.
ip routedefault via 192.168.1.1 dev eth0 proto dhcp metric 100
10.0.0.0/8 via 192.168.1.2 dev eth0 proto static metric 50
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.100 metric 100| 항목 | 뜻 |
|---|---|
default | 0.0.0.0/0, 더 구체적인 경로가 없을 때 쓰는 기본 경로 |
via | 다음 홉의 주소 |
dev | 패킷을 내보낼 인터페이스 |
proto | 경로를 넣은 주체. kernel은 인터페이스에 주소를 붙일 때 커널이, dhcp는 DHCP 클라이언트가, static은 관리자가 넣은 경로 |
scope link | 같은 링크에 바로 닿는 대역이라 게이트웨이를 거치지 않는다는 뜻 |
src | 이 경로로 보낼 때 기본으로 쓸 출발지 주소 |
metric | 같은 프리픽스의 경로가 여럿일 때의 우선순위로, 작을수록 먼저 쓴다 |
첫 줄은 기본 경로로, 다른 줄에 맞지 않는 패킷은 모두 공유기인 192.168.1.1로 보냅니다. 둘째 줄은 10.0.0.0/8로 가는 패킷을 사내망 라우터인 192.168.1.2로 보내라는 정적 경로이고, 셋째 줄은 같은 링크에 있는 192.168.1.0/24에는 게이트웨이 없이 바로 보내라는 뜻입니다. 둘째 줄의 metric이 50으로 첫 줄보다 작다고 해서 먼저 쓰이는 것은 아닙니다. 5장 1편에서 본 것처럼 최장 접두어 일치가 먼저이고, metric은 길이가 같은 프리픽스끼리만 비교합니다.
표를 보고 머릿속으로 따지는 것보다 확실한 방법은 커널에 직접 물어보는 것입니다. ip route get은 그 주소로 패킷을 보낸다면 커널이 고를 경로 하나를 보여 줍니다.
ip route get 10.20.30.40
ip route get 203.0.113.1010.20.30.40 via 192.168.1.2 dev eth0 src 192.168.1.100 uid 1000
cache
203.0.113.10 via 192.168.1.1 dev eth0 src 192.168.1.100 uid 1000
cache10.20.30.40은 10.0.0.0/8에 맞으므로 192.168.1.2로, 203.0.113.10은 기본 경로에만 맞으므로 공유기로 갑니다. VPN을 켜거나 정책 라우팅 규칙이 있는 컴퓨터에서는 표만 보고 짐작한 경로와 실제 경로가 달라지기 쉬우므로, "이 주소로는 어디로 나가는가"를 확인할 때는 이 명령부터 씁니다.
traceroute는 TTL을 하나씩 늘려 간다
4장 1편에서 본 것처럼 라우터는 패킷을 넘길 때마다 IP 헤더의 TTL을 1씩 줄이고, 0이 되면 패킷을 버리면서 보낸 쪽에 ICMP Time Exceeded 메시지를 돌려줍니다. traceroute는 이 동작을 거꾸로 이용합니다. TTL을 1로 둔 probe(경로를 알아보려고 보내는 시험 패킷)를 보내면 첫 번째 라우터가 Time Exceeded를 돌려주고, 그 메시지의 출발지 주소가 곧 첫 번째 라우터의 주소입니다. TTL을 2, 3으로 늘려 가며 같은 일을 반복하면 경로의 라우터를 하나씩 알아낼 수 있습니다.
probe가 목적지에 닿으면 목적지는 Time Exceeded 대신 다른 답을 보냅니다. Linux의 traceroute는 기본으로 아무도 쓰지 않을 법한 UDP 포트(33434부터 probe마다 하나씩 늘림)로 probe를 보내므로 목적지는 Port Unreachable을 돌려주고, ICMP Echo로 보내는 방식이라면 Echo Reply를 돌려줍니다. Windows의 tracert는 ICMP Echo Request를 씁니다. 두 도구 모두 기본으로 홉마다 probe 세 개를 보내고, 30홉까지 시도합니다.
다음 코드는 이 과정을 흉내 냅니다. 경로에는 라우터 네 대가 있고, 셋째 라우터는 정책상 Time Exceeded를 보내지 않도록 설정되어 있습니다.
path = [("192.168.1.1", True), ("10.0.0.1", True), ("172.16.0.1", False), ("198.51.100.1", True)]
destination = "203.0.113.10"
def send_probe(ttl):
for router, answers in path:
ttl -= 1 # 라우터는 넘기기 전에 TTL을 1 줄인다
if ttl == 0:
return router if answers else None # 0이 되면 버리고, 응답하도록 설정된 라우터만 Time Exceeded를 보낸다
return destination # 목적지까지 오면 목적지가 직접 답한다
for ttl in range(1, 31):
reply = send_probe(ttl)
print(f"TTL {ttl}: {reply or '* * *'}")
if reply == destination:
breakTTL 1: 192.168.1.1
TTL 2: 10.0.0.1
TTL 3: * * *
TTL 4: 198.51.100.1
TTL 5: 203.0.113.10TTL 3의 probe는 셋째 라우터에서 버려졌지만 답이 오지 않았고, TTL 4와 5의 probe는 그 라우터를 지나 넷째 라우터와 목적지까지 갔습니다. 답을 하지 않는 것과 패킷을 넘기지 못하는 것은 다른 일이라는 점이 traceroute를 읽는 첫 번째 요령입니다.
실제 명령은 다음과 같습니다. -n은 주소를 이름으로 바꾸는 조회를 하지 않게 해서 결과가 빨리 나오게 합니다.
traceroute -n 203.0.113.10 # Linux, 기본은 UDP probe
traceroute -n -I 203.0.113.10 # ICMP Echo로 보낸다
sudo traceroute -n -T -p 443 203.0.113.10 # TCP SYN을 443번 포트로 보낸다
tracert -d 203.0.113.10 # Windows, ICMP EchoUDP나 ICMP probe를 막는 방화벽이 있으면 traceroute가 중간에서 멈춘 것처럼 보이므로, 웹 서버까지의 경로라면 실제 서비스와 같은 TCP 443번으로 보내 보는 것이 도움이 됩니다. TCP 방식은 원시 소켓(raw socket)으로 SYN을 직접 만들어 보내기 때문에 관리자 권한이 필요합니다. 출력은 다음과 같은 모양입니다.
traceroute to 203.0.113.10 (203.0.113.10), 30 hops max, 60 byte packets
1 192.168.1.1 1.234 ms 0.987 ms 1.102 ms
2 10.0.0.1 5.432 ms 4.987 ms 5.123 ms
3 * * *
4 198.51.100.1 12.345 ms 11.987 ms 12.102 ms
5 203.0.113.10 12.678 ms 12.987 ms 12.234 ms한 줄의 시간 세 개는 그 홉에 보낸 probe 세 개의 왕복 시간이고, *는 정해진 시간 안에 답이 오지 않은 probe입니다.
결과에서 말할 수 있는 것과 없는 것
각 홉의 시간은 그 링크 하나의 지연이 아닙니다. probe가 그 라우터까지 간 시간, 라우터가 Time Exceeded를 만드는 시간, 그 메시지가 돌아오는 시간을 모두 더한 값입니다. 라우터는 지나가는 패킷은 전용 하드웨어로 빠르게 넘기지만, 자기에게 온 probe에 ICMP로 답하는 일은 우선순위가 낮은 CPU에서 처리하는 경우가 많습니다. 그래서 어느 홉 하나만 시간이 크고 그 뒤의 홉들이 다시 작아진다면, 그 라우터가 답을 늦게 만들었을 뿐 패킷은 제대로 넘기고 있을 가능성이 큽니다. 반대로 어느 홉부터 늘어난 시간이 목적지까지 계속 이어진다면 그 구간을 먼저 의심합니다.
* * *도 같은 방식으로 읽습니다. 라우터가 ICMP를 보내지 않도록 설정되었거나, ICMP 오류 메시지를 보내는 속도를 제한했거나, 중간의 방화벽이 응답을 막았거나, 실제로 패킷이 사라졌을 수 있습니다. 뒤의 홉과 목적지가 답한다면 그 홉은 패킷을 넘기고 있다는 뜻이므로, * * * 한 줄만으로 장애라고 판단하지 않습니다.
경로가 하나가 아닐 수도 있습니다. 라우터가 같은 비용의 경로 여러 개에 흐름을 나누어 싣는 ECMP(equal-cost multi-path)를 쓰면, traceroute가 probe마다 바꾸는 포트 번호 때문에 probe들이 서로 다른 경로로 갈 수 있습니다. 한 홉에 주소가 둘 이상 찍히거나, 실제로는 존재하지 않는 경로가 이어 붙은 것처럼 보이는 것이 그 때문입니다. 또 traceroute가 보여 주는 것은 가는 길뿐입니다. 돌아오는 길은 상대의 라우팅이 정하므로, 양방향을 모두 알고 싶다면 반대쪽에서도 traceroute를 해야 합니다.
ping으로 도달성과 지연을 본다
ping은 ICMP Echo Request를 보내고 Echo Reply를 기다리는 가장 단순한 도구입니다. 답이 오면 그 주소까지 갔다가 돌아오는 길이 적어도 ICMP에는 열려 있다는 뜻이고, 함께 찍히는 시간이 왕복 시간(round-trip time, RTT)입니다. Linux의 ping(8)은 다음과 같은 줄을 찍습니다.
64 bytes from 203.0.113.10: icmp_seq=1 ttl=57 time=12.3 ms64 bytes는 기본 데이터 56바이트에 ICMP 헤더 8바이트를 더한 크기이고, icmp_seq는 보낸 요청의 순번입니다. ttl=57은 답장이 도착했을 때의 TTL로, 상대가 64에서 시작했다면 7홉을 거쳐 왔다고 짐작할 수 있습니다. Linux와 macOS는 보통 64, Windows는 128, 네트워크 장비는 255로 시작하는 경우가 많지만 모두 바꿀 수 있는 설정이므로, 이 값으로 상대의 운영체제나 홉 수를 확정하지는 않습니다.
Linux와 Windows는 같은 일을 다른 옵션으로 시킵니다. 특히 -t는 Linux에서는 TTL을 정하는 옵션이고 Windows에서는 멈출 때까지 계속 보내는 옵션이라 헷갈리기 쉽습니다.
| 하고 싶은 일 | Linux(iputils) | Windows |
|---|---|---|
| 보낼 개수 정하기 | -c 5 | -n 5 |
| 멈출 때까지 계속 보내기 | 기본 동작 | -t |
| 데이터 크기 정하기 | -s 1472 | -l 1472 |
| DF 비트 켜기 | -M do | -f |
| TTL 정하기 | -t 5 | -i 5 |
| IPv4로 고정하기 | -4 | -4 |
DF 비트와 크기를 함께 쓰면 경로 MTU를 확인할 수 있습니다. 데이터 1472바이트에 ICMP 헤더 8바이트와 IPv4 헤더 20바이트를 더하면 1500바이트이므로, 다음 명령이 답을 받는다면 그 경로가 1500바이트 IPv4 패킷을 나누지 않고 실어 날랐다는 뜻입니다.
ping -4 -c 3 -M do -s 1472 203.0.113.10 # Linux
ping -4 -n 3 -f -l 1472 203.0.113.10 # Windows중간에 MTU가 더 작은 링크가 있으면 그 앞의 라우터가 패킷을 버리고 Fragmentation Needed 메시지에 자기 링크의 MTU를 담아 돌려줍니다. ping은 이 메시지를 받으면 어느 라우터가 얼마의 MTU를 알려 왔는지 보여 주고, 크기를 줄여 가며 다시 보내면 통과하는 가장 큰 크기를 찾을 수 있습니다. 다만 중간의 방화벽이 이 메시지까지 막으면 아무 답도 오지 않으므로, 답이 없다는 사실만으로 MTU 문제라고 단정할 수는 없습니다.
ping 출력을 코드로 읽을 때
ping 결과를 모니터링 스크립트로 모으다 보면 출력을 직접 해석하게 됩니다. 다음 코드는 iputils ping 출력의 응답 줄과 요약 줄을 읽어 손실률과 빠진 순번, RTT의 평균과 중앙값을 구합니다. 실제 ping을 실행하지 않고, 출력의 형식을 그대로 옮긴 문자열을 씁니다.
import re
from statistics import mean, median
output = """\
64 bytes from 192.0.2.10: icmp_seq=1 ttl=57 time=10.0 ms
64 bytes from 192.0.2.10: icmp_seq=2 ttl=57 time=12.0 ms
64 bytes from 192.0.2.10: icmp_seq=4 ttl=57 time=11.0 ms
64 bytes from 192.0.2.10: icmp_seq=5 ttl=57 time=39.0 ms
5 packets transmitted, 4 received, 20% packet loss, time 4005ms
"""
def summarize(text):
summary = re.search(r"(\d+) packets transmitted, (\d+) received", text)
if summary is None:
raise ValueError("요약 줄을 찾지 못했다") # 모르는 형식이면 0%로 넘기지 않고 멈춘다
sent, received = map(int, summary.groups())
seqs = [int(s) for s in re.findall(r"icmp_seq=(\d+)", text)]
rtts = [float(t) for t in re.findall(r"time=([\d.]+) ms", text)]
missing = sorted(set(range(1, sent + 1)) - set(seqs))
print(f"보냄 {sent}, 받음 {received}, 손실 {(sent - received) / sent:.0%}, 빠진 순번 {missing}")
print(f"평균 {mean(rtts):.1f} ms, 중앙값 {median(rtts):.1f} ms, 최대 {max(rtts):.1f} ms")
summarize(output)
try:
summarize("Packets: Sent = 5, Received = 4, Lost = 1 (20% loss),")
except ValueError as e:
print("Windows 형식:", e)보냄 5, 받음 4, 손실 20%, 빠진 순번 [3]
평균 18.0 ms, 중앙값 11.5 ms, 최대 39.0 ms
Windows 형식: 요약 줄을 찾지 못했다빠진 순번을 보면 다섯 번째가 아니라 세 번째 요청의 답이 오지 않았다는 것을 알 수 있습니다. 평균은 39ms짜리 답 하나에 끌려 18ms가 되었지만 중앙값은 11.5ms로, 대부분의 답은 10ms 안팎이었습니다. 지연을 볼 때 평균만 보지 않고 중앙값이나 최댓값을 함께 보는 이유입니다. 마지막 줄은 Windows의 요약 형식처럼 모르는 출력을 받았을 때 손실을 0%로 적고 넘어가지 않고 오류로 멈추게 한 결과입니다. 형식이 맞지 않아 아무것도 읽지 못했는데 "손실 없음"으로 기록되는 것이 이런 스크립트에서 가장 흔한 실수입니다.
MTR은 홉마다 통계를 쌓는다
MTR은 traceroute처럼 경로의 홉을 찾은 뒤, 각 홉에 ping처럼 probe를 계속 보내 홉마다 손실률과 지연의 통계를 모읍니다. 한 번 찍고 끝나는 traceroute와 달리 몇 분 동안의 변화를 볼 수 있어서, 간헐적인 손실을 조사할 때 많이 씁니다.
mtr -n -r -c 100 203.0.113.10 # 100번 보낸 뒤 보고서를 찍고 끝낸다
mtr -n -r -c 100 -T -P 443 203.0.113.10 # ICMP 대신 TCP SYN을 443번 포트로 보낸다
mtr -n -j -c 50 203.0.113.10 # 결과를 JSON으로 찍는다-r은 보고서 모드로, -c로 정한 횟수만큼 보낸 뒤 통계를 찍고 끝냅니다. 기본으로 1초에 한 번씩 보내므로 -c 100이면 100초쯤 걸립니다. 보고서는 다음과 같은 모양입니다.
HOST: client Loss% Snt Last Avg Best Wrst StDev
1.|-- 192.168.1.1 0.0% 100 1.1 1.2 0.9 2.3 0.2
2.|-- 10.0.0.1 40.0% 100 5.3 5.4 4.9 8.8 0.5
3.|-- 198.51.100.1 0.0% 100 11.9 12.1 11.6 14.2 0.4
4.|-- 203.0.113.10 12.0% 100 12.4 12.6 12.0 15.1 0.5여기서 Loss%는 그 홉을 겨냥해 보낸 probe 가운데 답이 오지 않은 비율입니다. 2번 홉의 40%가 3번 홉에서 0%로 돌아왔으므로, 2번 라우터는 자기에게 온 probe에 답하는 속도를 제한하고 있을 뿐 패킷은 잘 넘기고 있다고 읽는 것이 자연스럽습니다. 목적지의 12%는 그와 따로 조사해야 하는 값이며, 40%와 12%를 더해 52%가 사라졌다고 말할 수는 없습니다. 각 행은 서로 다른 probe의 통계이기 때문입니다.
MTR INTERPRETATION · PROBE RESPONSE / DESTINATION
중간 홉의 누락 응답률을 링크별 손실 합계로 읽지 않는다
서로 다른 설명용 데이터 두 가지입니다. Loss는 각 홉을 겨냥한 probe의 응답 통계로, 중간 홉 40%가 목적지까지 그대로 누적되었다는 뜻이 아닙니다. 목적지 손실과 실제 서비스 기준선을 함께 조사합니다.
| 예시 | 누락 응답 패턴 | 판단 범위 |
|---|---|---|
| 중간 수치와 목적지가 다름 | 중간 홉 40% → 다음 홉 0% → 목적지 12% | 40%가 그대로 누적된 것은 아님. 목적지 12%는 별도로 조사하며 홉 2의 장애로 단정하지 않음 |
| 뒤쪽에도 비슷한 손실 | transit 15% → 목적지 15% | 경로 품질 문제를 조사할 단서. ICMP 제한·반환 경로·다른 probe 흐름도 대조해야 위치를 좁힐 수 있음 |
- 중간 수치와 목적지가 다름
- 누락 응답 패턴: 중간 홉 40% → 다음 홉 0% → 목적지 12%판단 범위: 40%가 그대로 누적된 것은 아님. 목적지 12%는 별도로 조사하며 홉 2의 장애로 단정하지 않음
- 뒤쪽에도 비슷한 손실
- 누락 응답 패턴: transit 15% → 목적지 15%판단 범위: 경로 품질 문제를 조사할 단서. ICMP 제한·반환 경로·다른 probe 흐름도 대조해야 위치를 좁힐 수 있음
손실 패턴은 조사 단서이고 장애 위치의 확정은 아니다
목적지 응답·표본 수·시간대를 대조하고 ICMP 제한·반환 경로·probe 방식 차이를 확인합니다.
15%가 두 위치에서 같아도 특정 링크의 장애를 곧바로 확정하지 않습니다. 두 예시를 하나의 연속된 경로로 합치거나 모든 환경의 공통 임계값으로 쓰지 않습니다.
손실이 어느 홉에서 시작되어 목적지까지 비슷하게 이어진다면 경로의 품질 문제를 의심할 단서가 됩니다. 그래도 그 홉의 링크가 문제라고 바로 확정하지는 않습니다. ICMP 응답 제한, 돌아오는 길의 차이, probe 방식에 따라 달라지는 경로를 함께 따져야 하고, 평소의 값과 비교해 실제로 나빠졌는지, 여러 번 측정해도 같은지, 서비스의 응답 지연과 시간이 맞는지를 확인해야 위치를 좁힐 수 있습니다.
비대칭 경로와 상태 기반 방화벽
A에서 B로 가는 길과 B에서 A로 돌아오는 길이 다른 것을 비대칭 라우팅(asymmetric routing)이라고 합니다. 인터넷에서는 흔한 일입니다. 각 AS가 자기 정책으로 경로를 고르므로, 가는 패킷과 돌아오는 패킷이 서로 다른 통신사를 거치는 일이 자연스럽게 생깁니다. 비대칭 자체는 문제가 아니지만, 길 위에 상태를 기억하는 장비가 있으면 이야기가 달라집니다.
상태 기반 방화벽(stateful firewall)은 안에서 밖으로 나간 요청을 세션으로 기억해 두고, 그 세션에 속한 응답만 들여보냅니다. 요청이 방화벽 A를 지나고 응답이 방화벽 B로 돌아오는데 두 방화벽이 세션 정보를 나누지 않는다면, B에게 그 응답은 요청한 적 없는 패킷으로 보입니다.
반환 경로의 방화벽에 세션 상태가 없는 경우
클라이언트의 요청은 방화벽 A를 지납니다. B가 기존 세션을 요구하는 정책을 적용합니다. 각 단계의 값과 전환 이유를 비교합니다.
ASYMMETRIC ROUTING · DIFFERENT PATH / MISSING STATE
다른 방화벽으로 돌아올 때 상태와 정책을 함께 확인한다
요청은 A, 응답은 B를 지나는 예시입니다. A/B가 세션 상태를 공유하지 않고 B가 기존 세션을 요구하면 응답이 차단될 수 있습니다. 비대칭 경로만으로 장애라고 단정하지 않습니다.
실선: 요청, 점선: 응답의 경로를 비교합니다. 아래 점선은 반환 경로이며 B의 정책에 따른 최종 허용을 보장하지 않습니다. 상태 공유 여부·양방향 경로·필터 정책을 대조합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 요청 경로부터 B 정책 판단까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 요청 경로 클라이언트의 요청은 방화벽 A를 지납니다. |
|
|
| 응답 경로 서버의 응답은 방화벽 B로 돌아옵니다. |
|
|
| B 정책 판단 B가 기존 세션을 요구하는 정책을 적용합니다. |
|
|
반환 경로의 방화벽에 세션 상태가 없는 경우
클라이언트의 요청은 방화벽 A를 지납니다. B가 기존 세션을 요구하는 정책을 적용합니다. 각 단계의 값과 전환 이유를 비교합니다.
ASYMMETRIC ROUTING · DIFFERENT PATH / MISSING STATE
다른 방화벽으로 돌아올 때 상태와 정책을 함께 확인한다
요청은 A, 응답은 B를 지나는 예시입니다. A/B가 세션 상태를 공유하지 않고 B가 기존 세션을 요구하면 응답이 차단될 수 있습니다. 비대칭 경로만으로 장애라고 단정하지 않습니다.
실선: 요청, 점선: 응답의 경로를 비교합니다. 아래 점선은 반환 경로이며 B의 정책에 따른 최종 허용을 보장하지 않습니다. 상태 공유 여부·양방향 경로·필터 정책을 대조합니다.
이 예의 조건
- 본문과 전체 도표의 예를 따라 요청 경로부터 B 정책 판단까지 진행합니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 요청 경로 클라이언트의 요청은 방화벽 A를 지납니다. |
|
|
| 응답 경로 서버의 응답은 방화벽 B로 돌아옵니다. |
|
|
| B 정책 판단 B가 기존 세션을 요구하는 정책을 적용합니다. |
|
|
방화벽을 둘로 늘리면서 세션 동기화를 켜지 않았거나, 라우팅을 바꾼 뒤 한쪽 방향만 다른 장비로 가게 되었을 때 이런 일이 생깁니다. ping은 되는데 TCP 연결만 맺어지지 않거나, 연결이 맺어진 뒤 응답만 사라지는 증상으로 나타나는 경우가 많습니다. 양쪽에서 traceroute로 두 방향의 경로를 비교하고, 방화벽의 세션 표와 차단 기록을 확인한 뒤, 세션을 동기화하거나 두 방향이 같은 장비를 지나도록 경로를 맞춥니다. 경로가 비대칭이라는 사실만으로 장애의 원인이라고 단정하지 않는 것도 중요합니다.
TTL과 ICMP 메시지 한눈에 보기
이 글의 도구들은 모두 ICMP 메시지에 기대어 동작합니다. IPv4의 ICMP는 RFC 792에, IPv6의 ICMPv6는 RFC 4443에 정의되어 있습니다.
| 메시지 | ICMPv4 | ICMPv6 | 쓰는 곳 |
|---|---|---|---|
| Echo Request, Echo Reply | 타입 8, 0 | 타입 128, 129 | ping |
| Time Exceeded(TTL 또는 홉 한도 초과) | 타입 11, 코드 0 | 타입 3, 코드 0 | traceroute의 중간 홉 |
| Port Unreachable | 타입 3, 코드 3 | 타입 1, 코드 4 | UDP traceroute의 목적지 |
| Fragmentation Needed, Packet Too Big | 타입 3, 코드 4 | 타입 2 | 경로 MTU 탐색 |
보안을 이유로 ICMP를 모두 막는 경우가 있는데, 그러면 ping과 traceroute가 안 되는 것에서 끝나지 않습니다. 경로 MTU 탐색(RFC 1191)은 Fragmentation Needed 메시지를 받아야 동작하므로, 이 메시지가 막히면 작은 패킷은 오가는데 큰 패킷만 소리 없이 사라지는 장애가 생깁니다. 접속과 짧은 요청은 되는데 큰 파일을 받거나 응답이 큰 페이지를 열 때만 멈춘다면 이 경우를 의심합니다. IPv6에서는 라우터가 패킷을 나누지 않으므로 Packet Too Big을 막으면 문제가 더 커집니다.
더 깊이: ICMP가 막힌 경로에서 크기를 맞추는 두 가지 방법
첫째는 경로 중간의 장비가 TCP 연결을 맺는 순간에 MSS를 낮춰 주는 것입니다. Linux의 iptables에서는 TCPMSS 대상이 이 일을 하는데, iptables-extensions(8)은 이것이 Fragmentation Needed나 Packet Too Big을 막아 둔 망에서 작은 전송은 되는데 큰 전송만 멈추는 문제를 피하려는 기능이라고 설명합니다. --clamp-mss-to-pmtu를 주면 SYN 패킷의 MSS 값을 경로 MTU에서 IPv4는 40바이트, IPv6는 60바이트를 뺀 값으로 낮추므로, 양 끝은 처음부터 그 크기를 넘지 않게 보냅니다. 다만 이 방법은 MSS를 협상하는 TCP에만 통합니다.
둘째는 ICMP에 기대지 않고 보내는 쪽이 스스로 크기를 찾는 것입니다. RFC 4821의 패킷화 계층 경로 MTU 발견(packetization layer path MTU discovery, PLPMTUD)은 전송 계층이 점점 큰 패킷을 보내 보고, 그 패킷이 확인 응답을 받는지로 경로의 MTU를 알아냅니다. Linux TCP에서는 tcp_mtu_probing 설정이 이 기능을 정하며, 1이면 ICMP 블랙홀을 감지했을 때만, 2면 언제나 이 방식을 씁니다. UDP처럼 확인 응답이 없는 데이터그램 위의 프로토콜을 위해서는 RFC 8899가 같은 생각을 따로 정리해 두었습니다.
증상별로 먼저 볼 것
| 증상 | 먼저 확인할 것 |
|---|---|
| 아무 곳에도 연결되지 않음 | ip route에 기본 경로가 있는지, 게이트웨이에 ping이 되는지 |
| 특정 대역만 연결되지 않음 | ip route get으로 커널이 고르는 경로, VPN이나 정적 경로가 더 구체적인 경로를 넣었는지 |
| 간헐적으로 손실이 생김 | mtr -r -c 200의 목적지 손실, 시간대를 바꿔도 재현되는지, 링크와 장비의 오류 카운터 |
| 지연이 큼 | 평소의 값과 비교해 얼마나 늘었는지, 늘어나기 시작한 홉 뒤로 계속 이어지는지 |
| 한쪽 방향만 막힘 | 양쪽에서 본 traceroute, 방화벽의 세션 상태와 돌아오는 경로 |
| 작은 요청은 되는데 큰 전송만 멈춤 | DF를 켠 ping으로 크기를 줄여 가며 찾은 경로 MTU, Fragmentation Needed가 막혔는지 |
정리
ip route로 라우팅 표를,ip route get으로 특정 주소에 대해 커널이 실제로 고르는 경로를 확인합니다.- traceroute는 TTL을 1부터 늘려 가며 각 라우터가 돌려주는 Time Exceeded로 경로를 알아내며, 각 홉의 시간에는 돌아오는 길과 응답을 만드는 시간까지 섞여 있습니다.
* * *나 한 홉만 큰 지연은 그 라우터가 답을 하지 않거나 늦게 한다는 뜻일 수 있으므로, 뒤의 홉과 목적지가 어떤지 함께 봅니다.- ping은 도달성과 RTT를 보고, DF 비트와 크기를 함께 쓰면 경로 MTU를 확인할 수 있습니다. 출력을 코드로 읽을 때는 모르는 형식을 손실 0%로 처리하지 않게 합니다.
- MTR의
Loss%는 홉마다 따로 모은 응답 통계이므로 더하지 않으며, 목적지까지 이어지는 손실인지를 봅니다. - 비대칭 경로 자체는 정상이지만, 세션을 나누지 않는 상태 기반 방화벽이 양방향에 따로 있으면 응답이 막힐 수 있습니다.
연습 문제
1. 답하지 않는 홉
traceroute에서 TTL 1, 2의 홉은 답했고 TTL 3은 * * *였으며, TTL 4의 홉과 목적지는 답했습니다. 이 결과로 셋째 라우터가 패킷을 넘기지 못한다고 말할 수 있나요? 또 MTR에서 중간 홉의 Loss가 40%, 목적지의 Loss가 12%였다면 종단 간 손실이 52%라고 적어도 되나요?
해설 보기
말할 수 없습니다. TTL 4의 probe가 셋째 라우터를 지나 넷째 라우터까지 갔으므로 셋째 라우터는 패킷을 넘기고 있고, TTL 3의 probe에 대한 Time Exceeded만 오지 않은 것입니다. ICMP를 보내지 않도록 한 설정, 응답 속도 제한, 돌아오는 길의 필터 같은 원인을 생각할 수 있습니다.
MTR의 두 값도 더하지 않습니다. 중간 홉의 40%는 그 라우터를 겨냥한 probe의 응답 통계이고, 목적지의 12%는 목적지를 겨냥한 다른 probe의 통계입니다. 종단 간 손실에 가까운 값은 목적지 행의 12%이며, 이것도 목적지가 ICMP 응답을 제한한 결과일 수 있으므로 서비스의 실제 증상과 함께 확인해야 합니다.
2. PPPoE 회선의 MTU
경로 중간에 MTU가 1492바이트인 PPPoE 링크가 있습니다. Linux에서 ping -4 -M do -s 1472로 보내면 어떻게 되나요? 이 경로를 통과하는 가장 큰 -s 값은 얼마인가요?
해설 보기
데이터 1472바이트에 ICMP 헤더 8바이트와 IPv4 헤더 20바이트를 더하면 1500바이트인데, DF 비트가 켜져 있어 1492바이트 링크 앞의 라우터는 이 패킷을 나눌 수 없습니다. 그 라우터는 패킷을 버리고 MTU 1492를 담은 Fragmentation Needed를 돌려주므로, 이 메시지가 막히지 않았다면 ping이 그 사실을 보여 주고 답장은 오지 않습니다. 통과하는 가장 큰 값은 1492 − 20 − 8 = 1464입니다. Linux는 이렇게 알게 된 경로 MTU를 기억해 두므로, 그 뒤에 같은 목적지로 1472바이트를 다시 보내면 로컬에서 바로 크기 오류가 날 수도 있습니다.
3. 답이 하나도 없을 때
본문의 summarize에 3 packets transmitted, 0 received, 100% packet loss, time 2047ms라는 요약 줄만 있는 출력을 넣으면 어떻게 되나요? 어떻게 고치면 좋을까요?
해설 보기
요약 줄은 찾으므로 첫 번째 print는 보냄 3, 받음 0, 손실 100%, 빠진 순번 [1, 2, 3]을 찍습니다. 하지만 응답 줄이 없어 rtts가 빈 리스트이므로, 두 번째 print에서 mean(rtts)가 statistics.StatisticsError를 일으키고 프로그램이 멈춥니다. 손실이 100%인 상황은 모니터링에서 가장 알아야 할 순간인데, 바로 그때 스크립트가 죽는 셈입니다. 첫 번째 print 뒤에 if not rtts:로 응답이 없는 경우를 따로 처리해 "응답 없음"을 기록하고 함수를 끝내면 됩니다.
다음 장 TCP의 핵심 특성에서는 네트워크 계층 위에서 프로세스끼리 바이트를 순서대로, 빠짐없이 주고받게 해 주는 TCP를 살펴봅니다.