路由环路这四个字,听着像教科书里的概念,但只要在运维一线待过,就知道它有多折腾人。我印象最深的一次是凌晨被电话叫醒,机房两台设备的面板灯闪得跟过节一样,业务侧只说"时通时断",登上去一看 CPU 直接顶到 90% 以上,链路流量打满,可路由表翻来翻去又"看不出什么大毛病"。折腾了快四十分钟才定位到根源:两条静态路由互相指向了对方,中间还夹着一条默认路由兜底,数据包就这么在两台设备之间来回转,直到 TTL 耗尽被悄悄丢掉。
从那之后我把环路的成因、排查、修复整理成了一套固定动作。下面要讲清楚三件事:路由环路到底是怎么被"喂"出来的、在设备上怎么用几条命令把它圈出来、以及怎么在配置下发之前就把它掐死。内容覆盖静态路由、动态协议重分发、路由汇总、多实例与 NAT 场景,最后会给一套能在模拟器里复现的实验步骤。刚入行的运维照着走能少走弯路,做了几年的网工也可以拿它当一份排查清单。
1. 环路现场的三个信号:它和"网络慢"根本不是一回事
1.1 CPU 高、链路打满、业务时通时断,三个一起出现才有味道
很多新人第一次遇到环路,第一反应是"网络慢",然后去查带宽、查运营商线路、查服务器负载,方向全跑偏了。环路和普通的拥塞有一个本质区别:环路上的包永远不会到达目的地,它是靠 TTL 耗尽被丢弃的。也就是说,流量不是"走得慢",而是"根本没走通",只是这个过程消耗了大量转发资源。
三个信号叠加出现时,基本可以往环路方向想。第一是设备 CPU 利用率异常升高,因为每一个环回的包都要重新做一次路由查表、TTL 递减、二层封装,转发面和控制面一起被拖住,CPU 自然下不来。第二是链路利用率被单条流打满,而且是两个方向的接口同时飙高,你去看接口计数器,某个接口的 output 速率和对面设备的 output 速率几乎一样。第三是业务表现成"时通时断"而不是"全断",因为 TTL 起始值不同(64、128、255),有些小包可能因为某条明细路由短暂存在而侥幸到达,大包和长连接就直接超时。
这里还要提一下容易被混淆的二层环路。二层环路的表现是广播包数量爆炸、MAC 地址表频繁震荡、接口指示灯高频闪烁,症状和三层的路由环路很像,但根因完全不同——通常是 STP 没收敛、链路聚合配错,或者有人把网线两头插在同一台交换机的两个口上。判断方法很简单:看广播包占比和 MAC 表是否在几个口之间反复跳。三层环路不会引起 MAC 表震荡,这是最直观的分界线。
提示:如果 CPU 高但流量不高、路由表也很稳定,优先怀疑是不是路由协议在频繁重收敛(flapping),而不是环路。环路一定伴随异常流量。
1.2 用 TTL 和路径记录把"环"画出来
TTL 是排查环路时最便宜也最好用的工具。IP 头里的 TTL 初始值一般是 64、128 或 255,每经过一跳减 1,减到 0 时路由器丢弃报文并回一个 ICMP 超时报文。所以你在两端做一次路径跟踪,如果中间出现同一组地址反复循环,那就是环路实锤了。
# 华为设备 tracert -a 192.168.1.100 10.10.10.10 # 思科设备 traceroute 10.10.10.10 source 192.168.1.100 # 带路径记录的 ping,可以看到去程完整路径 ping -R 10.10.10.10但这里有三个坑必须提前知道,否则很容易误判。第一,tracert 只能看到去程,如果环路发生在回程,你在源端看结果会一切正常,必须两个方向都做一次,或者直接在两端设备上做带源地址的 traceroute。第二,有些设备默认禁 ICMP 回应,tracert 结果全是星号,这不代表没有环路,别把"看不到"当成"没问题"。第三,某些设备会改写 TTL(NAT 设备、部分安全设备、隧道封装),改写之后 TTL 递减规律就被破坏了,这时候要靠抓包看实际的源目 MAC 交替才能确认。
我个人习惯是:一旦怀疑环路,先在源端和目标端各做一次 traceroute,把两条路径的地址序列复制下来并排放在一起看。凡是出现 A→B→A→B 这种交替的,直接锁定这两台设备去查它们的路由表。这个方法不用抓包,五分钟内就能从"怀疑"变成"确认"。
2. 静态路由互指与默认路由兜底:最常见也最容易忽略的成因
2.1 "下一跳依赖目标可达"这个死结
静态路由互指是环路里最经典的一种。构造它只需要两条配置:
# R1 上 ip route-static 10.1.2.0 255.255.255.0 10.0.0.2 # R2 上 ip route-static 10.1.1.0 255.255.255.0 10.0.0.1单看每一台设备,配置都是"有道理"的:R1 认为去 10.1.2.0 网段应该走 R2,R2 认为去 10.1.1.0 网段应该走 R1。问题出在当 10.1.2.0 这个网段实际上并不挂在 R2 后面,或者 R2 那一侧已经摘掉了,R2 收下包之后按自己的默认路由或者别的明细路由,又把包送回 R1。两台设备的转发意愿叠加起来,就形成了一个封闭的圆。
更隐蔽的是递归查表失败引发的环路。如果写静态路由时只写了下一跳地址、没写出接口,设备需要再查一次路由表来确定怎么到达这个下一跳。要是这条用于解析下一跳的路由本身指向对端,或者被默认路由兜住,就会出现"A 为了到达 B 需要先到达 B"的死结。解决办法不复杂:静态路由明确写出接口加下一跳,例如ip route-static 10.1.2.0 24 GigabitEthernet0/0/1 10.0.0.2,这样设备不需要再做一次递归查表,转发路径是确定的。
我踩过的一个坑是:早期为了"配置简洁",习惯只写下一跳不写出接口,结果在一台有多条等价路径的设备上,静态路由被解析到了错误的接口上,包出去之后又被邻居送回来,绕了一大圈才排到。从那之后我的规矩是——静态路由一律写"出接口 + 下一跳",除非确认对端是点到点链路。
2.2 默认路由互指的隐蔽性:内网正常,只有外网不通
比静态互指更难查的,是两端都写了默认路由指向对方:
# R1 ip route-static 0.0.0.0 0.0.0.0 10.0.0.2 # R2 ip route-static 0.0.0.0 0.0.0.0 10.0.0.1这种配置最常见的来源是"想做一条备用线路",配的时候图省事,两端各指对面,想着"反正有一条通就行"。结果就是:内网互访全正常(因为有明细路由),只有访问外网或者某些不在明细表里的网段才出问题。这时候很多人会去查运营商、查光猫、查 DNS,方向完全跑偏。
这种环路的排查切入点很特别:看哪些流量受影响,反推它落在了哪张表里。如果是"访问外网不通但内网全通",优先怀疑默认路由。在两台设备上各执行一次display ip routing-table 0.0.0.0,看到的下一跳要是指向对方设备,而对方设备又指回来,环就成立。
正确的做法是:默认路由只能有一个明确出口。如果确实需要主备两条默认路由,要用**不同的优先级(preference)**来区分,而不是两端互指:
# R1 上两条默认路由,主用优先级 60,备用优先级 100 ip route-static 0.0.0.0 0.0.0.0 10.0.0.2 preference 60 ip route-static 0.0.0.0 0.0.0.0 10.0.0.6 preference 100注意:优先级数值越小越优,两条默认路由的优先级必须不同,否则会形成等价负载分担,一旦其中一条的下一跳不可达,流量分配就会出问题。
3. 动态路由重分发与路由汇总:协议交互里长出来的环路
3.1 双点双向重分发为什么几乎必然出问题
静态路由的环是"人写出来的",动态协议的环则是"协议之间传出来的"。最典型的就是双点双向重分发:两个不同的路由域通过两台边界设备互联,每台边界设备都做了双向重分发。这种设计在现网里非常普遍,比如一个 OSPF 域要和一个 BGP 域对接,或者是两个不同 OSPF 进程之间互引。
为什么这种结构容易出环?举个具体过程。R1 和 R2 是两台边界设备,左侧是协议 A,右侧是协议 B。R1 把协议 A 的路由引入协议 B,R2 也把协议 B 的路由引入协议 A。当 R1 把一条 A 域路由注入 B 之后,这条路由会沿着 B 域传到 R2;R2 因为在做双向重分发,会把这条"本来来自 A 域"的路由再注回 A 域。于是 R1 又从 A 域里学到了它自己发出去的路由,而且这条回注的路由度量值可能更优,R1 就把去往该网段的流量指向了 R2,而 R2 那边又指回 R1,环形成了。
这里的关键在于:路由协议自带的防环机制管不了跨协议这件事。OSPF 内部靠链路状态数据库天然无环,BGP 靠 AS-PATH 防环,但一条路由从 OSPF 出去、经过 BGP、再回到 OSPF,中间没有任何机制记录"它已经出过一次门"。所以跨域重分发必须靠人为打标记来防环。
我在排查这类问题时,习惯先看两件事:一是两台边界设备上同一条外部路由的下一跳是否互指;二是路由是否在短时间内反复出现又消失(flapping)。同时满足这两条,基本就是双向重分发没打标记。
3.2 汇总路由缺了 Null0 兜底,黑洞和环路就在一线之间
路由汇总本身不产生环路,但汇总之后缺少兜底路由会带来两类问题,一类是黑洞,一类是环路。假设一台 ABR 上配置了abr-summary 10.1.0.0 255.255.0.0,把这个大网段通告出去。如果这台 ABR 自己有完整的明细路由,没问题;但如果明细路由是从别处学来的、只存在一部分,那么当对端设备把目的地址落在汇总范围内、却没有任何明细匹配的流量送过来时,ABR 按汇总路由匹配上,却发现没有下一跳可以转发,只能丢弃。
那环路是怎么来的?如果这台 ABR 上还配了一条默认路由指向另一台设备,那么这批"汇总范围里找不到明细"的流量就会顺着默认路由被送到另一台设备,而那台设备如果又有一条指回来的路由(比如它也做了重分发),两个设备就会兜圈。黑洞和环路的分界线,就在于汇总设备有没有一条明确的兜底丢弃路由:
# 汇总网段指向 NULL0,明确丢弃 ip route-static 10.1.0.0 255.255.0.0 NULL0 # BGP 聚合时抑制明细 aggregate 10.1.0.0 255.255.0.0 detail-suppressed这条 NULL0 路由看起来"没用",因为它把流量丢了。但它丢得明确、干净、不消耗额外资源,远好过让流量在两个设备之间来回绕。这一点很多人在配汇总时会漏掉,以为汇总配完就万事大吉。
3.3 协议自带的防环机制到底能挡住多少
把各个协议的防环能力列清楚,排查时心里就有底了:
| 协议 | 主要防环机制 | 能防住的场景 | 防不住的场景 |
|---|---|---|---|
| RIP | 最大 15 跳、水平分割、毒性逆转、触发更新 | 协议内部计数到无穷 | 跨协议重分发、静态互指 |
| OSPF | 链路状态算法、区域间骨干规则、外部路由 tag | 区域内、区域间 | 重分发未打 tag、汇总无兜底 |
| ISIS | 分层结构、ATT 位、SPF | 协议内部 | 跨协议重分发 |
| BGP | AS-PATH、ORIGIN、Next-Hop | 域间防环 | 本地重分发、iBGP 全互联缺失 |
看这张表就明白了一个结论:协议内部的环基本不用担心,真正的风险全部集中在"协议与协议之间"和"人与设备之间"。所以排查环路时,优先级最高的动作不是去看协议状态,而是去看重分发配置、静态路由和策略路由这三处。
4. 多实例、NAT 与策略路由:虚拟化和转换场景下的隐藏环路
4.1 虚拟系统之间的路由泄露:别用"丢给对方实例"当兜底
现在很多防火墙和路由器都支持虚拟系统或者 VRF,一台物理设备上跑多个独立的路由实例,每个实例有自己的路由表和接口。这种设计本身很干净,但实例之间的路由关系如果没理清楚,环路比单实例更难查——因为你在一个实例里查路由表,看到的永远只是"半张图"。
典型的风险场景是这样的:实例 A 处理某个业务网段,实例 B 处理另一个。为了让 A 里访问不到的流量有个出口,有人在 A 里写了一条默认路由指向 B;同时 B 里为了兜底,也写了一条默认路由指回 A。单看每个实例的配置都"很合理",合起来就是一个死循环。而且因为跨实例的转发会经过内部通道,抓包都不一定抓得到,排查难度直线上升。
我的处理原则很明确:每个实例的兜底路由必须是 NULL0,绝对不能用"丢给另一个实例"来当兜底。跨实例的引流必须是有明确目的地的明细路由,而不是一条包罗万象的默认路由。同时,跨实例引流一定要保证回程路径存在,否则就会退化成一去一回两个默认路由的循环。
4.2 NAT 引流与回程路由不一致,会话表是唯一真相
NAT 场景下的环路,很多人会卡很久,因为路由表看起来完全正常。原因是 NAT 会改变报文的源地址或目的地址,转发的两个方向查的是不同的路由条目。
举个典型情况:源 NAT 之后,内部主机的地址被转换成出口地址。回程流量到达时,目的地址已经是转换后的地址,设备需要把会话还原再转发给内网主机。如果回程流量到达的是错误的实例或者错误的接口,而那一侧的默认路由又指向另一台设备,报文就会被送出去、再被送回来,形成环路。整个过程在路由表上看不出任何异常,但会话表里会有大量半开连接。
排查这类问题时,我建议直接看会话:
# 华为防火墙 display firewall session table verbose destination 10.10.10.10 display firewall server-map # 思科 ASA / 类似设备 show conn detail show nat detail会话表里重点看三件事:转换前后的地址、入接口和出接口、会话的存活时间和包计数。如果某个会话的包计数在两个方向上持续增长但业务不通,基本上就是回程路径错了。修正的方向是让回程流量在正确的接口和实例上被匹配到,而不是简单地"加一条默认路由"——加默认路由正是很多环路事故的起点。
4.3 策略路由绕开路由表,让"看表排查"彻底失效
策略路由(PBR)是排查环路时最容易被忽略的一环。它的优先级高于普通路由表,也就是说,即使路由表配得完全正确,只要有一条策略路由命中了流量,转发行为就由策略决定。这带来的直接后果是:你盯着路由表看半天,怎么都对不上号。
常见的环路构造是这样的:PBR 把某个源地址的流量强制引到下一跳 R2,而 R2 上没有针对该源地址的回程路由,于是 R2 按默认路由把回包送到 R3,R3 又有一条路由指回 R1。整条路径绕了一圈,但因为去程和回程查的是不同机制,你在任何一台设备上单独看都看不出问题。
两个实用建议。第一,PBR 的匹配条件要尽可能精细,用 ACL 精确匹配源、目的、端口,避免命中范围过大。第二,PBR 配置里最好有一条明确的兜底放行或者兜底丢弃,让不匹配的流量老老实实走路由表,而不是被默认动作送进某个不确定的方向。排查时,务必在所有沿途设备上执行一次 PBR 相关的查看命令:
# 华为 display policy-based-route display acl all # 思科 show route-map show ip policy5. 排查链路:从接口计数器逐跳定位到环路圈
5.1 第一轮:三个动作确认环路是否真实存在
排查环路,第一步不是查配置,而是确认现象。我通常按固定顺序做三个动作,三分钟出结果。
第一,看双向路径。从源端和目标端各做一次 traceroute,把结果并排比对,找重复出现的地址段。第二,看接口计数器。在两台嫌疑设备的互联接口上执行接口查看命令,重点看两个方向的速率是否同时异常高,以及错误包、丢弃包的数量:
# 华为设备常用 display interface GigabitEthernet0/0/1 display interface brief | include up display cpu-usage display ip routing-table statistics # 思科设备常用 show interfaces GigabitEthernet0/1 | include rate|errors show processes cpu sorted show ip route summary第三,看路由表规模有没有异常抖动。执行两次路由表统计,间隔十秒,对比条目数变化。如果条目数在短时间内反复加减,说明有协议在重收敛,这和环路常常是一对伴生现象:环路导致协议报文丢失,协议重收敛又加剧了路由变化。
提示:做接口计数的时候一定要用带速率显示的完整命令,不要只看
brief版本。brief只给 up/down 状态,看不出流量异常。
5.2 第二轮:列出"参与环路的设备清单"
确认环路存在之后,下一步是把环路圈里的设备一台台找出来。做法是沿 traceroute 的结果逐跳走,对路径上每一个地址对应的设备,查它去往目标网段的下一跳,看是否指向了路径上的上一台设备。
具体操作上,我会准备一张纸(或者一个文本文件),左边写下路径上出现的地址序列,右边写下每台设备去往目标网段的路由来源(静态 / OSPF / BGP / 直连)。只要出现"A 的下一跳是 B,B 的下一跳是 A"或者"A→B→C→A"的闭环,环路圈就确定下来了。
这一步有个技巧值得说:不要只查目标网段的路由,还要查默认路由。很多时候明细路由是对的,问题出在某一台设备上明细路由缺失,流量掉进了默认路由,而默认路由恰好指向环路里的下一台设备。所以查路由时要同时看三样:目标网段明细、默认路由、以及这条路由的来源协议。
5.3 第三轮:抓包验证,把推断变成证据
到这一步,环路圈和嫌疑设备都清楚了,但下结论之前我习惯再抓一次包,因为改动之前留一份证据,事后复盘和写变更报告都用得上。
抓包时在嫌疑链路做端口镜像,过滤目标地址,重点看三个特征:同一个目的地址的报文在两个方向上反复出现、TTL 值逐次递减 1、源目 MAC 在几个固定组合之间交替。这三个特征同时出现,环路就是板上钉钉。
下面这张对照表是我这些年排查时用得最多的,可以照着症状快速缩小范围:
| 症状表现 | 最可能的原因 | 快速验证方式 |
|---|---|---|
| 特定网段不通,ping 提示 TTL 传输中过期 | 静态路由互指、默认路由互指 | 两端查该网段下一跳是否互指 |
| 全网间歇抖动,路由表频繁变化 | 双点双向重分发未打标记 | 查重分发配置和 route-tag |
| 部分网段不通,路由表稳定但 CPU 偏高 | 汇总缺 NULL0 兜底 | 查 ABR 是否有汇总对应的丢弃路由 |
| 路由表完全正常,但特定业务不通 | 策略路由或 NAT 回程路径错 | 查 PBR 配置和会话表 |
| 广播包暴涨、MAC 表震荡 | 二层环路(非路由环路) | 查 STP 状态和链路聚合配置 |
6. 收口与预防:把环路掐在配置下发之前
6.1 静态路由的四条书写规矩
静态路由是环路的高发区,因为它是人手写的,没有协议帮你兜底。我给自己定了四条规矩,这些年基本没再写过环路。
第一,能写明细就不写大网段,除非明确知道需要一条汇总路由。第二,静态路由一律写出接口加下一跳,避免递归查表解析到意外的接口。第三,两台设备的默认路由绝不能互指,需要主备就用不同的优先级区分,需要兜底就用 NULL0。第四,任何静态路由变更都要先想清楚回程——去程怎么写,回程怎么走,两个方向都要在纸上画一遍再下发。
第四条听起来啰嗦,但它是性价比最高的一条。我见过太多环路事故,配置本身没问题,问题是只考虑了去程。网络是双向的,只规划单向的路由方案,等同于埋雷。
6.2 重分发必须配齐的三道护栏
跨协议重分发是环路的重灾区,但只要配齐三道护栏,风险能压到很低。
第一道,优先单向重分发。如果业务允许,只从一个协议域向另一个注入路由,不要双向。单向注入天然不存在回注问题,这一条能省掉后面所有的麻烦。第二道,必须双向时统一打 tag。在引入时给路由打上标记,在接收侧通过策略拒绝带标记的路由:
# OSPF 引入外部路由时打 tag ospf 1 import-route bgp tag 200 # 接收侧拒绝带 tag 200 的路由 route-policy DENY-TAG deny node 10 if-match tag 200 route-policy DENY-TAG permit node 20第三道,用前缀列表精确控制注入范围。只放行本域真实存在的网段,其他一律拒绝。很多重分发环路的根源就是"引入时图省事用了 no-advertise 全放",结果把邻居域的路由也带进来了。三道护栏配齐之后,重分发基本不会再自己长出环路。
6.3 汇总和黑洞路由必须成对出现
路由汇总的规范很简单:凡是有汇总的地方,汇总设备上必须有一条指向 NULL0 的丢弃路由。这不是可选项,是必选项。
除了 NULL0,还有两个细节值得注意。一是汇总范围要精确,不要用超网图省事,比如把两个不连续的网段硬汇总成一个大网段,会引入大量无效流量。二是保留关键明细,如果某个明细网段有特殊的下一跳需求(比如走专线),要在汇总的同时把它单独附加上,否则会被汇总路由淹没。
BGP 场景下还要注意聚合的写法差异:aggregate默认会同时发布明细和汇总,如果希望只发汇总,需要加上抑制明细的参数。这一点在跨域对接时特别重要,因为对端往往只想要一条汇总路由。
6.4 多实例环境的验证清单
虚拟系统、VRF 这类多实例环境,建议每次变更后跑一遍固定清单:每个实例的默认路由是否指向 NULL0(而不是指向别的实例);跨实例的引流路由是否写了明确的目的地址;两个方向的 traceroute 是否都能走通;会话表里有没有长期半开的连接。
这张清单看起来只有四条,但每一条都对应过真实事故。尤其是第一条,我见过太多人习惯性地把默认路由丢给"主实例",觉得主实例有出口能兜住,结果主实例的回程路由又指回来,两个实例之间的内部通道被打满,整机性能断崖式下跌。
7. 在模拟器里复现一次双点双向重分发环路
7.1 拓扑搭起来:两台边界设备、两个协议域
想真正理解环路,光看文字不够,最好自己复现一次。用模拟器搭一个最小拓扑就行:四台设备,R1 和 R2 作为双边界,左侧跑 OSPF 进程 1,右侧跑 OSPF 进程 2(用两个进程模拟两个域,比搭 ISIS 更省事)。
核心配置就是两台边界设备各自做双向重分发:
# R1 ospf 1 import-route ospf 2 ospf 2 import-route ospf 1 # R2 ospf 1 import-route ospf 2 ospf 2 import-route ospf 1搭好之后在任意一台设备上看路由表,你会看到右侧网段的路由同时从两个方向学过来,而且下一跳互相指向对方。这时候从左侧终端去 ping 右侧一个不存在的地址(比如右侧网段里的某个空缺地址),就能看到环路现象。
7.2 观察现象:用三条命令锁定环路
复现出来之后,别急着改配置,先把现象记录下来。三条命令足够:
# 1. 看路由表里目标网段的下一跳,是否互指 display ip routing-table 10.2.2.0 # 2. 看 CPU 和接口速率是否异常 display cpu-usage display interface GigabitEthernet0/0/1 # 3. 做一次 tracert,看地址是否循环出现 tracert -a 10.1.1.1 10.2.2.99我第一次做这个实验的时候,tracert 输出里 R1、R2 的接口地址连续出现了六次,那个画面比任何文字描述都直观。同时打开接口计数器,会看到两个方向的速率几乎完全对称——这就是环路的"指纹"。
7.3 修复与回归:先打标记,再验证
修复方式就是前面说的第二道护栏,给引入的路由打 tag,在接收侧拒绝带标记的路由。改完之后一定要做回归验证,重点看三件事:目标网段的路由下一跳是否唯一且正确、tracert 路径是否恢复正常、CPU 是否回落到正常水平。
我个人的习惯是在模拟器里把这个实验反复做过几次,然后故意造一些变种:把双向改单向、把 tag 去掉、加一条默认路由互指。每造一个变种,就按第 5 节的排查链路走一遍。折腾两三个晚上之后,现网再遇到类似问题,基本看两眼路由表就能判断方向。模拟器最大的价值就在这里——它允许你把错误犯够,而且不花一分钱。
最后分享一个小技巧:给所有做重分发的边界设备统一加上一句注释,写明"引入路由的 tag 值"和"拒绝策略的名称"。这个动作看起来无关紧要,但当你半年后接手一台别人配的设备,或者自己半夜被叫起来排查时,这两行注释能省下大量翻配置的时间。环路排查最耗时间的从来不是解决问题,而是搞清楚"当初为什么这么配"。