为什么 CGNAT 后面的 VPN 服务器永远无法接受连接
一个 100.64 地址、一个解析到别处的动态 DNS 名称,以及没有入站握手,其实是同一个问题:运营商 NAT。先确认,再从中继出站。
路由器报告的 WAN 地址以 100.72 开头,动态 DNS 名称解析到另一个公网地址,而这台路由器上的 VPN 服务器从未完成来自外部的握手。端口转发已配置,服务正在运行,从 LAN 发起的测试仍然成功。路由器日志里看不到任何错误。这种组合就是运营商级 NAT,没有任何防火墙规则能在 ISP 持有的地址上打开端口。
确认共享地址只需两分钟,却能避免一周的配置折腾。
指向运营商 NAT 的迹象
把路由器状态页上的 WAN 地址与外部查询报告的地址作对比。WAN 地址落在 100.64.0.0/10 内是常见迹象,私有网段 10/8、172.16/12 和 192.168/16 也说明同样的问题。如果 traceroute 反复显示同一个公网跳点,或者较早的跳点以私有地址应答,说明路由器和互联网之间还有一层转换。有些 ISP 只在客户门户里列出分配的地址,所以要把该值连同 traceroute 一起导出。
更换协议或端口没有用,因为问题在于入站可达性,而不是端口被关闭。
为什么端口转发和动态 DNS 都修不好
端口转发规则映射的是路由器 WAN 接口上的端口,但入站流量根本到不了这个接口:ISP 先做转换,只按自己的表转发允许的部分。把共享地址发布到动态 DNS,只会把客户端指向许多人共用的 ISP 网关,所以域名能解析,握手仍然失败。hairpin 测试能成功,是因为路由器从内部应答自己的规则,UPnP 也有完全相同的盲区。
重新配置客户端也没用,因为故障在任何客户端设置被读取之前就已经发生。
用带显式路由的 VPS 做中继
在前面放一个公网地址:租一台能承载流量的最小 VPS,然后让家里的路由器向外拨号连它。家庭网络发出的 UDP 能穿过运营商 NAT,于是隧道从内部建立,VPS 成为可到达的端点。家庭一侧变成拥有固定隧道地址的客户端,而不是服务器。
在 VPS 上添加针对该客户端的目录项,让服务器知道哪个对端拥有家庭子网,并添加匹配的内核路由。
# /etc/openvpn/server/ccd/home-site.conf ifconfig-push 10.8.0.10 255.255.255.0 iroute 192.168.50.0 255.255.255.0 # server.conf client-config-dir /etc/openvpn/server/ccd route 192.168.50.0 255.255.255.0 push "route 10.8.0.0 255.255.255.0"
iroute 行告诉 OpenVPN 哪个客户端接收家庭子网的流量;route 行把该子网放进 VPS 的内核路由表,数据包才可能进入隧道。对于要发布的服务,再添加一条目标 NAT 规则,把公网端口转发到 10.8.0.10,并启用 net.ipv4.ip_forward。
从外部测试路径
逐跳验证公网一侧,因为隧道成功并不证明服务可达。VPS 上的监听状态和抓包能区分“什么都没到”和“到了又被丢弃”。
ss -lunp | grep 1194 tcpdump -ni any udp port 1194 nft list ruleset | grep -A4 dnat # from a phone on mobile data, not the home Wi-Fi nc -vz -u 203.0.113.10 1194
如果抓包显示手机发来的数据包却没有回应,下一步要查服务器配置或防火墙。如果抓包什么都没有,客户端仍在解析或发往错误地址。
值得先比价的替代方案
向 ISP 申请独立 IPv4 地址是最短路径,通常只需少量月费。当 VPN、客户端网络和 DNS 记录都支持时,原生 IPv6 能消除两端的转换。在家庭网络内放一台子网路由器做 mesh 覆盖,也能实现同样的中继,代价是要依赖协调服务。
如果需求是稳定的入站访问、每条规则都要掌握在自己手里,就选中继。如果预算允许、目标只是不再操心这件事,就选 ISP 地址。