VPN 端口需求:比较之前先写三行
弄清你真正面对的是哪个端口问题:出站目标、入站发布,还是隧道自身的传输。然后只向服务商问这一件事。
VPN 服务的端口清单在比较页面之间被反复抄录,却常常略去决定它们是否有意义的关键细节:是谁发起连接。一条允许向外访问 TCP 443 的规则,并不能说明别的主机能否连到你机器上的监听端口。在比较任何一款套餐之前,先写下三条彼此独立的需求,并标出你的任务真正需要哪一条。一旦有了这张纸,大多数比较会收缩成一个很短的问题。
先拆开需求,再去看任何端口清单
三条线几乎覆盖所有情况:到达隧道之外的目标、对外发布让其他人能连进来、以及承载隧道本身。每一条线失败的原因都不同,而满足其中一条的服务商政策,可能完全不触及另外两条。把任务归到正确的那条线上只要五分钟,却能省掉日后的争论。
| Line | Who starts it | What can block it | Question for the provider |
|---|---|---|---|
| Outbound destination | Your application | Egress filter on the exit node | Is this protocol and port allowed to that destination? |
| Inbound publishing | A remote peer | No mapping, no public address, short lifetime | Which protocol, which external port, and how long does the mapping last? |
| Tunnel transport | Your client to the gateway | The access network drops that protocol | Does 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,而来自你无法控制的网络的请求仍然超时,缺口就在公网侧。这时该做的是把协议和外部端口告诉运维方,而不是描述应用的名字。
隧道自身的传输是另一个问题
承载端口是问题里最小的一部分,也最容易查。默认值随协议而不同,而客户端能协商什么,比任何套餐描述都重要。
| Client | Outer transport | Default port | When it helps |
|---|---|---|---|
| OpenVPN | UDP | 1194 | Default mode; needs outbound UDP to pass |
| OpenVPN | TCP | 443 | UDP is dropped and TCP 443 is not |
| WireGuard | UDP | 51820 | Short handshake; there is no TCP mode |
| IKEv2/IPsec | UDP | 500 and 4500 | Mobile 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
把输出当成检查清单来读。隧道开着时目标能应答,出站就算通。只有当你的网络之外的某台主机能连到外部端口上的监听,入站才算通。当承载在你接入网络允许的协议上完成握手,承载才算通。
两个习惯能让这张纸保持诚实。在每个结果旁边记下日期和客户端版本,因为服务商政策与客户端默认值都在变。然后申请能服务该流的最窄映射,因为“全部打开”的申请通常换来拒绝或加售,而不是技术答复。