一条用于实验室打印机的 /24 在两次 ip route show 调用之间消失了,而客户端日志只说网络发生了变更。命令、前缀和你的权限都没有问题。拥有主机路由模型的企业客户端,只要别人改动它,就会重新声明该模型,所以你添加的条目会被当作入侵者删掉。在同一张表里和它对抗是必输的局面。解决办法是彻底不去争那张表,把例外放到监视器从不查看的地方。

监视器解释了路由为什么消失

在重新设计任何东西之前先验证一下:读表、添加前缀、等几秒、再读一次。如果条目不在了,而你并没有输入任何删除命令,那就说明有代理在监视。找到负责这种行为的过程,读它在路由消失前后的日志,因为同一项服务通常也维护隧道、数据包过滤器和解析器配置。为了保住路由而杀掉它,是一笔你在下次重连时一定会后悔的交易。

ip route show
ip route add 192.168.2.0/24 dev enp0s31f6
sleep 5; ip route show

上面的前缀和接口只是占位符。从另一台机器上能用的示例里照抄接口名,是这次演示完全没有结果的最常见原因。

第二张表对监视器不可见

代理只会重建它自己拥有的那张表。给例外单独一张表,再加一条把匹配目标送过去的规则,主表就永远不会变,代理也就没有可纠正的东西。这就是全部思路:一个由别人维护的路由命名空间,和一个只有你动的命名空间。

代价是维护。两个对象可能会逐渐偏离,而规则在列表中的位置,和它所选择的表中的路由同样重要。

先构建例外,再让它胜出

创建表、安装路由,并添加一条为该目标选择这张表的规则。优先级决定你的规则是在客户端插入的规则之前还是之后被检查,而排在后面的规则永远不会被到达,所以这个数字是设计的一部分,而不是装饰。然后问内核它实际会怎么做,这和“你配置了什么”是两个不同的问题。

ip rule add to 192.168.2.0/24 table 110 pref 30010
ip route add 192.168.2.0/24 dev enp0s31f6 table 110
ip route get 192.168.2.50

ip route get 会解析内核将使用的路由,包括设备和源地址。它不打印表标识符,所以要把 ip rule show 和它配合使用,当你需要 确认哪张表胜出 时。两者合起来回答了唯一重要的问题:发往该地址的流量,是不是从你想要的地方出去的?

同一思路的其他形态

一旦不再以目标来思考,这个模式就可以推广。隧道保留默认路由时,本地子网仍然可以访问。连在局域网上的 SSH 服务器的响应,可以从它们进入的接口出去,而不是消失在隧道里。基于接口的规则则可以把从无线接口进来的一切推入隧道,而主机自己生成的流量仍留在 Ethernet 上:

ip rule add iif wlan0 table 10
ip route add default dev tun0 table 10

最后一种情况匹配的是入站接口而不是目标,这是另一种规则类型,也是另一种思维模型。被转发的流量还需要启用 net.ipv4.ip_forward,远端必须把源范围路由回去或做转换;一台机器上的规则永远不会创造路径的另一半。

把规则当作一次性用品

规则和表路由都活在内存里。重连之后,隧道设备可能改名,分配到的地址可能变化,持有旧值的规则会悄无声息地不再匹配。要从一个拥有全部变更的脚本重新应用,然后在信任这个例外之前,重新核对 ip rule show、当前设备名,以及针对真实目标的新的 ip route get。

无论多小心,总有两个边界存在。客户端仍然控制它自己的过滤规则和解析器配置,路由例外对两者都不起作用。如果客户端还删掉了你的策略规则,受支持的路径只有它自己的拆分隧道策略,或者像 OpenConnect 这样允许你管理路由的客户端;给厂商二进制打补丁让它监视器闭嘴,是一项附带完整性校验的维护承诺。

把表号、优先级和目标写进脚本旁边的笔记里。否则下一个人会发现一条压过隧道的规则,却无从知道为什么。

相关文章