路由计算已经选中了新的下一跳,为什么同一个目的地址仍然沿旧路径转发?如果配置接口返回成功,是否足以说明所有后续报文都会使用新路径?这两个问题都要求区分计算结果、已安装状态和实际查询结果。

第 07 篇给定一张表,验证最长前缀匹配。本篇继续使用那份教学转发器,但不再直接把候选路由全部交给它。候选信息先经过选择,再显式安装为转发快照。实验只输出决策,不配置内核路由,不发送真实报文。

同一目的前缀为何有两种信息

Routing Information Base(RIB)与 Forwarding Information Base(FIB)描述不同用途的信息。路由信息可以包含可供选择的候选路径;转发查询需要取得当前应使用的下一跳、出口等信息。两者有关联,但不能因为都包含前缀就将它们视为同一份状态。

RFC 3222 §5.3–5.4在路由器基准测试语境中说明 RIB 与 FIB。使用这些术语时应保留文档范围:它不是要求每台路由器必须采用两张物理表,也不能由其中对次选路径的描述推导出现代实现不能支持等价多路径。

设目的前缀为 10.6.2.0/24,候选 A 的下一跳为 192.0.2.1、成本为 10,候选 B 的下一跳为 192.0.2.2、成本为 20。若明确规定同一前缀只保留成本最低者,计算结果应选 A;B 仍可作为候选存在,但不会因其存在就自动成为本次转发出口。

这套成本规则仅用于教学。不同协议、路由来源和产品可能采用不同选择规则,不能把这里的两个整数当成跨协议可直接比较的统一度量。后续章节再讨论自治系统内算法与跨自治系统策略,本篇只固定选择规则来观察安装边界。

控制与转发是职责划分

控制面负责形成、维护影响报文处理的决定;转发面执行已经可用的决定。RFC 7426 §3.2–3.3提供这种架构术语。职责分离不等于两个部分必须位于不同机器,也不能据此认定每到一个报文就运行一次全网路由计算。

1
2
3
4
5
6
7
8
9
10
候选路径及策略
│ 选择

计算结果(版本 c)
│ 安装

已安装转发快照(版本 f) ← 目的地址查询


下一跳决策

图中的箭头需要具体操作来完成。改变候选集只改变计算输入;算出新结果也只产生待安装内容。转发查询如果仍读取旧快照,就仍按旧快照决策。不能用控制面日志中的新版本覆盖数据面尚未改变这一事实。

在教学程序中,这种分离可以只用两个变量表达,不需要引入线程、消息队列或设备接口。这样能直接验证“计算完成不等于安装完成”的因果,不把异步框架本身变成本篇的新主题。

两次选择发生在不同范围

同一前缀的候选选择与不同前缀之间的最长匹配不能混在一起。前者决定某个前缀采用哪条候选,后者针对一个目的地址决定哪条已安装前缀更具体。

例如候选选择后同时安装了 10.0.0.0/810.6.2.0/24。目的地址 10.6.2.9 同时落在两者范围内,RFC 1812 §5.2.4.3所述最长前缀规则选择 /24,第 07 篇模型采用了这一查询语义。不能因为 /8 的教学成本更小,就在每次查询时绕过已安装的更长匹配。

第 07 篇的 forward 明确拒绝同前缀重复项。本篇的控制步骤正好补上其输入前提:先为每个前缀形成一个选择,再传入转发快照。拒绝重复项是教学实现的限制,不是现实 FIB 永远只能关联一个下一跳的证明。

查表语义与实现结构

最长前缀匹配说明应该得到什么结果,并不规定一定逐行扫描。教学程序遍历候选匹配项,是为了便于阅读和验证,不是对商用路由器查表方法的描述。

Linux LC-trie 文档描述了路径压缩、层级压缩和查询过程中的回溯。它展示一种具体实现如何支持查询语义,不能据此声称所有软件、所有硬件都使用同一种结构。

本篇没有对查表耗时进行基准测试。路由条目数、查询分布、缓存状态、实现结构以及运行环境都会改变测量问题;一个小列表的运行时间不能回答硬件转发容量。即使后续换用另一种结构,也应先检验匹配结果相同,再讨论相应环境下的成本。

安装返回值能证明到哪一步

安装接口可以证明某个接收方接受了操作,但它是否意味着硬件已经开始使用新状态,需要查实现契约。调用成功、内核状态更新、硬件完成更新和实际报文采用新路径,是不同观察点。

