服务器更换 IP 后,SNAT 规则仍指向旧地址
隧道能连上,但每个被转发的数据包都会卡住,因为转换规则仍在改写成一个主机早已不再持有的地址。
服务器搬到了新地址,客户端仍能连接,可它们发出的东西一件也到不了互联网。这个割裂正是线索:监听套接字和端口转发熬过了这次搬迁,所以隧道能起来,而 应用于转发数据包的源地址转换 仍指向一个主机早已不再持有的地址。发往这些被改写的数据包的回复,会去往一台已经不存在的机器。
连接与转发在不同的地方失败
连接建立取决于客户端早已知道的端点:公网地址、它前面的端口转发,以及内部的监听者。转发流量则取决于数据包进入隧道之后做出的第二个决定——主机改写自己的源地址,好让回复找得到回程。
地址变更只会打断第二个决定,所以症状读起来就是"能连上却没有网络"。这也解释了为什么客户端那一侧惯常的检查看起来都正常:握手完成了,客户端拿到了地址,连 ping 服务器的隧道地址都可能得到回应。故障明明在对面,时间却花在客户端上,所以规则表才是第一个该看的地方。
改之前先读规则和它的计数器
带计数器列出转换规则,把配置的源地址和主机实际持有的地址对比。被改写成死地址的规则通常只在入方向显示流量,因为数据包到达它并被转换,回程方向则什么都没有。
sudo iptables -t nat -L POSTROUTING -n -v --line-numbers
ip -4 addr show
趁还在那里,检查三个字段:源匹配里的地址池、规则若指定了出口接口、以及防止隧道间流量被改写的排除项。这个排除项在编辑时很容易丢掉,一旦丢掉,对端之间的流量看起来能用,回复却走了一条奇怪的路径。
如果计数器停在零,说明数据包从未到达这条规则,地址不是当前的问题。去链上更早的位置找一条竞争的规则,这通常是有人新增了替换规则而没有编辑原来那条造成的。把规则编号和一分钟前记下的输出对比。顺序是配置的一部分,不是装饰性细节。
替换源地址,或者干脆不再指定
当主机拥有稳定地址时,就地替换这条规则,而不是再追加一条。按规则编号替换能保住链上其余部分所依赖的顺序。
sudo iptables -t nat -R POSTROUTING 1 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to-source 10.151.0.40
当地址是动态分配时,完全不指定源地址,让内核从出口接口选择当前地址。这种写法不需要编辑就能扛过下一次搬迁,代价是看不出究竟用了哪个地址。
sudo iptables -t nat -R POSTROUTING 1 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -o eth0 -j MASQUERADE
确认 IPv4 转发仍然通过受管配置开启,然后用发行版自己的机制把规则持久化。只在运行中的内核里做的改动会在下次重启时回来,通常发生在没人把改动和维护窗口联系起来的时段。
把替换做错的两种方式
把地址池转换成服务器的隧道地址而不是它的 LAN 地址,回复会直接回到隧道里,被路由给一个从未请求过它们的客户端。连接卡住的样子很像路由环路,因为它就是环路。
在旧规则上再叠一条转换是另一种错误。第一条匹配的规则获胜,所以过期条目会一直处理数据包直到被删除,新规则的计数器停在零,所有人却都在怪替换。新增任何东西之前先删掉重复项。然后再读一遍列表,确认恰好只有一条转换与地址池匹配。
如果隧道还承载 IPv6,就在另一套协议栈上把整套检查重做一遍。没有转换、也没有路由前缀的 IPv6 默认路由同样会无声失败,而且很容易忘,因为它上面的一切看起来都一样。
入站服务需要方向相反的转换
VPN 客户端上需要从外部访问的服务,需要的是目的地址转换而不是源地址转换:公网端口在进入之前被改写成客户端的隧道地址。之后转发路径还需要一条显式允许该流量的规则。
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.9.8.2:8080
绝不要转换承载隧道本身的那个端口,因为那次改写会中断让服务保持可达的连接。把隧道端口完全排除在目的规则之外。最后从两端验证:新规则上的计数器、隧道设备上的一次短抓包,以及在尝试公网地址之前对隧道内地址的一次请求。