如何区分 VPN 站点上的路由环路与路由抖动
路由环路和抖动路由会造成同样的间歇性丢包。在动手改动之前,网关上的三项检查就能把两者区分开。
两个站点、一条隧道,以及一个先应答一阵、随后归于沉默的远端前缀。第一反应是把它叫作路由环路,有时确实如此。但环路和抖动的路由会引发同样的投诉,却需要相反的修法:一个是永不终止的转发决策,另一个是不断离开路由表的前缀。三个观察能把它们分开,而在动手改过滤器或重启隧道之前,你就能从网关命令行把这三项都收集齐。
环路是一跳一跳定下来的。站点 A 把 B 选为该目的地的下一跳,B 又把 A 选为同一目的地的下一跳,于是数据包在两趟往返之后因 TTL 耗尽而丢弃。抖动则由路由协议决定:前缀被撤销、重新安装,或被备用的下一跳取代,因此有些数据包根本没有路由,另一些却沿着一条完全正常的路由转发。
三项判定性检查
做第一个判断不需要抓包。你需要两端的前缀状态、各自选出的下一跳,以及一个要么重复要么不重复的跳数。若流量失败期间前缀仍以同一下一跳安装着,环路就位于转发路径上。若前缀消失又回来,故障就在转发之上,修法属于路由协议,而不是静态路由。
| Observation | Routing loop | Route flapping |
|---|---|---|
| Prefix in the table | Installed, same next hop throughout | Withdrawn and reinstalled, or next hop alternates |
| Hop count | Reaches zero at the same two gateways | Expires only while the prefix is missing |
| Failure shape | Consistent loss for that prefix from every source | Bursts, often after a link or peer event |
| Where the fix belongs | The site that owns the prefix, plus filters on the transit path | Advertisement, hold timers, or the flapping link |
开始之前先提醒一句。如果你正通过那条正在环路的隧道阅读本文,改动路由的瞬间你的会话就会断开。请打开一个不依赖待修前缀的控制台或管理通道。
检查一:前缀是稳定的还是在翻腾
要问内核装了什么,而不是配置文件承诺了什么。在 Linux 网关上,ip route get 10.20.30.40 执行的是与转发路径完全相同的查表过程,包括策略路由表;而 ip route show 10.20.30.0/24 只显示这一条记录本身。ip monitor route 会在每次变化发生的当下把它流式输出,这是观察前缀来去最快的方式。
在运行 FRR 或 BIRD 的路由器上,值得看的并不是转发表。show ip bgp 10.20.30.0/24 返回被选中的路径及其存活时间;每隔几分钟就重置一次的存活时间是可以叫出名字的抖动,而不是环路。show ip bgp summary 显示邻居状态和前缀数量,因此不断掉线的对端就能把嫌疑从转发路径上移开。
动手之前先把答案写下来。在 ping 全部失败的情况下稳定十分钟,与十分钟内消失四次,导向的下一步完全不同。后一种情况会让所有静态路由的猜测都失去意义。
检查二:下一跳是否指回入向隧道
环路需要两台网关互相配合。在接收数据包的站点上,看这个包出站会走哪条路由:用带真实源地址的 ip route get 10.20.30.40 from 10.10.5.20,因为策略规则给出的答案可能与主表不同。然后再到发出方站点重复同样的查询。如果两侧为同一个前缀互相把对方选为下一跳,环路即被证实,再抓包也不会多出任何信息。
原因几乎总是 一条本不该穿过隧道的通告。被重分发进隧道的默认路由、覆盖了远端站点自身子网的聚合路由,或是为防万一只指向对端的静态路由,都会造成同样的互相指认。隧道可以完全建立,却仍然把流量绕成圈;strongSwan 的转发说明描述了这一要求的选路侧。
WireGuard 值得单独写一条,因为它没有通告可以怪罪。wg show 列出每个对端的 AllowedIPs,内核只有在目的地址落在这个范围内时才把它交给该对端。那里的环路要么是重叠的 AllowedIPs 范围,要么是指回接口另一侧的静态路由,因此修法只在这两处。
检查三:读跳数,不要读星号
traceroute -n -I 10.20.30.40 和 mtr -n -r -c 20 10.20.30.40 都能显示路径,而 ICMP 模式往往能在吃掉 UDP 探测包的过滤下存活。环路看起来就是同样两个网关地址短促重复,而 TTL 每次都往上加一。丢包则表现为最后一个应答网关之后的星号,而孤单的一个静默跳并不能证明任何事。
路径含糊时,去读线上的 TTL。在两台网关上运行 tcpdump -ni any -vv icmp,可以看到每个包的 TTL,以及每个超时报文的源地址。如果一个包到达远端站点时的 TTL 低于这条路径可能包含的跳数,就说明有什么东西在悄悄加跳,而 traceroute 看不见;这通常发生在其中一个站点的防火墙替另一个站点应答探测包的时候。
修前缀的归属方,然后双向验证
先确定哪个站点产生该目的地、哪些过渡路径被允许,然后在该归属方修改通告或静态路由。去改接收端很诱人,但通常是错的:它只把一个前缀的环路藏起来,却把错误的通告留给下一个前缀。路由标记、分发列表和水平分割规则正是为了在源头拦住它,而且比只匹配单个前缀的过滤器更省事。
改动之后要分开验证去程和回程,因为环路可能被换成一个黑洞,而症状看上去毫无改善。从两端分别运行 ip route get,从未充当网关的主机发送流量,并让 ip monitor route 至少跑过一个保持定时器,以免把抖动误当成修好了。
动手之前先确认这五点:前缀是稳定还是在翻腾、两端下一跳是否都指向隧道、跳数是否重复、是否只有一个站点拥有该前缀,以及你手上是否有一条能扛住这次改动的管理通道。五点里只要有一点不确定,其余四点就都是猜测。