两个分离隧道配置、两个私有前缀,以及一台被两者同时主张的主机。症状很少是干脆的故障:某项服务周一还能用,第二台客户端重连后就超时,重新启动之后又能用。客户端连接的先后顺序并不决定结果,客户端界面中行的顺序也不决定。请按顺序执行这些检查,并在第一个不成立的检查处停下,因为只有前面的检查成立,后面的检查才有意义。

1. 写下目的地址和源地址

从失败请求真正要去的精确地址开始,而不是主机名,也不是应用名。把名称解析一次,记录每一个应答,并选出失败流量实际使用的那个。然后记下同一条流量的本地地址,因为源地址会改变哪条策略规则被匹配,从而改变查的是哪张表。

发往多条 A 记录之一的请求可能失败,而相邻地址却成功,这看起来像隧道不稳定,实际上却是两条不同的路径。如果目的地是有多个应答的名称,请在改动任何东西之前逐个测试每个应答。

2. 问内核它选了哪条路由

不要读路由表然后推断答案。用同样的源地址问栈会问的那个问题:Linux 上是 ip route get 10.20.4.10 from 10.8.0.2,PowerShell 中是 Find-NetRoute -RemoteIPAddress 10.20.4.10。输出会指出实际会被使用的路由,而它往往不是读表时你以为的那条。

在匹配的路由之间,通常更具体的前缀胜出:对同时落在两者内部的地址,/24 胜过 /16。长度相同的匹配之间,度量值更低者胜出,而装在已断开链路上的路由会被跳过。两条隧道推送同一个 /24 且度量值相同是另一种情况:内核同时保留两条路由并在其间选择,所以结果会来回变化,而不是遵循客户端连接的顺序。正是这种情形造成了周一能用、周二失败的规律,换一个顺序重连也改变不了它。

3. 检查是否有策略规则改掉了查找

在查路由表之前,内核会先走一遍策略规则。规则可以把来自某个源、某个用户或某个防火墙标记的流量送到完全另一张表,因此主表中胜出的前缀对这条流量可能根本不会被查到。ip rule show 按评估顺序列出它们,而 ip route show table 100 显示被选中的表里到底有什么。

这里有两个陷阱。指向空表的规则会产生一次落到下一条规则的查找,于是这条规则看起来什么也没做。另一个是优先级编号本身:规则按升序读取,内置值是 0 local、32766 main 和 32767 default。编号小于 32767 的规则会在兜底的默认规则之前被查到。编号大于 32767 的规则排在它后面,只有在没有更早的表给出应答时才会到达,而 main 中一条普通的默认路由就阻止了这一点,所以实际上它永远到不了,看起来装好了却什么也没做。如果客户端同时写了一条规则和一条路由,两样都要检查,并在重连之后再次检查,因为被重写的配置可能会追加一条优先级不同的第二条规则。

4. 把路由打平与地址规划重复区分开

有时前缀完全不是问题所在。两个站点都使用 192.168.1.0/24,意味着同一个地址标识了两台不同的主机,没有任何路由能把它们区分开。指向两个应答之一的主机路由只在另一站点不再为同名应答之前有效,随后这个例外就悄悄变成了故障本身。

线索是目的地址在两条路径上完全相同,只有预期归属不同。这是地址规划的决定,而不是路由的调整:给一侧重新编号、把一侧放到做地址转换的网关后面,或者通过一个自行解析名称的代理访问第二个站点。降低默认路由或堆叠主机例外,只是把冲突挪到别处。

5. 确认解析器与路由一致

一个解析到错误私有地址的名称会产生路由冲突的全部症状,却没有任何一个原因。把主机收到的应答与目的端实际使用的地址做比较,并检查是哪个解析器给出的应答。如果隧道下发了某个解析器,客户端应针对走隧道的流量安装它;如果没有下发,主机就会继续询问本地解析器,而本地解析器可能用搜索域或缓存记录来回答。

直接向解析器查询并记下应答的服务器。TTL 很短的缓存应答也能解释为什么重连后的第一个请求与第五个请求表现不同。

6. 重连、检查实际生效的状态,然后测试两条流量

修正之后要重连,并检查客户端实际装了什么,而不是相信配置编辑器。再读一遍规则、表以及由此得到的 ip route get 应答。第二台客户端可能在连接时 重新安装自己的路由 并重新制造冲突,所以状态检查必须放在最后一台客户端连接之后,而不是之前。

测试两条流量而不是一条:过去失败的那个请求,以及本该走另一条隧道的资源。把所有私有流量挪到一条连接上会让第一个测试通过,同时弄坏第二个,只有成对测试才能抓到。把你保留的每个例外连同目的地和理由记录下来,这样下一个人就能分清有意设置的主机路由和遗留的路由。

相关文章