FRR Zebra 文档的 --asic-offload 说明明确区分 Linux 初始安装确认与 ASIC offload 完成:初始确认本身不包含硬件卸载是否成功的信息。这里引用的是当次查阅的官方滚动文档,没有在本次实验中部署 FRR 或 ASIC。

因此,排查一次路由变更时,应把“希望安装什么”与“查询实际读到了什么”对应起来。若仅有接口返回值,结论就止于接口契约允许的范围。若要证明业务恢复,还需真实报文及应用结果,不能用路由表截图替代。

可复现的快照实验

实验复用第 07 篇的 RoutePacketforward,用同一个目的地址和 TTL 查询不同阶段。这里的“报文”是 Python 数据对象,没有 IP 序列化、网卡发送或 ICMP 回包。

初始候选含 A、B,先计算并安装第一版。随后撤销 A,计算新的选择,但故意暂不安装。第三次查询才使用新安装版本。最后移除所有候选并安装空表,检查结果是否变成不可达,而不是错误地保留上一个下一跳。

这个撤销动作只改变教学输入,不模拟链路检测时间,也不等于拔掉了真实端口。计算期间仍查到 A,是模型中旧快照继续存在的结果;它没有证明真实路由器在故障期间一定以相同方式处理旧状态。

运行结果与输入限制

附件 control.py 通过相邻目录导入第 07 篇的 forwarder.py。在博客源仓库中保留两篇同名素材目录即可运行;单独下载时,需将第 07 篇的 forwarder.py 放在 control.py 旁边。只下载一个驱动文件不包含它依赖的转发实现。

1
PYTHONDONTWRITEBYTECODE=1 python3 source/_posts/2026-09-19-计算机网络10-路由器内部怎样转发/control.py

默认输出保存为 result.json,包括各阶段候选集、计算结果、安装快照与查询决策。所有阶段查询同一个目的地址 10.6.2.9,输入 TTL 为 64。

阶段 计算版本 安装版本 查询结果
A、B 均存在,第一版已安装 1 1 下一跳 192.0.2.1
撤销 A,仅计算第二版 2 1 仍为 192.0.2.1
第二版已安装 2 2 下一跳 192.0.2.2
候选全撤销,安装空表 3 3 network-unreachable 决策

前三个转发结果的输出 TTL 都为 63。另一个 TTL=1 的输入得到 time-exceeded 决策;这里没有真正发出 ICMP。变更路由来源没有取消转发器原有的 TTL 检查。

程序要求成本非负,默认演示要求 A 的成本小于 B。正常运行还断言同一前缀最低成本并列和候选名称重复会被拒绝。本模型选择显式拒绝歧义,没有实现 ECMP,也没有隐藏一个按名称排序的决胜规则。命令行参数可以用 --cost-a 0 --cost-b 1 验证零成本合法;负数、相等成本和非整数输入返回参数错误。

“安装”在此处是把不可变快照赋给查询使用的变量。它覆盖整张教学表,因此最后的空快照移除了原有前缀。若实际系统采用逐项增量更新,还需要处理删除、部分成功、重试及版本关系,本实验没有为这些机制提供验证。

验证边界

本次运行证明的是固定输入下的候选选择、快照安装与转发决策关系。没有运行路由守护进程,没有修改系统 RIB/FIB,没有内核接口确认,也没有 ASIC 或真实报文路径。模型中人为保留旧快照的阶段不是测得的设备更新时间。

版本号是实验标签,不构成真实更新协议。程序没有线程、锁、乱序消息或多设备部署,不能据同步赋值推导内核更新原子性、并发读写安全或者全网同时切换。某台设备安装新路由也不能单独证明整条路径已收敛;多节点消息传播与临时环路留到第 11 篇。

练习

第一题:计算版本已经是 2,安装版本仍为 1。目的地址未变,查询依旧返回 A。只根据这些事实,能否判断最长前缀实现有错误?

校验:不能。先对照已安装版本 1 的内容;如果其中该前缀仍指向 A,转发器可能完全符合当前输入。需要更新安装状态,或者修复计算到安装之间的处理,不能先改查询算法。

第二题:候选 B 被撤销后,程序只安装其他前缀的新项,没有删除旧的 /24。对匹配这个 /24 的目的地址,会发生什么?

校验:若旧项仍在已安装表里,最长前缀查询仍可能选它。撤销要求最终安装状态不再包含该项。完整快照替换与增量删除是不同更新方式;采用增量方式时不能只处理新增和修改而漏掉删除。

参考资料