开始前,会这些就够了
- 知道IP地址用于标识网络地址
- 会运行Python文件
- 了解二进制的一位是0或1
- 需要补基础时:先看一次网络请求经过哪些层
遇到不熟悉的词,先看这 1 个解释
- 网络前缀长度
- 地址后面的斜杠数字说明,开头有多少个二进制位固定为网络部分。IPv4 的
/24固定前 24 位;/32固定全部 32 位,表示一个具体地址。
假设目的地址是 10.20.30.7,路由表里既有整个园区的 10.0.0.0/8,又有实验室的 10.20.30.0/24,还有这台机器自己的 /32 条目。它们都包含目的地址,仅仅找到第一条能匹配的路由还不够。
在本文选定的单张 IPv4 转发表里,我们先找出所有包含目标的网段,再保留前缀最长的那个。实验只处理内存中的虚构路由表,不读系统配置,也不会改变你的网络。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- longest_prefix.py:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B longest_prefix.py解压到独立练习文件夹,在含 longest_prefix.py 的目录打开终端运行。静态 IPv4 单表的最长前缀教学模型,不发送网络包,不读取或改变系统路由;拒绝重复网段,未实现等长路由、metric、ECMP、策略路由、协议选路或硬件转发。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
同一个地址,究竟选哪条路?
先查哪些网段包含目标地址,再在这些候选中选前缀最长的一条。取消更具体的路由,观察它会退回哪一层。
选择结果:10.20.30.7/32 → single-host(指定主机)
匹配的候选中,32 是最长的前缀长度。
| 网段 | 是否启用 | 包含目标 | 决定 |
|---|---|---|---|
| 0.0.0.0/0 | 是 | 是 | 匹配,但前缀较短 |
| 10.0.0.0/8 | 是 | 是 | 匹配,但前缀较短 |
| 10.20.0.0/16 | 是 | 是 | 匹配,但前缀较短 |
| 10.20.30.0/24 | 是 | 是 | 匹配,但前缀较短 |
| 10.20.30.7/32 | 是 | 是 | 选中 |
这是固定 IPv4 路由表的线性查找模型,不读取或修改系统路由。这里只比较一组已可用路由的最长前缀,不处理策略路由、路由学习、同长候选的取舍或多路径负载分担,也不会测试目标能否连通。
斜杠后面的数字,表示前面固定了多少位
IPv4 地址有 32 位,点号分成四组,每组占 8 位,写成十进制时取值为 0 到 255。10.20.30.0/24 表示前 24 位固定,后 8 位可以变化;因此它涵盖 10.20.30.0 到 10.20.30.255。
10.20.0.0/16 固定得更少,范围更大。/32 固定全部 32 位,只覆盖一个地址;/0 不固定任何位,可作为默认匹配。前缀也可以切在一组内部:例如 192.0.2.64/26 固定前三组的 24 位和最后一组的前 2 位。
64 的 8 位写法是 01000000,固定开头的 01,余下 6 位从 000000 变到 111111,所以最后一组的范围是 64 到 127。
这里的长短是二进制前缀位数,不是字符串有多少个字符,也不是地址看起来有几段相同。本文用标准库 IPv4Network 判断归属,避免手写字符串 startswith 把相似文本误认成同一网段。
先判断能否匹配,再比较谁更具体
实验表依次为 /0→default、10.0.0.0/8→campus、10.20.0.0/16→building、10.20.30.0/24→lab、10.20.30.7/32→single-host。这些名字只是下一站标签,并非真的网关IP。目标 10.20.30.7 五条都匹配,最长的是 /32;10.20.30.8 不匹配那条 /32,所以选 /24。
这让广泛规则与局部例外能够同时存在:默认路由处理没有更具体条目的地址,实验室网段处理自己的范围,单机条目还能覆盖其中一个地址。最长前缀规则并不表示道路更短或时延更低,前缀长度与路径的跳数是两回事。
第一条能匹配就返回,为什么会选错?
本例把默认路由放在第一条。若查询器遇到匹配就立即返回,所有地址都会被 /0 接走,再具体的条目都没有机会参与。这就是代码里 wrong_first_match 输出 0.0.0.0/0 的原因。
正确实现要比较全部匹配条目的 prefixlen。把路由列表重新排列不应改变结果,除非你另外定义了等长条目的选择策略。实验直接枚举五条路由的 120 种排列,对每种排列都查询五个目的地址。
完整程序检查五层匹配、回退和输入边界
保存为 longest_prefix.py,执行 python -B longest_prefix.py。输出中五个地址分别选中 /32、/24、/16、/8、/0。随后移除单机 /32 条目,再查 10.20.30.7,此时回退到仍然匹配的 /24。移除默认条目后查询 192.0.2.1,则返回 None,表示这张模型表没有路由。
程序还拒绝 10.20.30.7/24 这种带主机位的网段输入,以及同一网段被重复配置的情况。前者使用 strict=True 提醒你明确网络地址,后者明确把等长路由策略留在本篇范围之外。总计执行 605 项核对。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""IPv4 longest-prefix teaching lookup; never reads or writes OS routes."""
import itertools
import json
from ipaddress import IPv4Address, IPv4Network
def build_table(entries):
table = []
networks = set()
for cidr, next_hop in entries:
network = IPv4Network(cidr, strict=True)
if network in networks:
raise ValueError("duplicate prefix: equal-prefix route policy is outside this model")
networks.add(network)
table.append((network, next_hop))
return table
def lookup(table, destination):
address = IPv4Address(destination)
matches = [(network, hop) for network, hop in table if address in network]
if not matches:
return None
network, hop = max(matches, key=lambda route: route[0].prefixlen)
return {"prefix": str(network), "next_hop": hop}
def main():
entries = [("0.0.0.0/0", "default"), ("10.0.0.0/8", "campus"),
("10.20.0.0/16", "building"), ("10.20.30.0/24", "lab"),
("10.20.30.7/32", "single-host")]
expected = {"10.20.30.7": "10.20.30.7/32", "10.20.30.8": "10.20.30.0/24",
"10.20.9.8": "10.20.0.0/16", "10.9.8.7": "10.0.0.0/8",
"192.0.2.1": "0.0.0.0/0"}
table = build_table(entries)
checks = 0
for permutation in itertools.permutations(entries):
reordered = build_table(permutation)
for address, prefix in expected.items():
assert lookup(reordered, address)["prefix"] == prefix
checks += 1
assert lookup(build_table(entries[1:]), "192.0.2.1") is None
checks += 1
for invalid in [[("10.20.30.7/24", "bad")],
[("10.0.0.0/8", "a"), ("10.0.0.0/8", "b")]]:
try:
build_table(invalid)
raise AssertionError("invalid table accepted")
except ValueError:
checks += 1
smaller = build_table([row for row in entries if row[0] != "10.20.30.7/32"])
fallback = lookup(smaller, "10.20.30.7")
assert fallback["prefix"] == "10.20.30.0/24"
checks += 1
assert lookup([], "10.1.2.3") is None
checks += 1
report = {"checks_passed": checks,
"results": {address: lookup(table, address) for address in expected},
"after_removing_host_route": fallback,
"wrong_first_match": str(next(network for network, _ in table
if IPv4Address("10.20.30.7") in network)),
"boundary": "static IPv4 teaching model; no ECMP, policy routing, metrics or packets"}
print(json.dumps(report, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中执行 python -B longest_prefix.py。2026-10-17 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。静态 IPv4 单表的最长前缀教学模型,不发送网络包,不读取或改变系统路由;拒绝重复网段,未实现等长路由、metric、ECMP、策略路由、协议选路或硬件转发。
运行后对照结果
{
"checks_passed": 605,
"results": {
"10.20.30.7": {
"prefix": "10.20.30.7/32",
"next_hop": "single-host"
},
"10.20.30.8": {
"prefix": "10.20.30.0/24",
"next_hop": "lab"
},
"10.20.9.8": {
"prefix": "10.20.0.0/16",
"next_hop": "building"
},
"10.9.8.7": {
"prefix": "10.0.0.0/8",
"next_hop": "campus"
},
"192.0.2.1": {
"prefix": "0.0.0.0/0",
"next_hop": "default"
}
},
"after_removing_host_route": {
"prefix": "10.20.30.0/24",
"next_hop": "lab"
},
"wrong_first_match": "0.0.0.0/0",
"boundary": "static IPv4 teaching model; no ECMP, policy routing, metrics or packets"
}它解释的是一次表内查询,不是整台路由器
真实系统可能先根据策略决定查哪张表,也需要由路由协议和管理规则决定哪些候选进入转发表;同样具体的路由还可能比较其他属性或进行多路径转发。本例没有实现这些步骤,所以不能把这个几十行查询器称为完整系统路由算法。
实验也不检查下一站是否可达、接口是否工作、MTU 是否合适,更不测量时延。一次最长前缀匹配成功,只说明在给定表里选到了一个条目;它不能证明真实数据包已经送达。RFC 1812 是 IPv4 路由器的经典规则来源,本文只取其中基本匹配与最长匹配这一层。
理解它之后,排障时该看什么?
当某一小段地址总走不同出口,先找是否存在覆盖它的更具体路由;不要只盯着默认网关。更具体条目一旦撤掉,下一次查询可能落到较宽的条目,这能解释某些局部例外消失后的路径变化。
本篇线性遍历 R 条路由,每次查询要检查至多 R 次,适合把规则写清楚。生产设备可能使用不同数据结构和硬件;这次验证的是选择结果,不是其吞吐量或内部实现。
记住这三点
- 最长前缀是在已匹配条目里选固定地址位数最多的那个。
- 默认路由不会压过更具体的匹配,列表先后也不应代替这个规则。
- 选到路由、真实可达和路径更快,是三件需要分别验证的事。
别把例子用过头
- 把字符串前缀匹配当作IP网段匹配。
- 一看见默认路由匹配就返回,漏掉更具体条目。
- 把单表教学模型当成涵盖策略路由、多路径与协议选路的完整操作系统实现。
自己试一小步
在实验室 /24 里增加一条 /25 例外,会影响哪些目标?
- 新增 10.20.30.128
/25,给它一个新的下一站标签。 - 先在纸上预测 10.20.30.127、10.20.30.128、10.20.30.200 和原有单机地址的查询结果。
- 核对边界两侧的选择,并测试只删除
/25时如何回退。
做到这些,就算完成
- 解释
/25的边界来自二进制位,而非字符串长度。 - 保留
/32单机例外并考虑它与新条目的关系。 - 保持本地模型,不执行系统路由修改;本节不提供结果答案。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- RFC 1812:IPv4 下一跳选择 (新标签页)↗
5.2.4.3 中 Basic Match 与 Longest Match 的先后规则,以及其后仍可能有其他筛选。
- RFC 4632:CIDR 与最长匹配 (新标签页)↗
CIDR 聚合背景和按最长匹配转发的规则。
- Python 3.14:ipaddress 网络对象 (新标签页)↗
IPv4前缀含义、strict主机位校验与地址是否属于网段的容器运算。