VPN 服务的端口清单在比较页面之间被反复抄录,却常常略去决定它们是否有意义的关键细节:是谁发起连接。一条允许向外访问 TCP 443 的规则,并不能说明别的主机能否连到你机器上的监听端口。在比较任何一款套餐之前,先写下三条彼此独立的需求,并标出你的任务真正需要哪一条。一旦有了这张纸,大多数比较会收缩成一个很短的问题。

先拆开需求,再去看任何端口清单

三条线几乎覆盖所有情况:到达隧道之外的目标、对外发布让其他人能连进来、以及承载隧道本身。每一条线失败的原因都不同,而满足其中一条的服务商政策,可能完全不触及另外两条。把任务归到正确的那条线上只要五分钟,却能省掉日后的争论。

LineWho starts itWhat can block itQuestion for the provider
Outbound destinationYour applicationEgress filter on the exit nodeIs this protocol and port allowed to that destination?
Inbound publishingA remote peerNo mapping, no public address, short lifetimeWhich protocol, which external port, and how long does the mapping last?
Tunnel transportYour client to the gatewayThe access network drops that protocolDoes the client offer a fallback the local network permits?

营销话术会刻意把三条线搅在一起。P2P 许可说的是目标策略,专用地址说的是地址,而“没有端口限制”的说法通常意味着套餐并没有逐条列出。这些话没有一句回答了另外几句。

挑一个具体的流,把它写成一句话:发起方、传输、端口、地址族、方向。夜间把备份上传到远端主机的客户端只是出站。摆在你桌上、朋友会加入的游戏服务器是入站。让两者共用一个隧道,意味着这张纸需要两条线,而不是在两者之间折中。

出站访问失败在目标策略上,而不是隧道上

经过转换网关的出站流量会建立状态。来自内部地址和端口的第一个数据包会建立一条转换表项,回包与它匹配,这就是 RFC 3022 所描述的映射行为。正因如此,浏览器标签页通常在完全没有任何端口申请的情况下就能工作。

仍有两件事会破坏它。出口节点可能过滤它认为可被滥用的目标或端口号,而应用也可能在出口策略从未预料到的端口上监听。要问的是目标对,而不是入站规则;并且 TCP 与 UDP 的答案往往不同。

先测量,再争论。在将要位于隧道后面的那台机器上依次运行,一次关掉隧道,一次打开隧道:

ss -lntup | sort
nc -vz -w 3 203.0.113.10 443
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 5 https://example.com

同样的命令、同样的目标、两种隧道状态,能给运维人员一份可以据以行动的先后对比。把文档地址换成你真正需要的主机;重要的是对比能重复,而不是哪个数字看起来惊人。

入站发布需要映射、地址和有效期

除非公网侧有东西知道把连接送到哪里,否则外部无法主动连到你的监听端口。运维方持有的固定端口预留、客户端通过 UPnP、NAT-PMP 或 PCP 申请的映射,以及被路由的公网地址,是三种不同的产品,限制也各不相同。

对其中任何一种都问五个问题:协议是什么、外部端口是什么、外部端口能否与本地不同、覆盖哪种地址族、重新连接后映射还能维持多久。绑定在活动会话上的预留,会在会话结束时消失,套餐页面怎么写都没用。

先确认本地那一半,因为死掉的监听和被封住的入站路径,从外面看完全一样:

ss -lntup | grep -E ':(8080|51820)\b'
python3 -m http.server 8080 --bind 0.0.0.0
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080

当本地请求返回 200,而来自你无法控制的网络的请求仍然超时,缺口就在公网侧。这时该做的是把协议和外部端口告诉运维方,而不是描述应用的名字。

隧道自身的传输是另一个问题

承载端口是问题里最小的一部分,也最容易查。默认值随协议而不同,而客户端能协商什么,比任何套餐描述都重要。

ClientOuter transportDefault portWhen it helps
OpenVPNUDP1194Default mode; needs outbound UDP to pass
OpenVPNTCP443UDP is dropped and TCP 443 is not
WireGuardUDP51820Short handshake; there is no TCP mode
IKEv2/IPsecUDP500 and 4500Mobile clients; 4500 carries NAT traversal

丢弃出站 UDP 的网络会接受 TCP 443 隧道,却拒绝 UDP 隧道,所以备用模式应当写进需求表。更换承载并不会移动隧道内的应用端口,也不会放宽目标策略;那些仍留在各自的行里。

碎片问题值得在那里顺手测一次,因为连上之后在大流量传输时卡住的隧道,通常是 MTU 不对,而不是端口被封:

ping -M do -s 1400 203.0.113.10
ping -M do -s 1372 203.0.113.10

在 Windows 上同样的测试是 ping -f -l 1400 203.0.113.10。把隧道 MTU 设到能通过的最大尺寸以下,然后重测失败的传输。

在为套餐付费之前,验证那三条线

把三条线放在同一页上,每一格都填实测结果,而不是承诺。下面这些命令大约一分钟就能做出那页的本地一半:

ss -lntup | sort
nc -vz -w 3 203.0.113.10 443
python3 -m http.server 8080 --bind 0.0.0.0
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080
ping -M do -s 1400 203.0.113.10

把输出当成检查清单来读。隧道开着时目标能应答,出站就算通。只有当你的网络之外的某台主机能连到外部端口上的监听,入站才算通。当承载在你接入网络允许的协议上完成握手,承载才算通。

两个习惯能让这张纸保持诚实。在每个结果旁边记下日期和客户端版本,因为服务商政策与客户端默认值都在变。然后申请能服务该流的最窄映射,因为“全部打开”的申请通常换来拒绝或加售,而不是技术答复。

相关文章