把端口转发到位于路由器自身 VPN 客户端之后的服务器,失败的方式看起来像防火墙问题,因为请求明明到达了。服务端记录了连接,客户端在等待,却什么都没有回来。目的地址转换完成了它的工作;真正丢失的是回程数据包。路由器上一条有选择的源地址 NAT 规则就能解决,而有趣之处在于决定这条规则能收得多窄。

为什么回复走了另一条路

端口转发只改写数据包要去哪里,从不改写它从哪里来。互联网客户端的地址在整个隧道中一直留在源字段里,所以远端服务看到的是一个陌生人,只能按它唯一知道的方式回应:经由自己的默认网关。那个网关是对侧的 VPN 集中器,它没有通往任意公网地址的回程路由。

在隧道接口上抓包,不对称一目了然:请求包出去,进来的什么都没有。转发链放行了流量,隧道也把它送了过去,所以丢包发生在远端主机已经选好回程之后。

一条窄到可以信任的源地址规则

让路由器成为远端主机回复的源。在这个布局里,WAN 地址是 203.0.113.24,LAN 是 192.168.88.0/24,路由器的隧道地址是 172.20.5.1,服务在 172.20.5.40:8443 上应答。下面的规则匹配的是目的地址转换之后的包,所以它写的是转换后的地址而不是公网地址。规则里没有任何一项依赖客户端地址,这正是要点。

iptables -t nat -A POSTROUTING -o tun0 -p tcp -d 172.20.5.40 --dport 8443 -j SNAT --to-source 172.20.5.1

那一行里的每个匹配项都是为了把改写限制在一条隧道上的一个服务上。去掉接口或端口,你改写的流量就会超出问题所需,定向修复也就这样变成了下一个人眼里的谜团。

什么时候全量伪装才是诚实的做法

如果路由器把许多服务都转发进同一个 VPN 子网,逐服务规则就会变成没人维护的清单。对离开隧道接口的一切做伪装更简单也更可预测,代价是对侧每台主机都看不到原始客户端。如果那边不用日志做归属判断,就选它;如果要用,就保留窄规则。

两种做法的失败方式也不同。缺少窄规则会弄坏一个服务;匹配过宽的全量伪装可能悄悄改写本来已经正常的流量,包括路由器自己的出站连接。

发布之前先核对计数器

从不匹配的规则在数包之前和正常工作的规则一模一样。在任何客户端连接之前,先带着字节计数器读一遍表,然后从非 LAN 的网络连一次再读。两次读数才能把猜测变成事实。只有第二次读数动了,才发布这条转发。

iptables -t nat -L POSTROUTING -v -n --line-numbers

真实连接之后源规则计数仍为零,说明匹配错了,通常是因为目的地址或端口与转换后的包对不上。如果计数在涨,客户端却仍然超时,说明回程已经修好,是别的东西在丢回复,接下来去看对侧的转发策略。从 LAN 内部访问的 hairpin 是另一个问题,需要它自己的规则,而不是把这条规则放宽。

相关文章