TTL 到底是怎么变成“traceroute 的每一跳”的?为什么有的跳显示 * * *,同一跳还会出现几个不同的地址?这篇在真实公网路径上抓包,逐个看探测包、路由器回的 ICMP,再自己用 IP_TTL 写一个 traceroute,最后用收到的包的 TTL 估算跳数并和实测对比。
net.inet.ip.ttl: 64 TCP 套接字 getsockopt(IP_TTL) 默认 = 64;setsockopt 设成 7 以后读回 = 7 UDP 套接字 getsockopt(IP_TTL) 默认 = 64;setsockopt 设成 7 以后读回 = 7 给本机(127.0.0.1)发 TTL=1 的 UDP 包:对方收到 b'ttl1';抓到的包 TTL = 1——不经过路由器,TTL 不会被减 ping -m 1 到公网地址:第 1 跳的路由器回了 “Time to live exceeded”(它是本机局域网网关) ping -m 2 到公网地址:第 2 跳的路由器回了 “Time to live exceeded”(它是私有地址) ping -m 3 到公网地址:第 3 跳的路由器回了 “Time to live exceeded”(它是私有地址) ping -m 1 到默认网关(它就是目的地,不需要再转发):收到应答
TTL 是 IP 头里 1 字节的生存时间,每过一个路由器减 1,到 0 就丢弃并回 ICMP Time Exceeded。系统默认 64,套接字上可用 IP_TTL 读写;发给本机不经路由器,所以不会被减。
第 1 跳 1/3 个应答 来自 1 个不同地址 RTT 3.2 ms (本机局域网网关) 第 2 跳 3/3 个应答 来自 1 个不同地址 RTT 4.3 / 2.7 / 3.0 ms (私有地址) 第 3 跳 3/3 个应答 来自 1 个不同地址 RTT 3.1 / 2.8 / 3.2 ms (私有地址) 第 4 跳 3/3 个应答 来自 1 个不同地址 RTT 4.5 / 6.8 / 12.0 ms (公网地址) 第 5 跳 0/3 个应答 (* * *) 第 6 跳 2/3 个应答 来自 2 个不同地址 RTT 16.6 / 7.6 ms (公网地址) 第 7 跳 1/3 个应答 来自 1 个不同地址 RTT 237.2 ms (公网地址) 第 8 跳 3/3 个应答 来自 3 个不同地址 RTT 39.2 / 62.4 / 39.2 ms (公网地址) 第 9 跳 2/3 个应答 来自 2 个不同地址 RTT 50.0 / 48.2 ms (公网地址) 第 10 跳 3/3 个应答 来自 3 个不同地址 RTT 53.0 / 59.5 / 55.5 ms (公网地址) 第 11 跳 3/3 个应答 来自 1 个不同地址 RTT 49.8 / 63.0 / 53.8 ms (公网地址) 第 12 跳 3/3 个应答 来自 3 个不同地址 RTT 54.9 / 55.9 / 50.9 ms (公网地址) 第 13 跳 1/3 个应答 来自 1 个不同地址 RTT 182.9 ms (公网地址) 第 14 跳 0/3 个应答 (* * *) 第 15 跳 3/3 个应答 来自 2 个不同地址 RTT 194.9 / 236.1 / 196.5 ms (公网地址) 目的地在第 15 跳应答;全部无应答的跳:[5, 14](共 2 个);同一跳的 3 个探测来自不止一个地址的跳有 6 个([6, 8, 9, 10, 12, 15]);最大 RTT 237.2 ms
每跳通常发 3 个探测,看到的是该跳的地址和 RTT。实测 15 跳里有 2 跳全无应答、6 跳的探测来自多个地址(路径上有多条并行链路),RTT 随跳数增大。
抓到 16 个 UDP 探测包、11 个 ICMP 回复(类型 11 = Time Exceeded,类型 3 = Destination Unreachable)
序号 发出的 TTL 目的端口 IP 总长 UDP 长度 → 回复 ICMP 类型/代码 引用原包里的 TTL 引用的目的端口 回复包自己的 TTL RTT
1 1 33435 40 20 11/0 1 33435 64 2.6 ms
2 2 33436 40 20 11/0 1 33436 63 2.7 ms
3 3 33437 40 20 11/0 1 33437 62 2.7 ms
4 4 33438 40 20 11/0 1 33438 252 6.0 ms
5 5 33439 40 20 无回复
6 6 33440 40 20 无回复
7 7 33441 40 20 无回复
8 8 33442 40 20 11/0 1 33442 248 37.7 ms
9 9 33443 40 20 无回复
10 10 33444 40 20 11/0 1 33444 245 54.6 ms
11 11 33445 40 20 11/0 1 33445 244 54.6 ms
12 12 33446 40 20 11/0 1 33446 244 56.7 ms
13 13 33447 40 20 无回复
14 14 33448 40 20 11/0 1 33448 239 243.4 ms
15 15 33449 40 20 11/0 1 33449 239 242.2 ms
16 16 33450 40 20 3/3 1 33450 48 199.4 ms
目的端口从 33435 开始,每个探测加 1(到 33450);源端口都是同一个:True;所有探测 IP 总长都是 40 字节(UDP 长度 20,载荷 12 字节)
Time Exceeded 10 个:引用原包里的 TTL 全是 [1](被引用的是路由器丢弃它时手里的那份包头);Destination Unreachable 1 个:代码 [3](3 = 端口不可达),来自目的地本身:True
回复包自己的 TTL 取值: [48, 62, 63, 64, 239, 244, 245, 248, 252]——前三跳依次是 64、63、62(同一类设备,每多一跳回程少 1),后面几跳是 252、248、246……(接近 255,是另一类设备)探测是 UDP 包,TTL 递增,目的端口从 33435 起逐个加 1;中间路由器回 ICMP 11/0 并引用原包头(其中的端口用来对应探测),目的地因为没人监听该端口回 3/3 端口不可达,traceroute 就知道到了。
目的端口从 33435 起、间隔 40 ms 发 TTL=1~20 共 20 个探测(抓包确认发出 20 个):收到 ICMP 回复 15 个;其中来自目的地本身的有 6 个 目的端口从 33435 起、间隔 250 ms 发 TTL=1~20 共 20 个探测(抓包确认发出 20 个):收到 ICMP 回复 15 个;其中来自目的地本身的有 5 个 目的端口从 33435 起、间隔 1000 ms 发 TTL=1~20 共 20 个探测(抓包确认发出 20 个):收到 ICMP 回复 18 个;其中来自目的地本身的有 6 个 目的端口从 34500 起、间隔 250 ms 发 TTL=1~20 共 20 个探测(抓包确认发出 20 个):收到 ICMP 回复 6 个;其中来自目的地本身的有 0 个 取回复最多的一轮(端口 33435 起、间隔 1000 ms)和系统 traceroute(-q 1)逐跳对比:两边都有应答的跳里地址相同 8 个、不同 4 个;只有我的有应答 6 个、只有系统的有应答 1 个 那一轮里无应答的中间跳(取前 4 个):[6, 7]。对每个再间隔 1 s 发 8 个探测:TTL=6 收到 3/8;TTL=7 收到 0/8——“*” 只表示这次没收到回复,不一定是路由器不存在;不回的原因这里没有验证
设 IP_TTL 发 UDP 探测就能做 traceroute,回复要读 ICMP(没有原始套接字权限时从抓包里读)。探测间隔和目的端口都影响收到的回复数,无应答的跳多发几次有时能收到。
试了 5 个公网目的地,TCP 能连通 4 个
目的地 SYN-ACK 的 TTL 推测初始值 按 TTL 估的跳数(初始值−TTL+1) traceroute 实测到达的跳数
目的地1 48 64 17 15
目的地2 51 64 14 14
目的地3 51 64 14 13
目的地4 51 64 14 未到达
估计与实测相同的有 1 / 4 个;差值(估计−实测):[2, 0, 1, None]
给目的地 1 的 TCP 连接设 IP_TTL=n 再 connect(超时 1.2 s):n=15 是第一次成功的;n 更小时都失败,错误码分布 No route to host(65): n=[1, 2, 3];Resource temporarily unavailable(35): n=[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];traceroute 说该目的地在第 15 跳
给目的地 2 的 TCP 连接设 IP_TTL=n 再 connect(超时 1.2 s):n=14 是第一次成功的;n 更小时都失败,错误码分布 No route to host(65): n=[1, 2, 3, 10];Resource temporarily unavailable(35): n=[4, 5, 6, 7, 8, 9, 11, 12, 13];traceroute 说该目的地在第 14 跳收到的包的 TTL 向上取到常见初始值,初始值 − TTL + 1 约等于跳数,实测与 traceroute 差 0~2;把 TCP 连接的 IP_TTL 从 1 往上试,第一次能连通的值与 traceroute 到达的跳数吻合。