为什么 VPN 客户端背后的服务器会经隧道回包
当回包沿 VPN 默认路由而非到达路径返回时,入站连接就会失败。策略路由或源地址转换可以恢复回程路径。
发往同时运行 VPN 客户端的服务器的入站连接会以非常特定的方式失败:握手到达,守护进程作出响应,而客户端却收不到任何回包。没有任何东西阻塞端口。回包从隧道出去了,因为 隧道占用了默认路由,而内核按目的地址转发回包,并不记得请求从哪里来。仅此一处遗漏就能解释接下来出现的每一个症状。
回包是被路由的,不是被记住的
安装默认路由的 VPN 客户端会添加一条压过 LAN 网关的条目,或者靠度量值,或者把空间拆成两个更具体的半区。出站数据包现在选择隧道接口。内核从不过问被回应的数据包最初从哪里到达。仅此一处遗漏就能解释接下来出现的每一个症状。
正向按目的地址路由,反向也一样。回包被发往一个公网客户端,隧道会乐意把它送出去。远端收到一个源地址并不属于它的数据包,将其丢弃,而原始客户端只能一直等到超时。
严格的反向路径过滤可能会在守护进程读到入站数据包之前就把它丢弃。先检查那个计数器,因为过滤丢弃和路由错误从外面看完全一样。
ip route show default sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.eth0.rp_filter nft list ruleset | grep -A3 'hook forward'
第一条命令显示当前哪条默认路由胜出。第二条报告严格过滤是全局生效还是按接口生效,第三条显示任何可能有关的转发链规则。
保留回程路径的三种办法
把服务目的地址排除在隧道之外是最窄的做法。一条指向 LAN 网关的主机路由,再加一个绑定物理地址而非所有地址的守护进程,就能让那一条流留在隧道外。改动是局部的,也很容易回退。
策略路由是通用答案。一条以源地址或防火墙标记为键的规则会选择默认指向 WAN 网关的路由表,而主机自身的连接继续使用隧道。把优先级写明确,免得这条规则日后被遮蔽。
ip rule add from 203.0.113.25/32 table 100 priority 100 ip route add default via 192.0.2.1 dev eth0 table 100 ip route get 198.51.100.7 from 203.0.113.25
前两条命令为源地址是公网地址的回包搭建备用路径。第三条向内核查询,恰好这个源和这个目的会走哪条路,你就这样确认规则是否真的生效。
源地址转换是第三种选择,也是服务前面已有路由器做 NAT 时唯一还能用的办法。回包必须回到那台路由器,伪装到 WAN 接口正好能做到这一点,同时把客户端从服务日志里藏起来。连上之后手工替换默认路由只是诊断手段,因为客户端下次重连时会重写路由表。
当这台机器也为 LAN 路由时
当主机为身后的设备路由时,同样的故障会在更远一跳处出现。入站连接被转发到一台内部设备,它唯一的回程路径就是隧道,回包便从提供商的地址到达外部客户端。内部设备自始至终都在正常工作。
于是连接跟踪看到一个从未记录过的流的回包,就把它丢弃。目的地址转换把入站连接送到设备,源地址转换让回包看起来像是从路由器本身发出的。
让转发端口留在 WAN、而路由器自身流量走隧道的策略路由,是更干净的等价做法。没有源地址转换时,回包被发往外部客户端,而内部设备根本没有通往该地址的路由。
分离原因的三项检查
做三项检查:从互联网连进这台机器、从它连出一台公网主机、以及一次域名查询。前两项不一致,问题就在回程路径。两个方向的名字解析都失败,那就是另一个故障,只是恰好共用同一条隧道。
先修路由问题,再看名字。把两者混在一起,就是让一台正常工作的服务器替坏掉的解析器背锅。
记下例外指的是哪个接口,并把配置纳入版本控制。针对升级后会被改名的接口写下的规则,今天能通过,下一次重启后就会失败。