你在配置文件里给一条路由加了 metric,重新连接后,流量仍然从错误的接口出去。问题很少出在数字本身。metric 只对操作系统已经在比较的候选路由排序,而更具体的前缀或更早生效的策略表会在任何 metric 被读取之前做出决定。

先看配置文件,再看已安装的路由表,最后看选择规则。这个顺序与客户端构建路由的方式一致,也能避免你去调一个根本不参与比较的数值。

按字段顺序读路由行

Community OpenVPN 2.6 客户端在 IPv4 的 route 行上接受四个字段:网络、掩码、网关和一个可选的 metric。网关的位置就是特殊关键字 vpn_gateway 所在之处。它表示这条连接的 VPN 侧下一跳,不是路由表编号,也不是你的物理路由器。漏掉这一点,这一行剩下的部分就会被按错误的理解去读。网络选项参考记录全部四个字段。

对于私有目的地址,这一行可以写成:

route-metric 50
route 172.20.0.0 255.255.0.0 vpn_gateway 30

显式的 30 适用于那条路由,而 50 仍是配置文件中所有未写数字的其他路由的默认值。从左到右读一遍,就足以看出哪个值在哪里生效。

route-metric 是默认值,不是排名

route-metric 为没有自带数字的路由设定回退值。它不会把某条路由抬到别的路由之上,也不会重新排列那些用显式 metric 安装的路由。

当配置文件混用两种形式时,这个区别就很重要。靠近顶部的一行 route-metric 看起来很有权威,也容易被怪罪,而真正承载流量的那条路由可能在三行之下带着自己的 metric。

如果你想让某条路由优先走某条路径,就把数字放在那条路由上。如果你想让生成的配置文件有一个一致的默认值,就用 route-metric,别去动单独的行。无缘无故把两者混在一起,正是一个配置文件最后出现一条没人能解释的路由的原因。

检查客户端安装了什么

重连完成后,问内核,而不是问配置文件。

ip route show 列出客户端添加的路由,而 ip route get 172.20.5.10 回答更有用的问题:这个目的地址实际会走哪条路由、用哪个源地址。如果目的地址不在表里,说明客户端没有安装它。

本地指令、服务器推送或另一个网络管理器都可能添加或删除同一个前缀,最后写入者获胜。工作时把端点路由放在视野里,因为连接需要一条通往远端服务器、且不经过它正在建立的隧道的路径。

为什么更小的 metric 仍然会输

metric 比较的是通往同一目的地的路由。它不会让一个宽前缀变得更具体。当 redirect-gateway def1 生效时,客户端会安装 0.0.0.0/1 和 128.0.0.0/1,这两个前缀覆盖整个 IPv4 空间,比 0.0.0.0/0 更具体。默认路由上的 metric 1 赢不了它们。

同样的道理适用于任何一对重叠的路由。对于位于其内部的地址,/24 胜过 /16,与附加的数字无关,因为查找先选择最具体的匹配,然后才在相同者之间比较 metric。

这就是看起来像 metric 缺陷、其实不是的那部分。改任何数字之前,先检查两个候选的前缀长度。

策略规则先于路由表执行

在 Linux 上,ip rule 决定查哪张路由表。规则按优先级顺序求值,所以更早的表可以在主表被读取之前就返回一条路由。main 里的 metric 赢不了先被查询的那张表里的匹配。

先用 ip rule show,再查询具体的那张表,例如 ip route show table 100,看看那条规则会返回什么。如果 VPN 客户端或容器运行时加了一条规则,答案可能在那里,而不是你一直在读的那张表里。源地址也很重要,因为规则可以按源匹配,并对隧道流量和 LAN 流量以不同方式解析同一个目的地。

验证目的地址,而不是排名

终结争论的测试,是向目标子网内的一台主机发一次查询,再加上对内核所选源地址的检查。如果这一对符合设计,metric 就在履行职责,即使列表看起来不像一份干净的排名。

把 IPv6 和 DNS 排除在这项检查之外。它们由不同的指令配置、由不同的机制解析,一条能用的 IPv4 路由对两者都说明不了什么。一次只改一层,然后重新运行查询。

相关文章