有人给你首地址和末地址,并要求一条 VPN 路由。诱人的做法是选一个能同时包含两端的最短前缀,而这个做法会悄悄加入没人要求的地址。CIDR 块的大小总是二的幂,起始地址也与该大小对齐,因此任意区间通常需要不止一个块。走一遍区间,精确拆分,然后检查紧邻区间之外的地址。

从区间开始,而不是从前缀开始

在写其他内容之前,先把首地址和末地址写下来。这一对地址就是需求;前缀只是它的一种可能表达,有时还是错误的表达。两个数字也比一个掩码容易核对得多。

对齐的区间会合并成一个块。未对齐的区间会拆成多个块,而且拆解是精确的而非近似的。绝不能做的是向外取整并称之为足够接近,因为多出来的地址会继承路由所携带的策略。

两个方向的错误都重要。对于进入隧道的路由,多出的地址会被拉进 VPN。对于绕行例外,多出的地址会跳过它。只显示你所安装前缀的路由表看不出任何一种结果。

第一种情况:区间恰好对齐

取 198.51.100.64 到 198.51.100.127。首地址对齐在 64 个地址的边界上,区间正好是 64 个地址,因此单个 /26 就能完整描述,毫无剩余。

这是简单情形,值得识别,因为这意味着需求很可能出自已经在用前缀思考的人。这也意味着核对很短:确认大小是二的幂,且起点是该大小的整数倍。

文档范围能成为好例子,正因为它不属于任何人。不要把这些地址安装到真实隧道上,也不要仅因为某个范围看起来不寻常就认为它是文档空间。

第二种情况:没有对齐,所以要拆分

现在取 198.51.100.100 到 198.51.100.107。这是八个地址,是二的幂,但 100 不是八的整数倍。单个该大小的块无法从这里开始,因此区间必须由不止一个块覆盖。

精确覆盖是两个 /30 块:198.51.100.100/30 覆盖 100 到 103,198.51.100.104/30 覆盖 104 到 107。总共八个地址,区间之外一个都不多,而且对给定区间来说这种拆解是唯一的。

python3 -c "from ipaddress import ip_address, summarize_address_range as s; print(*s(ip_address('198.51.100.100'), ip_address('198.51.100.107')), sep='\n')"

标准库函数会替你完成算术,ipaddress 文档定义了它返回什么。它不做的是选择网关、决定地址归谁所有,或授予对它们背后任何服务的访问权。

衡量更宽的前缀会捕获什么

在接受捷径之前,先算一下代价。从 198.51.100.96 开始的 /28 覆盖 96 到 111,也就是十六个地址而不是八个。多出的八个是 96 到 99 以及 108 到 111,它们每一个都会继承该路由。

python3 -c "from ipaddress import ip_network as n; w=set(n('198.51.100.96/28')); t=set(n('198.51.100.100/30'))|set(n('198.51.100.104/30')); print(len(w), len(t), sorted(w-t))"

多出八个目标是能写进变更请求的具体数字。它也是决定这条捷径是否可接受的数字,而那是策略问题,不是算术问题。

把算术和发现分开。网络运营商给出的区间会被精确转换。来自变化中的服务的单条 DNS 应答并不能确立该服务完整或永久的范围,而不相关的名字也可能共用一个地址。

安装精确集合并验证边缘

把得到的每个前缀交给拥有 VPN 配置的人,让他们通过受支持的机制安装。如果需求需要两条记录,就保留两条,而不是为了缩短表单把一条放宽。

sudo ip route add 198.51.100.100/30 via 10.90.0.1 dev tun0
sudo ip route add 198.51.100.104/30 via 10.90.0.1 dev tun0
ip route get 198.51.100.101
ip route get 198.51.100.97

最后两条命令正是人们会跳过的。第一个内部地址应当走新路由,而紧邻区间之外的地址不应当。如果两者走同一条路,说明区间在需求与配置之间的某处被放宽了。

记录首地址、末地址以及表达它们的那些前缀。下一位审阅规则的人就能在不重算算术的情况下把需求与配置对照,而精确路由与便利路由之间的差别始终可见。

相关文章