一条链路断开后,剩余拓扑仍有可用路径,为什么报文可能暂时在两个路由器之间往返?最终最短路径存在,并不意味着各节点已经同时知道变化,也不意味着它们同时安装了相容的转发状态。

第 10 篇区分计算与安装。本篇把这个区别扩展到多个节点,用同一张三节点图比较两类信息传播方式:距离向量交换到目的地的距离,链路状态传播拓扑信息。实验是有界、确定性的教学模拟,没有运行 RIP 或 OSPF 守护进程。

先固定拓扑与故障

设无向链路 A-B 的成本为 1,B-C 为 1,A-C 为 5,目标是 C。这里的成本是算法输入,不是测得的时延、丢包率或带宽倒数。

1
2
3
4
    1       1
A ───── B ───── C
╲ ╱
╲──── 5 ────╱

断链前,A 经 B 到 C 的成本为 2,比直接到 C 的 5 小;B 直接到 C 的成本为 1。删除 B-C 后,稳定答案应为 A 直接到 C,成本 5;B 经 A 到 C,成本 6。两种算法比较的是同一个删除事件,不能给其中一种额外保留一条链路。

这个最终答案可以手算,也可以在完整新图上计算。但分布式更新中,每个节点处理的是自己当前掌握的信息。用全知视角算出最终答案,尚未解释过渡过程。

距离向量中的旧信息

对目的 C,节点 A 可以比较各邻居报告的距离:

1
2
经邻居 N 的候选成本 = A 到 N 的链路成本 + N 报告的到 C 距离
A 的估计 = 所有可用候选中的最小值

这里读取的是邻居曾经报告的数值,不是直接观察邻居此刻采用的完整路径。一个小的距离值可能依赖已经发生变化的链路,也可能依赖当前节点本身。若更新消息没有携带完整路径,仅凭数值不能识别所有相互依赖。

RFC 2453 §3.4讨论距离向量及其更新问题。RIP 还包含度量范围、水平分割、毒性逆转、触发更新和定时处理等机制;本篇有意先省略这些保护,展示朴素更新规则可能出现的现象。因此程序不能称为完整 RIP 实现。

在 B 刚得知 B-C 已断开时,若它仍保存 A 之前报告的距离 2,就可能计算经 A 到 C 的成本为 1+2=3。但此刻 A 的已安装下一跳还是 B。于是两台节点都认为对方提供了出口,实际下一跳关系形成 A-B-A。

这个环路不需要任意一个节点故意选择较差候选。每次局部选择都可能符合其当前缓存,问题是缓存之间缺少一致的新信息。不能只检查一台节点的最小值计算,就断言全网没有转发环路。

为什么距离会逐步增加

若按固定事件顺序传递消息,A 收到 B 的新值 3 后会比较直接成本 5 与经 B 成本 4,于是仍选 B。B 再收到 A 的 4,又把经 A 的成本更新为 5。下一次 A 收到 B 的 5,直接成本 5 小于经 B 的 6,才改为直达 C。B 随后获知 A 的 5,稳定为经 A 的 6。

这段过程中的数值增加来自旧路径信息的相互引用。由于图中存在成本有限的直接 A-C 链路,此例会转向该备用路径;它不是一个一直增长到 RIP 无穷度量的运行记录。计数到无穷的更一般问题与具体 RIP 的有限无穷值,不能和这张图的停止条件混成一件事。

水平分割和毒性逆转会改变节点向特定邻居报告什么值,因此会改变上述事件。不能先在模拟中省略这些机制,又把得到的环路写成所有 RIP 配置的必然结果。反过来,加入某一种保护也不能凭一个小图就宣称所有拓扑都没有环路。

链路状态先改变知识,再改变路径

链路状态方法传播链路相关信息,节点根据掌握的拓扑运行最短路径计算。OSPF 的泛洪与最短路径计算分别见 RFC 2328 §13§16.1

“节点拥有同一张图”需要条件。同一区域内完成同步后可以讨论相应的一致链路状态数据库,不能把传播期间也当成全体节点同时获得新状态,更不能要求不同区域的数据库完全相同。本篇仅用单一小图说明局部知识更新,不模拟 OSPF 的邻接建立、LSA 编码和可靠泛洪。

即使 B 已拿到完整的新图并正确计算经 A 到 C 成本 6,如果 A 仍安装旧路径,经 B 到 C,报文仍可能沿 A-B-A 循环。B 的新结果和 A 的旧结果分别都有计算依据,但组合起来暂时不相容。

RFC 5715 §6.8讨论转发更新相关的微环路问题。本例人为安排 B 先安装、A 后安装,只证明这种顺序可以产生环路;它不是某个 OSPF 实现必然采用的事件顺序,也不提供真实持续时间。

