计算机网络 07:IP 地址怎样决定下一跳,CIDR、子网、最长前缀匹配与 IPv4 转发
同一个目的地址同时命中默认路由、10.0.0.0/8 和 10.6.2.0/24,应该选哪一条?如果每次都采用表中的第一条,添加一条宽泛路由就可能改变所有细分网络的去向。最长前缀匹配用地址范围的具体程度解决这个问题。
第 06 篇已经观察过默认网关的 ARP。本篇向前一步,推导默认网关为何被选中,再用一个不发包的教学转发器检查路由选择、TTL 递减与错误决策。这里没有部署新的 Linux 路由拓扑,程序输出不能当作真实 ICMP 报文。
前缀表示哪些地址
IPv4 地址有三十二位。CIDR 的 /p 表示前 p 位固定,其余位可以变化,因此一个地址块包含 2^(32-p) 个地址。RFC 4632 §3.1用前缀长度表示地址块,不再依赖旧式 A、B、C 类地址边界。
10.6.2.0/24 固定前三个字节,范围是 10.6.2.0 到 10.6.2.255。这有 256 个地址,不等于在任何接口类型上都有 256 个可分配主机地址;地址块范围和链路上的地址使用规则应分开计算。
以 10.6.2.130/26 为例,最后一个字节的二进制值为 10000010,掩码最后一个字节为 11000000。按位与后得到 10000000,即 128:
1 | |
前二十六位固定,后六位变化,范围大小是 64。这里的 130 可以作为接口地址的一部分描述,但把它直接当作规范化网络前缀的起点会产生歧义。附件使用 Python ipaddress 的严格网络构造:含非零主机位的网络输入会被拒绝,而不是悄悄改成另一个前缀。
常见的“地址数减二”也有适用范围。RFC 3021 §2.1允许点到点链路的 /31 使用两个端点地址。因此 /31 不能机械地算出零个可用端点。/32 则只匹配一个地址,常用于主机路由,不表示一条普通广播子网。
多条路由都匹配时
本篇教学路由表包含五个互相嵌套的前缀。表中下一跳只是模型标签,没有对应到宿主接口。
| 目的前缀 | 用途 |
|---|---|
0.0.0.0/0 |
没有更具体匹配时的默认选择 |
10.0.0.0/8 |
覆盖整个 10 开头的地址块 |
10.6.0.0/16 |
为其中一部分提供更具体路径 |
10.6.2.0/24 |
再细分到一个地址块 |
10.6.2.9/32 |
只匹配单个目标 |
先判断地址是否属于每个前缀,再从匹配者中选择前缀最长的一条。RFC 1812 §5.2.4.3分别讨论 Basic Match 和 Longest Match。前缀长度比较必须在“已经匹配”的集合中进行,不能直接选全表最长的一条。
目的 10.6.2.9 同时属于五个范围,选 /32。目的 10.6.2.10 不在那条 /32 里,却属于 /24,所以选 /24。10.6.3.1 不在 10.6.2.0/24 中,仍可以命中 /16。
1 | |
“前缀长”不是“路径短”或“延迟低”。它只表示匹配的地址集合更具体。路径代价、路由来源和策略属于另外的决策;最长前缀原则不能单独说明为什么某个前缀下安装了这个下一跳。
若两个候选具有相同前缀长度且覆盖同一个目标,它们可能需要优先级或等价路径选择。本篇不实现这类规则,直接拒绝同一前缀的重复项,避免把 Python 列表顺序误当成网络规范。控制面如何形成路由表,留到第 10–12 篇。
默认路由的存在与否
/0 不约束目的地址的任何位,因此它能够匹配所有 IPv4 地址。但它只能在没有更长匹配时胜出;一条默认路由不会覆盖更具体的 /24。
对 192.0.2.1,上述五条路由只有 /0 匹配。保留默认路由时,模型返回默认下一跳;移除默认路由后,才进入没有匹配路由的分支。默认下一跳本身是否可达,是第 06 篇所区分的另一项条件。
这里的无路由对应网络不可达决策,不能与“已经选定直连网络,但某台主机解析失败”混写为同一种原因。IPv4 的 Destination Unreachable 还包括不同代码,本篇仅使用 RFC 1812 §4.3.3.1中的网络不可达含义,不模拟所有错误类型。
TTL 限制继续转发
路由条目可能形成环路。若 A 的某条路由指向 B,而 B 又把同类报文交回 A,仅靠目的地址不会自动结束循环。TTL 为报文继续转发设置限制。
RFC 1812 §5.3.1要求路由器转发时至少把 TTL 减一,耗尽时丢弃。本篇把每次模型转发固定为减一,不模拟路由器内部停留时间;这不等于声称 TTL 的规范历史只定义了跳数。
从 TTL=2 开始,在第一台教学路由器上得到 TTL=1;把该输出交给下一台教学路由器时,不能继续转发,而进入 Time Exceeded 决策。这里两次函数调用表示两个转发步骤,没有经过任何真实接口。
TTL=1 的报文若正是发给当前主机,与需要经当前主机继续转发的情况不同。本篇模型只处理需要转发的输入,没有本地交付分支,不能用它判断一个真实接收端是否应当丢弃发给自己的报文。
真实 IPv4 转发还要处理首部。RFC 791 §3.1定义首部校验和;TTL 改变后需更新相应校验和。附件只改变结构化对象中的数字,没有构造 IPv4 字节串,因而不能称为可上网工作的 IP 转发实现。
丢弃与返回错误分开判断
TTL 耗尽不意味着可以对任意输入都发送一份 ICMP 错误。对 ICMP 错误再产生错误,可能造成无意义的反馈;广播、多播或非首片等输入也有抑制规则。RFC 1812 §4.3.2.7列出禁止错误响应的条件。
附件保留三个教学标签:原报文是否为 ICMP 错误、是否由链路广播接收、分片偏移是否非零。它们由测试输入明确指定,不是从真实报文解析出来。发生 TTL 耗尽或无路由时,只要有这些条件之一,就记录丢弃但抑制错误。
这样得到两个不同结果:原报文是否继续转发,以及是否满足模型中产生错误决策的条件。即使程序记录“应返回 Time Exceeded”,仍没有执行发送,更没有证明错误报文能沿返回路径到达源端。
模型不是完整 ICMP 合规检查器。它不接收任意原始首部,不模拟错误限速、IP 选项和全部无效地址场景,也没有接口配置可用于判定子网定向广播。需要这些语义时,应增加相应输入和规则,不能把现有三个标签当作全部例外。
复跑与对照
附件 forwarder.py 使用 Python 标准库,运行时不创建 socket,不改系统路由,不要求管理员权限。使用 --demo 输出检查结果,完整结果见 result.json。
1 | |
先手算每个目的地的候选集合,再对照程序选中的前缀。只比对一个最终标签不容易发现“先选最长、后判断匹配”的错误,因此样例同时包含 /32、/24、/16、/8 和默认路由各自胜出的目标。
TTL 用例串联两次转发决策,检查第一步输出为一、第二步丢弃。错误抑制用例保持同样的触发原因,只改变一个输入标签。这种对照把“导致丢弃的原因”和“是否生成错误决策”分开,避免将没有错误响应理解成仍在正常转发。
本次在 Python 3.14.4 运行,五个目的地址依次得到以下匹配,所有正常转发用例均把 TTL 从 64 减到 63:
| 目的地址 | 选中前缀 |
|---|---|
192.0.2.1 |
0.0.0.0/0 |
10.9.0.1 |
10.0.0.0/8 |
10.6.3.1 |
10.6.0.0/16 |
10.6.2.10 |
10.6.2.0/24 |
10.6.2.9 |
10.6.2.9/32 |
程序按“输入与重复前缀检查、TTL 检查、最长匹配”的顺序执行。如果 TTL 已耗尽,就先记录耗尽,不再查询路由;这是附件明确选择的模型顺序,不是所有实现的内部流水线保证。每个错误原因各有三个抑制对照,共六个用例。
命令不带 --demo 时,使用空路由表对 --destination 和 --ttl 指定的输入给出单次决策,便于观察不可达与 TTL 耗尽。源地址固定假设为合法单播,报文结构不含可变源地址;输入门禁拒绝多播、回环等不在模型范围内的目的地址。这些限制是教学程序的输入契约,不是完整的 IPv4 地址合法性判定器。
验证边界
本篇完成的是确定性教学模型与前缀手算。模型没有接收、重写或发送真实 IP 报文,没有生成 ICMP 字节,没有校验真实路由器的实现顺序,也没有性能测量。它能验证列出的决策规则在这些输入上的结果,不能证明实际网络的可达性。
第 06 篇的 Linux 证据仍属于那一篇的拓扑,不能因为本篇引用了它,就把这里的 Python 结果升级为真实转发实验。若要验证真实 TTL 或 ICMP,应在隔离拓扑保留入口和出口报文及返回错误,不能只保存一行程序输出。
练习
第一题:在表中增加 10.6.2.128/25,分别为 10.6.2.9、10.6.2.130、10.6.2.100 列出最终前缀。再删除 /32,哪个答案变化?
校验:最初分别选 /32、新 /25、原 /24;删除 /32 后,.9 改选 /24,另两个不变。新 /25 只覆盖最后一字节 128–255,不包含 9 或 100。
第二题:一个需要转发的报文 TTL=1,目标有匹配路由,但它本身是 ICMP 错误。能否因为不应返回新错误,就继续转发原报文?如果模型没有收到任何返回错误,能否确定远端仍在正常工作?
校验:原报文仍因 TTL 耗尽丢弃;错误抑制只改变反馈决策,不恢复转发。没有收到错误也可能源于抑制、限速或返回路径等条件,不能单独推出远端状态。本题是规则推导,没有真实 ICMP 交换。
参考资料
- RFC 4632 §3.1:CIDR 前缀;RFC 3021 §2.1:点到点
/31。 - RFC 1812:§5.2.4.3 下一跳选择、§5.3.1 TTL、§4.3.3.1 不可达、§4.3.2.7 错误抑制。
- RFC 791 §3.1:IPv4 首部字段与校验和。
- Python 3.14 ipaddress:严格网络构造、前缀长度与成员测试。





