服务在 VPN 客户端连接的一瞬间消失,通常不是被拦截,而是从错误的接口应答。请求从物理网卡进来,守护进程写好应答,路由表却把这个应答交给隧道,因为隧道已经接管了默认路由。远端客户端始终看不到自己打开的那条连接的应答,于是一直等待,最后报告超时。整个过程中没有任何防火墙规则参与。

症状:请求进得来,应答却从错误的门出去

判断依据是:主机上其他一切正常,唯独入站连接失败。另一个特征是主机可以从局域网访问、却无法从互联网访问,或者主机一直正常,直到隧道重连之后才停摆。每种情形都是去的方向正常、回来的方向不正常,这说明问题在路由不对称,而不是策略。

确认方法是观察应答去了哪里,而不是观察请求怎么进来。

同时在物理接口和隧道设备上抓包。如果请求出现在物理接口、应答出现在隧道上,诊断就结束了。如果应答哪里都不出现,说明服务从未接受这条连接,那是另一项成因不同的调查。

两条默认路由与一次路由决策

安装默认路由的客户端会新增一条度量值优于局域网网关的表项。有些客户端改为安装两条半区间路由,它们更具体地覆盖同一空间,同样能在查找中胜出。两种做法都会让普通出站数据包选择隧道接口。

这个选择只由目的地址决定。内核不会过问数据包从哪里来,除非策略规则这样要求。于是发往公网客户端地址的应答被交给隧道,隧道远端收到一个源地址并不属于它的数据包,便将其丢弃,或者转发到毫无用处的地方。在 Linux 上,严格的逆向路径过滤还可能在服务看到请求之前就把它丢掉,从外面看两者一模一样。这两种症状都源自入站流与其应答之间同一个缺失的关联。

隧道全程行为正确。它被要求承载默认路由,它也确实照做了。

在不破坏隧道的前提下修复回程

存在三种经得起时间考验的修法,选哪种取决于主机自身的流量有多少必须留在隧道里。排除该服务的目的地址或端口改动最小。按源地址或标记做策略路由最精确。源地址转换最粗糙,但两年后最容易解释。

按主机必须做什么来选择,而不是按你最先找到的哪段代码来选择。

排除目的地址适合拥有稳定公网地址的服务;把守护进程绑定到物理地址而不是绑定到所有地址,会让这种排除更安全。按源地址选择路由表的策略规则,既能让主机自身的连接留在隧道里,又能让服务应答经 WAN 网关返回。在 WAN 接口上对回包做地址伪装能达到同样路径,代价是客户端地址会从服务日志里消失。临时改动默认路由是诊断手段而不是配置,因为客户端每次重连都会重写路由。

ip rule show
ip route get 203.0.113.9 from 192.0.2.44

当主机还为它背后的局域网做路由时

再往外一跳,同样的不对称又回来了,而且零件更多。流量从互联网到达路由器,被转发给内网设备,而该设备只有一条回程即隧道,于是应答以运营商自己的源地址送到原来的客户端。连接跟踪随后遇到一个它没见过的流的应答,直接丢弃。而客户端那边什么也看不到。

两处改动可以修好:目的地址转换,让入站连接到达内网设备;以及源地址转换,让该设备的应答看起来来自路由器本身。

在路由器固件里,常见写法是一条策略规则,把端口转发固定在 WAN 侧,而路由器自身的流量走隧道。没有源地址转换,这个应答的地址是外部客户端,内网设备根本没有通往它的路由。要分别测试三件事,因为它们失败的原因不同:从外部进来的连接、主机自己发起的连接,以及名称解析。

相关文章