怎样判断已经稳定

每一步都应保存节点掌握的信息和实际下一跳。距离向量记录邻居距离缓存,链路状态记录本地图的版本;计算结果与安装状态也应能够对应。只打印最终路径会丢掉最需要解释的过渡阶段。

模拟报文沿已安装下一跳前进时,可以维护已经访问的节点集合。再次访问同一个节点说明在该静态快照中形成了环;每一跳还必须检查当前图中是否存在对应链路。只有经过存在的链路达到目的节点,才表示该快照提供一条到达路径。这是图上的轨迹,不是抓包结果。

有限步数只保证程序结束,不自动证明算法收敛。验收还应检查最终成本和下一跳,并在知识状态不再变化的条件下再次处理有效更新,确认结果不变。本例这样验证的是这张图和这段事件序列的固定点,不是任意网络规模下的普遍收敛定理。

TTL 可以限制真实报文在环路中的寿命,却不修复产生环路的控制状态。模拟中检测到环后结束轨迹,是为了避免无界循环,不能称为已经消除了路由环路。

复现同一故障的两段更新

素材目录提供 模拟程序本次运行结果实验记录。下载到同一目录后,使用 Python 3.11 或更新版本运行,无须安装第三方包:

1
2
3
python3 routing.py --help
python3 routing.py > actual.json
python3 routing.py --event-limit 6

前两条正常结束;第三条应报告事件容量不足并以状态码 2 退出。默认容量为 7,只容纳固定的七次距离向量更新,不是以秒计的超时,也不改变事件调度。不得使用 python3 -O,因为运行内置验收需要保留断言。

2026 年 9 月 20 日执行所得距离向量记录如下。每一行对应一次节点接收距离、计算并立即安装的事件,轨迹从 A 出发。

事件 更新节点 新成本 新下一跳 该快照的 A 轨迹
1 B 3 A A-B-A,环路
2 A 4 B A-B-A,环路
3 B 5 A A-B-A,环路
4 A 5 C A-C,模型内可达
5 B 6 A A-C,模型内可达

随后再次更新 A、B,成本和下一跳均不变。程序由新图的邻接关系和接收缓存计算候选,而不是直接把表格中的路径作为结果返回;断言另行核对这张图的预期答案。

链路状态部分在每个接收事件运行 Dijkstra。B 先采用新图后成本直接为 6,但 A 仍指向 B,因此第一次快照仍为 A-B-A。A 随后采用新图并安装直达 C 的路径,A 的轨迹变为 A-C。再次计算 B、A,结果不变。这只能比较两段指定序列中的状态,不能用事件数之差推算真实协议收敛速度。

反序对照先让 A 更新、B 保留旧表,从 B 出发会返回 link-down,轨迹标记为 B-C。这里 C 是尝试采用的下一跳,不表示报文已经抵达 C;程序检查到 B-C 已从当前图中删除就结束。只沿下一跳名称走到目标而忽略断边,会错误地把这个案例判成成功。

验证边界

本次验证覆盖初始最短路径、距离向量缓存更新、链路状态重算、已安装下一跳组合、环路检测、断边检测与指定序列的固定点。正常调用的重复输出一致,帮助入口可用,容量不足和非法参数会被拒绝。结果是标准库程序生成的状态记录,没有路由报文或数据报文。

程序只支持写在源码中的正成本三节点图,等成本时按下一跳名称排序。没有模拟 RIP 的水平分割、毒性逆转、定时器和有限无穷度量,也没有模拟 OSPF 的邻接、可靠泛洪、LSA 老化、区域间路由或硬件安装延迟。每个节点在自己的事件中立即安装结果,异步性来自节点间的事件顺序。

没有启动实际路由协议,因此未验证守护进程行为、故障检测时延、收敛耗时、实际丢包量或生产网络可用性。要补这些证据,需要在隔离拓扑中记录链路变化、协议消息、各节点路由安装及业务报文的共同时间线;本篇结果不能替代这组记录。

练习

第一题:断开 B-C 后,B 读取 A 的旧距离 2,为什么经 A 得到 3,而不是最终正确成本 6?这个算式本身是否算错?

校验:链路 B-A 成本为 1,加上缓存的 2 得到 3,算术没有错误。旧距离 2 依赖已经失效的 B-C 路径,问题发生在信息有效性及依赖关系,不是加法。

第二题:链路状态场景中,若 A 先安装新路径直达 C,B 仍保留指向已断 B-C 的旧路径,这是否也会形成 A-B-A?

校验:不会形成同一个双节点环,但 B 的旧出口已经断开,仍可能丢包。不同更新顺序可以改变瞬态故障形态;没有观察到环路,不能推出无损切换。

参考资料