症状总是同一个形状:某台主机本来工作正常,随后它作为客户端连上 VPN,现在 LAN 客户端仍能到达它,但它的回包消失了。在主机上抓包会发现回包从隧道设备出去,而不是从 LAN 接口。隧道本身没有坏,需要修的是一条路由,而不是重连。请依次做完下面四项检查,遇到第一个不通过的就停下。

回包是新的数据包,不是路径的逆流

先放弃“回包沿请求走过的原路返回”这个想法会很有帮助。路由是在发包主机上按数据包、按方向逐个决定的。从隧道内 10.64.0.23 来的请求不会留下任何回程路径;主机回包时,那个包会作为新包重新查表,从胜出的路由出去。

这就是为什么同时对外提供 LAN 服务的机器上跑全隧道客户端会造成如此费解的故障。客户端装了一条默认路由,或者某些客户端偏好的两条半默认路由,于是发往 LAN 客户端的回包现在匹配到了隧道。它被送往服务商,而服务商没有理由转发,于是 LAN 客户端看到超时,尽管主机本身活着并在本地应答。抓包能把这一点看得很清楚,路由表则解释了原因。

阅读后文时请记住两个网段。一个是远端客户端来源的 VPN 地址池,本例中为 10.64.0.0/24。另一个是必须继续工作的 LAN,172.16.24.0/24,其网关在 172.16.24.1。故障在于两者之间的流量被默认路由决定了。

用抓包和查表证实

动手之前先确认回包会走哪个设备。查表比抓包快,而且它回答的正是内核在构造数据包时会回答的那个问题。

ip route get 10.64.0.23 from 172.16.24.10
tcpdump -ni any -c 6 icmp

分两段读这条回答。如果它写着 dev tun0,主机就会把回包封装后交给服务商,LAN 客户端永远看不到。如果它写着 dev eth0 但客户端仍然超时,说明路由已经对了,包是在后面被丢掉的,那是另一个问题、另一种修法。

同一步的抓包能解决 ping 解决不了的疑问。回显应答到达 LAN 客户端只能证明某条路通,不能证明走的是哪个接口,所以要在主机本身上抓包,看帧从哪里出去。同一网段里的打印机照常应答,因为它的直连路由比隧道的默认路由更精确。端口转发也一样失败,因为选接口的是路由表,不是连接。

装上比隧道更精确的最窄路由

起决定作用的是前缀长度而不是度量值,所以经由 LAN 网关、指向客户端地址池的路由一定胜过 VPN 客户端装的任何默认路由。不必去动度量值,默认路由可以原样留在客户端放的位置。

ip route replace 10.64.0.0/24 via 172.16.24.1 dev eth0
ip route get 10.64.0.23 from 172.16.24.10

用 replace 而不是 add,因为重新连上的客户端已经装过自己的条目,第二次 add 会报“路由已存在”,看起来像权限问题。Windows 主机上的等价写法是 route add 10.64.0.0 mask 255.255.255.0 172.16.24.1 -p,其中 -p 让条目跨重启保留,因为隧道重新起来时客户端可能会重建路由表。在较新的 Windows 主机上,受支持的等价写法是 New-NetRoute -DestinationPrefix 10.64.0.0/24 -NextHop 172.16.24.1 -InterfaceAlias Ethernet -PolicyStore PersistentStore,旧的 route 命令作为遗留形式保留。

有一个细节决定这条路由是正确还是仅仅看似合理:下一跳必须是真正拥有回客户端路径的那台路由器。把路由指向一个本身要靠隧道到达的地址,会形成环路,表现为时断时续的丢包而不是干净的失败。落笔之前先确认下一跳在 LAN 网段里。

重连之后仍然保住回程

手工添加的路由最多撑到下一次重连。如果客户端地址池是动态的,就把它钉在服务端配置里,让你路由的前缀始终是客户端实际拿到的前缀;否则条目指向一个空区间,几周后症状会在没有明显原因的情况下复发。把路由写进客户端自己的 up 脚本是另一个可靠选择,因为它在客户端重写路由表之后才执行。

有状态防火墙仍然可能击败一条正确的路由。如果抓包现在显示回包从 eth0 出去,而客户端依然超时,那就要查主机是否在发包前就把包丢了。严格反向路径过滤以及把 net.ipv4.conf.all.rp_filter 设为 1,会丢弃源地址无法从入口接口回程的数据包,而这恰恰是你刚刚制造又修好的非对称情形;conntrack 状态对端口转发讲述的也是同一个故事。

最后一项检查是大多数人跳过的:在隧道断开再起来之后,把查表再做一遍。四个值应当与你记下的完全一致:目的地址、下一跳、设备、选中的源地址。其中任何一个变了,就说明客户端重写了路由表,修法应该落在它的钩子里,而不是留在 shell 历史里。

相关文章