每一次网络排障都以同样的方式开场:你敲下 traceroute,得到一份整整齐齐的编号跳数列表,
然后开始讲故事——“它经过法兰克福,然后某个 CDN 边缘,然后就没然后了”。这事我干了很多年,
得承认:那份列表离地图还远,更接近一则传闻。Traceroute 会撒谎,温和而频繁。它撒谎,是因为
ICMP 限速,因为按流做的负载均衡让每次探测走了不同的路径,因为 MPLS 把要紧的跳数
藏了起来,因为非对称路由意味着回程压根不是你画的那一条,还因为大量路由器干脆不回应。
mtr 并不能靠魔法变得更诚实——它只是给了你 traceroute 从来给不出的那一样东西:时间。
下面讲:这工具怎么工作、它是怎么骗你的、如何探测一条可信的路径、如何读延迟而不至于慌张, 以及一例看着坏了、其实只是被限速的 traceroute。一句话版本:traceroute 是证据,不是地图—— 它是某一刻、某一条流上的一个采样。
Traceroute 为何撒谎:探测包不等于路径
机制懒得很漂亮。traceroute 把第一个探测包的 TTL 设为 1;第一台路由器把它减到 0、丢包,
并回一条 ICMP Time Exceeded。那条 ICMP 的源地址,就是这台路由器朝你这侧的地址——第 1 跳。
再把 TTL 设成 2,重复。你就一步步走完了路径。注意你实际测到的是什么:不是你的流量所走的
路径,而是一串手工构造的探测包所走的路径,再加上每台路由器愿不愿意回应。下面每一种失败
模式,攻击的都是这两个假设之一。
ICMP TTL 超时限速:装聋作哑的路由器
路由器长期泡在扫描带来的 TTL 超时流量里,所以几乎每台生产设备都会对 ICMP Time Exceeded
的生成做限速——常见是每秒一条,其余被 policer 丢掉。而 traceroute 每跳会连发三个探测包,
两三个就这么被吞了。症状是路径中间冒出一段 * * *:
$ traceroute -n 203.0.113.10
1 192.168.1.1 0.412 ms
2 10.0.0.1 1.203 ms
3 * * *
4 * * *
5 198.51.100.1 12.884 ms
6 203.0.113.10 13.010 ms
偷懒的读法是“第 3 跳有个黑洞”;正确的读法是“第 3、4 跳拒绝报上名字”——包其实过去了, 毕竟第 5 跳紧跟着它们。不发 ICMP 错误的路由器照样转发数据。Linux 上你能直接看到这条策略:
sysctl net.ipv4.icmp_ratelimit # 1000 = 每 1000 ms 一个包,即每秒 1 个
sysctl net.ipv4.icmp_ratemask # Time Exceeded(type 11)就在这个掩码里
按流负载均衡:一次 traceroute,多条路径
等价多路径(ECMP)转发的是流而不是包,它把五元组做哈希:源/目的 IP、源/目的端口、协议。
traceroute 故意变动了其中两项——每个探测包换一个 UDP 目的端口(默认从 33434 起),或者换一个
ICMP id。哈希一变,链路就变,下一跳也变。于是在一条做负载均衡的路径上,你拿到的是一个网格
而不是一条线:第 4 跳在探测之间于 10.0.4.1 和 10.0.4.2 之间来回跳。把它读成“抖动”或“环路”,
通常都不对——你只是同时在采样好几条并行路径,却把它们当成一条打印了出来。
MPLS、NAT,以及干脆不回应的路由器
MPLS 在 ISP 内部用标签交换路径取代逐跳 IP 转发。做标签转发的路由器,不必以产生 ICMP 错误
的方式去递减 IP TTL,于是整段 ISP 核心就坍缩成两台边缘路由器之间的一行 * * *。你的 trace
说“一跳 3 ms,然后瞬间 40 ms”,你便脑补出一桩 37 ms 的悬案。里面明明有九台路由器,只是被
设计成隐形。
NAT 则从另一头把它弄坏。穿越 NAT 的探测包,回来的 ICMP Time Exceeded 里引用的包已经被
改写;有些 NAT 宁可直接丢掉,也不愿把它翻译回来,于是某一跳静默了。还有很多路由器根本就被
配置成从不发 ICMP 错误。一串 * * * 是一记耸肩,不是一堵墙。
如何拿到可信信号:tcptraceroute、mtr 与固定流 ID
要解掉“探测包很特殊”这个问题,就让它看起来像真实流量。TCP traceroute 向一个真正开着的 端口发 SYN——443、22,或你的服务在听的那个——于是沿途路由器把它当作正常连接,更关键的是, 最终主机会用 SYN-ACK 回应,而不是一条常被防火墙丢弃的 ICMP port-unreachable。
# TCP 模式 trace 到一个真实端口:-T = SYN 探测,-p = 目的端口。
traceroute -n -T -p 443 203.0.113.10
tcptraceroute -n 203.0.113.10 443
然后是 mtr,我第一个就想抓的工具:traceroute 外加一个循环,持续打印逐跳统计,让你看到损失与
延迟的分布,而不是单个采样。相当于照片对比电影。
# 实时矩阵;-T / -P 提供同样的 TCP 探测优势。
mtr -T -P 443 -n 203.0.113.10
# 报告模式:100 个周期足够把真实丢包与瞬时噪声分开。
mtr -T -P 443 -n -c 100 -r 203.0.113.10
对负载均衡的路径,要把流钉住。端口一变,ECMP 的哈希立刻跟着变;所以要固定四元组——并使用 固定的 Paris 流 ID——去走一条链路,而不是采样整片网络。
# 固定源/目的端口与流 ID,让哈希保持不变:一次运行里跳数相同说明是单条路径,
# 跳数不同说明你一直在采样。
traceroute -n -p 443 -s 40000 -N 1 203.0.113.10
读延迟:排队,还是距离
别急着把数字相加。某一跳的延迟,是到那台路由器控制面的 RTT,而这是它优先级最低的活儿。 一台负载中的路由器,可以第 5 跳显示 200 ms、第 6 跳显示 20 ms——对真实路径而言不可能,只是那台 路由器生成 ICMP 慢,而不是转发慢。这就是控制面的排队。所以:某一跳冒尖、随后的跳又 正常,意思是“忽略那一跳”;而冒尖在之后的每一跳都持续,才是路径上真实的、累加的增加。
另一个线索是丢包。从第 N 跳开始、一直延续到目的地的丢包,是真的。在第 N 跳出现、到了第 N+1 跳 又消失的丢包,是限速的生成器或测量假象——终端主机其实每个包都收到了。第 5 跳丢而目的地零丢, 不是路径问题;第 5 跳丢且目的地也丢,才是。
非对称路由:回程是另一个故事
Traceroute 只给你看去程——出站探测包收到了错误回应。你的服务器回出去的包,往往走一条
完全不同的运营商序列,尤其是涉及 anycast、CDN 边缘或双上游的时候。这就是非对称路由,
在公网上它是常态,不是例外。它要紧,是因为几乎所有真实症状——git clone 卡住、握手永远
完不成——都关乎两个方向的一对路径。一条去程看着完美无瑕的路径,回程可能已经坏了,而单端
永远看不见。
一例看着坏了、其实只是被限速的 traceroute
有用户报告 api.example.com“从办公室连很不稳”。我跑了这么一条:
$ traceroute -n api.example.com
1 192.168.1.1 0.3 ms
2 10.0.0.1 0.9 ms
3 * * *
4 * * *
5 * * *
6 * * *
7 198.51.100.7 14.2 ms
8 198.51.100.9 14.4 ms
9 * * *
10 203.0.113.20 15.1 ms
故事自己就写好了:“第 3 跳是黑洞,后面全是编的”。可是 curl 那台主机能通,还只用 15 ms——
和第 10 跳对上。包明明过得去,哪有什么黑洞。第 3~6 跳是某个运营商的 MPLS 核心不肯生成
ICMP,第 9 跳是一台会回应数据、却不回应探测的防火墙。图很丑,路径很健康。判据就是上面那条:
目的地回应了,且它的延迟对上最后一个可见跳。 只要你在通话的那一端可达且快,它上面的每一个
* * * 都是沉默的路由器,而不是坏掉的路由器。
到底怎么诊断一条路径的问题
当抱怨是真的,别再只抓一张快照。
1. 让 mtr 跑上一段时间。 一次 traceroute 只是随机过程里的单一样本;mtr -c 200 能给出
逐跳的丢包率与抖动。
# 看最后一行的丢包:只要目的地是 0%,它上面的每个空档都只是观感问题。
mtr -T -P 443 -n -c 200 -r api.example.com | tee /tmp/mtr-api.txt
2. 两端都测。 要看非对称路由,唯一办法是从另一侧跑同样的探测再对比。若去程干净、回程 不干净,你就找到了真正要命的那个方向。
3. 拿 TCP 重传去对照,而不是只看 ICMP。 ICMP 丢包只是代理指标;真实丢包会不折不扣地出现 在内核 TCP 计数器里。
ss -tin dst api.example.com # 逐连接的重传、RTT、RTO
nstat -az TcpRetransSegs TcpExtTCPSynRetrans
watch -n1 'nstat -z TcpRetransSegs TcpExtTCPSynRetrans | tail -3'
如果 mtr 显示目的地零丢包,而 TcpExtTCPSynRetrans 一路往上爬,那你有一个 ICMP 从未提及的
真实问题——典型的 ECMP 路径抖动,或者一条丢数据却不丢探测的链路。重传是地面真相,ICMP 是道听
途说。
4. 该拉第三条腿时就拉。 大传输死、小请求活,那更可能是 PMTU 而非路由问题——去读 MTU 与 PMTUD。查询慢但连接正常,那不是路径的锅,是解析器的锅—— 见DNS 如何解析。把“路由坏了”和“路由上某个东西坏了”分开,就是 这项工作的大半。
「路由经过了 X」这句话到底是什么意思
把这一切想过之后,诚实的那句话不是“流量经过了法兰克福”,而是:“在 14:02,我发往该目的地的 探测收到了来自这些路由器的 TTL 超时消息;目的地以 15 ms 的 RTT 回应;200 次探测里,目的地丢包 0.2%。” 这才是你站得住的论断。
Traceroute 是一幅路径速写,画它的是些随时可能拒绝报上姓名的陌生人,背景是一片按流变化、 回程你又看不见的织网。接受了这一点,它就不再是谎言,而回到它一直是的东西:一个便宜的探测包, 值它那点价钱——而当你跑它两百次、从两端跑、再拿 TCP 计数器去对照时,它就值钱得多。