浏览器请求 https://203.0.113.10:8443,代理接受连接,而 WireGuard 对端上的服务始终不响应。DNAT 规则本身工作正常,失败的是响应这一环。沿着两次转换跟踪这个数据包,缺失的规则就一目了然。修复方法是在正确的 hook 中再加一条 NAT 语句,而不是新建隧道。

从头到尾跟踪一个请求

请求到达代理的公网接口,源地址是客户端的公网地址,目的地址是代理地址。在 nat PREROUTING 中,DNAT 规则把目的地址改写为对端的隧道地址 10.90.0.2。随后路由决策把数据包从 wg0 发出,转发路径就此完整。

在对端上,数据包现在携带原始公网源地址,因为没有任何环节改写它。服务接受连接,并把响应发往自己的默认网关,也就是对端前面的家用路由器。那台路由器从未见过这条流,没有相应状态,于是要么丢弃数据包,要么把它转换成自己的公网地址,而客户端不会接受这样的响应。

这种不对称就是全部问题:请求经过代理,响应却试图直连。代理的连接跟踪只有在响应经由代理返回时才有效,而只要源地址仍指向原始客户端,响应就无法回到代理。

添加返回转换

把被转发的流的源地址改写 到代理的隧道地址,然后再发往对端。随后服务向 10.90.0.1 回复,代理收到响应,连接跟踪会在数据包返回客户端之前把两次转换都反转回来。

iptables -t nat -A PREROUTING -p tcp -m multiport --dports 8080,8443 -j DNAT --to-destination 10.90.0.2
iptables -t nat -A POSTROUTING -d 10.90.0.2 -o wg0 -p tcp -m multiport --dports 8080,8443 -j SNAT --to-source 10.90.0.1

POSTROUTING 规则刻意收窄:它只匹配转换后的目的地址和隧道接口,因此不会改写恰好从 wg0 出去的其他流量。一条宽泛的 masquerade 规则同样会隐藏客户端地址,但它会对每条流都这样做,并使 NAT 表更难审计。

把规则放在隧道存在的位置

引用隧道地址或接口的规则必须在 wg-quick 配置好隧道之后运行。接口在 PreUp 之前创建,但地址、MTU、DNS 和路由在之后才应用,PostUp 在这一切就位后才运行。把添加放在 PostUp,把删除放在 PreDown。

PostUp = iptables -t nat -A PREROUTING -p tcp -m multiport --dports 8080,8443 -j DNAT --to-destination 10.90.0.2
PostUp = iptables -t nat -A POSTROUTING -d 10.90.0.2 -o wg0 -p tcp -m multiport --dports 8080,8443 -j SNAT --to-source 10.90.0.1
PreDown = iptables -t nat -D PREROUTING -p tcp -m multiport --dports 8080,8443 -j DNAT --to-destination 10.90.0.2
PreDown = iptables -t nat -D POSTROUTING -d 10.90.0.2 -o wg0 -p tcp -m multiport --dports 8080,8443 -j SNAT --to-source 10.90.0.1

删除必须与添加逐 token 对应,包括 multiport 和接口匹配。只要相差一个匹配,删除就不会生效,下次重连又会添加第二条规则。几次重连之后,表里会留下难以归因到某行配置的重复项。

重连后用 iptables -t nat -S 检查结果。如果同一条规则出现两次,说明 PreDown 那一行没有匹配,清理正在悄悄失败。

转发、过滤以及另一种 NAT

永久开启 IPv4 转发,然后在 filter 表中放行被转发的流。DNAT 发生在 FORWARD 链之前,所以过滤规则匹配的是转换后的目的地址,而不是公网地址。如果防火墙由 UFW 管理,请把放行写成路由规则;普通的 host 规则并不覆盖被转发的流量。

即使代理规则正确,另外两层 NAT 仍可能破坏路径。在家庭宽带上,公网地址属于路由器,在其防火墙里打开端口并不等于创建一条把该端口送到代理的端口转发记录。从外面看起来是开着的、却没有任何响应的端口,往往正是缺了这条映射。

在 NAT VPS 上,服务商把公网端口映射到私网地址。映射必须与你公布的端口一致,不一致时的表现与隧道损坏完全相同。修改 WireGuard 配置之前,先确认服务商的映射。

验证返回路径并接受代价

从不在代理 LAN 内的网络连接,然后在连接打开时检查转换对:

conntrack -L -p tcp --dport 8080
tcpdump -ni any tcp port 8080

conntrack 条目应显示原始元组、转换后的元组以及响应方向。如果响应缺失,说明对端没有通过隧道发送,这通常意味着 SNAT 规则没有生效,或者对端自己的防火墙丢弃了这条流。

重启一次以证明 PostUp 会重新应用规则,然后停止隧道并确认 PreDown 已将其删除。断线后仍然残留的死规则,正是之后改动行为不可预测的原因。

最后,接受 SNAT 的代价。服务把所有请求都看作来自代理,因此访问日志、按客户端限速和地址白名单都不再能区分用户。hairpin 场景需要为代理 LAN 内的客户端单独做源地址转换。如果对端需要真实的客户端地址,替代方案是加一条回到客户端网络的路由,工作更多但保留了信息。

相关文章