你的路由行被拒绝了,而你找到的建议却让你开启 IP 转发。这两件事处在问题的不同阶段。转发决定主机能否在接口之间移动数据包;它对你要写的目的地是否是合法网络没有意见。先修正前缀,因为转发救不了互相矛盾的地址和掩码。

解析器究竟在抱怨什么

写成地址加掩码的目的地必须描述一个网络,而网络从归零的主机位开始。当你提供的地址在主机部分仍有取值时,这对组合在内部就是矛盾的:172.20.130.0 因为以零结尾而显得整齐,但第四级八位组里的零并不是唯一要紧的东西。持有游离位的是第三级八位组。

工具处理这种矛盾的方式各不相同。严格解析器会拒绝该行并指出主机位。宽松解析器会把地址掩掉并安装你并未要求的东西,这更糟,因为配置现在路由了一个你从未打算使用的范围。Python 的 ipaddress 模块直接给出选择:严格网络解析会报错,而 strict=False 会把主机位掩掉。你的客户端可能严格、宽松,或在不同版本间不一致,所以要读消息而不是相信习惯。

手动演算这个例子

假设一条隧道需要用 /20 掩码到达 172.20.130.0。/20 从第三级八位组借走四位,因此该八位组只能按 16 的步长计数:0、16、32,一直到 240。数值 130 落在 128 与 143 之间,这意味着包含它的网络从 172.20.128.0 开始,而不是把最后一个八位组清零后的 172.20.130.0。

写出来,这个块从 172.20.128.0 延伸到 172.20.143.255,共含 4,096 个地址。把同样的值交给非严格解析器,就能看到它会替你做的替换:

$ python3 -c "import ipaddress; n=ipaddress.ip_network('172.20.130.0/20', strict=False); print(n, n.num_addresses)"
172.20.128.0/20 4096

确定你真正想要的是哪个目的地

被拒绝的行通常藏着三种意图之一,而只有你能选。如果隧道需要整个块,就写 172.20.128.0/20,即规范网络地址。如果隧道只需要 130 这一段,那掩码就是错的,172.20.130.0/24 才是诚实的写法。如果目标是一台机器, IPv4 主机路由就是 /32, 其中不涉及任何掩码运算。

这几种写法授权的流量大小各不相同,这正是不要一直放宽掩码直到抱怨停止的真正原因。/16 几乎在任何地方都能接受该行,并把流量发往你从未打算经隧道路由的目的地。写出最符合本意的最窄前缀,然后用你预期的下一跳去确认它,而不是用解析器的沉默。

为什么转发从来不是解决之道

被拒绝的路由根本到不了转发路径,所以切换 net.ipv4.ip_forward 改变不了结果。 这个设置要到后面、主机必须在接口之间传递数据包时才重要,在路由表正确之前打开它,只会改变下一步失败的是哪个阶段。

另外两件事常在同一点被归咎。路由可能被接受,却仍然输给更具体的路由;防火墙规则也可能在正确的查找之后丢弃流量。把阶段分开:语法、安装、选择、过滤。在错误的阶段工作会显得很有成效,因为每次改动都会重启点什么。

检查已安装的路由以及它胜出的路径

客户端接受该行后,看看它安装了什么,以及内核为块内一个代表性地址选择了哪条路由。如果安装的前缀与你写的不一致,就是某个规范化器替你做了主。

既要查询块内的成员地址,也要查询块外的一个地址,这样比较才有意义,而不是只得到一行令人安心的输出:

ip -4 route show 172.20.128.0/20
ip -4 route get 172.20.140.10
ip -4 route get 172.20.200.10

如果前缀正确,第三次查询应当落在隧道之外;若没有,你构建的路由就比想象中更宽。在触碰下一个设置之前,记下前缀本来要覆盖什么,因为能解析的路由和能工作的路径仍然是两回事。

相关文章