上周在公司想连回家里那台 NAS,用惯的那套组网工具一直显示 relay,延迟两百多毫秒,拖一个几百兆的文件能把人等走神。
我一开始认定是公司出口在限速。换端口、开 UPnP、在路由器上做 DMZ、把中转节点从香港换成上海,一圈折腾下来,延迟数字一动没动。
中间还试了个更没用的办法:把家里路由器的固件换了个版本,因为论坛上说某个版本的 NAT 实现"更宽松"。现在回头看,那次刷机除了让我多花四十分钟重配拨号,什么也没解决。
直到我在两边分别看了一眼各自的出口地址,才反应过来:它根本没在直连,从第一天起就在老老实实绕中转。中转服务器的物理位置决定了那两百毫秒,跟我怎么改家里的设置一点关系都没有。
这事让我把打洞翻出来重看了一遍。原理不复杂,几张纸能讲完,但网上流传的版本大多停在"两边同时发包就打通了"这一步,真到异网、真到运营商那儿多了一层 NAT 的时候,还是不知道卡在哪儿。
所以这篇按顺序讲五件事:八个步骤各自在干什么,为什么某些组合天生就打不通,异网额外难在哪里,怎么在三十秒内测出自己属于哪一种,以及几个我踩过的判断误区。判据都从 RFC 里抄,实测数据是在我这条电信宽带上跑出来的,跑不通的服务器和测不了的项,我都会明说。
再统一一下叫法。这篇讲的都是 NAT44,也就是 IPv4 上的地址转换,跟 NAT64 那种 v6 和 v4 互译是两件事。NAT44 是家宽、公司网络、手机热点里跑的那套东西,也是打洞真正的主战场。
先说结论
- 打洞不是凿穿防火墙,是让两边的 NAT 各开一个临时口子。口子只对特定目标开放,靠的是"你先寄出去、对方回寄才能进来"这条默认规则,没有任何一条安全策略被绕过。
- 能不能通,只看两件事:映射行为和过滤行为。映射决定你报出去的门牌号是不是真的,过滤决定别人的包能不能进来。RFC 4787 把这两件事各分了三种,组合出九种可能。
- "两端分数加起来超过 6 就打不通"是社区经验,但它背后有一条硬逻辑。超过 6 意味着两边都得靠猜对方的端口,猜中是运气,猜不中是常态。
- 异网难的不是距离,是接入侧多了一层你完全看不到的 NAT。你自己那台路由器通常是好的,坏在运营商那层,而那一层的端口分配规则你改不了,也没法从外面观测。
- 测 NAT 类型最容易错的地方,是把"端口随机"当成"对称型"。这两件事没有关系,我下面贴的实测结果就是同时成立的——很多人恰恰在这一步判错,然后把一台好好的 NAT1 判成 NAT4。
打洞不是凿墙,是互相报门牌号
先把 NAT 想成一栋楼的门禁——楼里每户都没有对外门牌,寄出去的信统一由门卫换成大楼地址加一个临时编号,回信就按这个编号送回楼里。
门卫有一条铁律:只有你主动寄过信的地址,回信才放进来,别的地址直接扔。这条规则不是安全漏洞,是 NAT 存在的意义——没有它,内网每一台设备都会被外面直接摸到。
于是两户人家想直接通信,就卡在一个死循环上:我不知道你的编号,你也收不到我的信,因为我的信对你来说属于"陌生地址寄来的",门卫照规矩扔掉,一点情面都不讲。
打洞干的事,是找一台两边都认识的公共服务器当中间人,各自先给它寄一封信,问"我在你那儿显示的地址和编号是多少",然后把这个编号互相转告。
注意,是转告,不是让服务器代转数据——服务器全程不碰你的文件,它只回答一个问题:你从外面看长什么样。这类服务器有个名字,叫 STUN,后面所有流程都从它开始。
有了编号,两边同时往外寄,这时候各自的门卫都有了"我寄给过对方"的记录,对方的回信开始放行。整套机制的巧妙之处就在这儿:它利用的是 NAT 自己的规则,不是绕开它。
就这么简单。全程没有一条规则被破坏,只是把"主动寄出"这个动作做给了对方看。所以打洞不降低安全性——这也是它能在浏览器、游戏、视频通话里普遍使用、而没有被当成攻击手法的原因。
八个步骤里,第七步注定要失败一次
把上面的比喻摊开,一次成功的打洞是八个步骤。前面六个都在交换信息,真正开始打的是第七步和第八步。

很多人第一次看这个流程会卡在第 7 步:A 明明已经知道 B 的地址端口了,为什么打过去会被丢?
答案在 B 的门卫那儿——B 还没往 A 的地址寄过信,所以从 A 来的包在它眼里就是陌生地址,照规矩扔掉,这一扔是必然的,跟运气无关。
不过这个包的作用本来也不是送达,而是在 A 的门卫那里留下一条记录。B 紧接着从同一个端口往回打,这时候 A 的门卫放行,包就到了 A 手上——A 的门卫已经认了 B。而 B 的门卫那边,刚才 B 往外打的时候也留了记录,A 后面再发就通了。
所以第 7 步失败是预期行为,不是 bug:实际实现里两边都不会只发一次,而是每隔几百毫秒重发一轮,直到某一轮正好对上——所谓"同时发包",缩短的是这个对上的时间,不是必要条件。重发就是了。
顺便说,这一步也是排查时最容易看错的地方。只在一端抓包,看到"包发出去了但没回音",很容易得出"对端把我封了"的结论,而实际上对端可能连包都没收到。也可能收到了,只是回的那一发正好落在你的门卫还没开门之前。
这里还有一个隐含前提,整篇文章后面都在围绕它展开:A 报给 B 的那个编号,必须是 A 真正去敲 B 的门时用的编号。如果这两者不是同一个,B 打过去就打在空气里,流程在第 7 步之后就卡死了。
你报的门牌号,可能不是你真正用的那个
上面那个前提,在大部分家用路由器上成立,在运营商的设备上不一定。RFC 4787 把映射行为分成三种,说白了就是"同一个门卫,对不同目的地会不会给你换编号":
| 映射行为 | 人话 | 对打洞的影响 |
|---|---|---|
| 端点无关映射(EIM) | 不管寄给谁,都用同一个编号 | 问到的编号可以直接用,条件最好 |
| 地址相关映射(ADM) | 寄给不同的大楼换编号,同一栋楼不换 | 跨楼就失效,同楼还有戏 |
| 地址端口相关映射(APDM) | 每换一个收件人就换一个编号 | 问到的编号基本没用,俗称对称型 |
RFC 4787 的第一条要求就是 NAT 必须支持端点无关映射。它给的理由很直白:不这么做,对等应用就只能靠中继,而中继在很多场景下并不现实。要么成本顶不住,要么延迟没法接受。
这条要求写于 2007 年,家用路由器基本都照着做了,这也是为什么你在家里测,十有八九测出来是好的:设备厂商有动力遵守标准,不遵守的后果会直接砸在用户的通话和游戏体验上,口碑比合规更疼。这不奇怪。
但运营商那层是另一回事:共享地址池、按用户切块、还要防端口扫描,这些需求把它推向了相反的配置。标准写了要求,现实里守不守是另一码事,这也是同一句"我这儿是 NAT1",在家里测和在 4G 热点上测会得出两个答案的原因。
顺便记一个细节:判断映射行为,必须用同一个本地端口去问不同目的地。换一个本地端口再问,端口当然会变,那测出来的是"新连接会不会换端口",跟映射行为没关系。很多一键测速网站就是在这儿偷懒的。
严格按 RFC 5780 的做法,应该用同一台 STUN 服务器的主地址和备用地址(OTHER-ADDRESS)来比,这样能保证除了目的地以外其他变量都不动。
我下面那个脚本用的是"不同 IP 的多台服务器"这个近似办法,够用,但不完全等价:跨运营商去问和同机房去问,路径上经过的设备都不一样,端口恰好不变也可能只是巧合。能说明问题就行。
门卫还有另一套规矩
映射只是第一层。门卫还有一套过滤规则,决定"什么样的回信能进来",RFC 4787 同样分了三种:
| 过滤行为 | 放行条件 | 严格程度 |
|---|---|---|
| 端点无关过滤(EIF) | 只要编号存在,谁都放行 | 最松 |
| 地址相关过滤(ADF) | 你寄过信的那栋楼,谁来都放 | 中 |
| 地址端口相关过滤(APDF) | 必须是你寄过的那个具体地址加端口 | 最严 |
这两件事经常被混为一谈,其实它们管的方向完全不同:映射管"我出去时用哪个编号",过滤管"别人进来时要什么条件"。一个 NAT 完全可能映射很松、过滤很严,也可能反过来——两者之间没有任何绑定关系。
混起来讲的后果很具体。你会看到有人信誓旦旦地说"我是全锥,肯定能打洞",然后打不通。全锥这个词本来就同时说了映射和过滤两件事,只测了其中一半就下结论,当然对不上。
过滤行为比映射行为难测:RFC 5780 测过滤要服务器配合,让服务器从另一个地址端口回包给你,看你收不收得到,而国内公开的 STUN 服务器基本不支持这个扩展。
所以我下面那个脚本干脆不测过滤,只测映射。与其给一个测不准的数字,不如老实说这一项没测——这也是我给自己定的一条规矩:能测的报数,不能测的说不能测。
网上那套 1 到 4 分的打分,就是把这两件事压成了一个数字:1 分是全锥,2 分是地址限制,3 分是端口限制,4 分是对称型。
这个打分是社区约定,不是 IETF 标准,没有哪份 RFC 定义过"1 到 4"这个编号。但因为它把组合结果说清楚了,一直有人在用,下面我也沿用这套说法——只当它是个备忘标签,别当成规范。能看懂就行。
6 这条线是怎么画出来的
两端各有一个分数,加起来做判断,这就是"和超过 6 就打不通"的来历。我把十六种组合全推了一遍,按 RFC 4787 的映射和过滤行为逐格算,结果是这样:

图上有个容易看漏的细节:和值 6 出现了三次,但结论不是一个——NAT3 加 NAT3 是稳的,NAT2 加 NAT4 只是勉强。所以这条经验线的准确说法是"超过 6 一定不行,等于 6 要看具体组合"。就这么细。
7 分那三格是死锁,值得单独讲一遍:A 是端口限制型,出去用固定编号,B 是对称型,对每个收件人换编号,两种行为叠在一起就是互相看不见。
A 从信令拿到的是 B 对公共服务器用的那个编号,不是 B 对 A 用的那个,打过去全部落在空处;B 往回打,源地址端口又不是 A 认的那个,照样被门卫挡在外面。两边都在等对方先打中自己,而两边都打不中,这个状态会一直持续到有一方放弃。
8 分更彻底,两端都要猜对方端口。端口空间六万多个,两端同时猜中的概率低到没有工程意义——除非其中一端的分配规律可以被推出来,那就退回到端口预测那条偏门路上去了,而那需要天时地利。没戏。
6 分那两格里,NAT2 加 NAT4 能通的原因有点绕,但它正好解释了为什么不能一刀切。有点意思。
B 是地址相关过滤,只校验寄信人属于哪栋楼、不校验具体编号——A 打过来的包源地址正好是 A 那栋楼,而 B 之前给 A 那栋楼寄过信,所以门卫放行。
包进了 B 的机器,B 从包的源地址里直接读到了 A 的真实编号,然后照着回——整个过程没有用到信令交换来的那个错误编号,是靠"收到一个陌生包"顺手纠正的。
ICE(RFC 8445)给这种半路学到的地址起了个名字,叫 peer-reflexive candidate,专门用来救这种"报错了编号但对端还能收到"的局面——名字挺唬人,机制其实就是"从收到的包里读一下源地址"。反过来说,只要 B 换成端口限制过滤,A 的包进不去,B 就学不到,死锁成立。
所以严格来说,决定成败的不是那个分数,是更朴素的一句话:两端是不是都需要对方猜自己的端口——都不需要,稳;一端需要一端不需要,还能通;两端都需要,趁早别试。
异网难的不是距离
同城同网打得很顺,一跨运营商就掉到 relay,这个现象太常见了,以至于很多人以为是互联带宽的问题。带宽确实有影响,但不是主因。
主因是路径上多了一层设备。现在的家宽大多不给独立公网地址了,运营商侧再做一次地址转换,把你换成 100.64.0.0/10 网段里的一个地址,再统一出口。这个网段是 RFC 6598 专门划出来的。
这个网段有个名字叫共享地址空间,意思很直白:一段地址被很多人一起用。你家路由器拿到的是它,不是你,你从外面看到的那个公网 IP,可能挂着同一栋楼里的几十户人家,端口冲突全靠运营商那层去分。
于是你的包要穿过两层门卫:自己家路由器一层,运营商一层。

路径的严格程度按最严的那层算:你家里那台路由器哪怕配成最宽松的全锥,只要运营商那层是对称型,整条路就按对称型算,木桶效应,不看平均值。这也是为什么"我路由器设置没问题"和"打不通"可以同时成立。改不了。
想知道自己在不在运营商 NAT 后面,一条命令就够:Windows 用 tracert -d -h 3 223.5.5.5,Linux 和路由器上换成 tracepath -n -m 3 223.5.5.5,只用前三跳。选 223.5.5.5 是因为它在国内解析稳定,别拿境外地址试。
看第二三跳。出现 100.64.x.x 到 100.127.x.x 之间的地址,就说明出口经过了运营商级 NAT;前两跳还是 192.168 之类,说明你家里自己还套了一层,那就是双层加双层,四层门卫。这种局面打不穿,几乎是注定的。
这里有个反直觉的点:运营商 NAT 如果守规矩,打洞是能通的,RFC 6888 给 CGN 提的要求里,让它跟随 RFC 4787 的 UDP 行为,包括端点无关映射,并推荐端点无关过滤。
问题在于执行程度不一,各省各厂商自己拿捏,你没法从外面判断对面守不守。同一个运营商,市区和县城可能是两套设备、两套配置,这就是为什么同样一条线路,邻居说能直连、你不行。没法预判。
还有一条容易忽略的限制:同一个 CGN 要服务成百上千用户,端口是紧俏资源,RFC 6888 允许它限制每个用户的端口数量,用完了就排队。
这意味着大规模并发打洞——比如一次向对端几百个端口狂发——在某些线路上可能直接把配额打满,后面的新连接反而建不起来。"多发点总能中"这个思路,是有代价的。代价还不小。
至于跨网丢包,我的判断是它会放大失败率,但很难单独造成失败。打洞靠一个来回几百毫秒的窗口,跨网抖动和 UDP 限速会让窗口内的重传更容易落空——本来三次能对上的,变成十次。这是经验,不是结论。
这部分我没做过跨省的系统性对比,只能说是我几次排查下来的经验判断,不是结论。真要验证,得在两省各架一台机器做对照,我一直没凑齐这个条件。
打通之后还会断,问题在保活
还有一个坑在打洞成功之后。NAT 的映射是有寿命的,RFC 4787 要求 UDP 映射至少维持两分钟,并推荐默认五分钟以上,短于这个数值就算不合规。
注意这是对普通 NAT 的要求,CGNAT 的实际情况我测不到——你得有办法观察运营商设备的会话表才行。我的经验是它比家用路由器短,但短到多少,我给不出数字。网上流传的几十秒,我不确定来源。
所以保活间隔要按路径上最短的那个超时来定,而不是按你自己路由器的。这就是"用着用着突然变慢、过一会儿又恢复"的一个常见来源:映射过期、直连断开、工具悄悄切回中继,过一阵又试通了。
我现在的习惯是二十秒发一个空包。频率再高没必要,反而会白白消耗运营商的端口配额;再低就容易撞上超时。二十秒,够用了。
三十秒测出自己属于哪一种
网上那些"一键测 NAT 类型"的网页,多数只做了一次 STUN 查询,然后告诉你一个类型,而那只能看出出口地址,看不出映射行为。就这么点信息量,连映射和过滤都分不开。
要判断映射,必须用同一个本地端口去问不同目的地,这是 RFC 5780 的思路,也是下面这个脚本在做的事。只有八十来行,没用任何第三方库,Windows 和 Linux 直接跑。
# -*- coding: utf-8 -*-
"""nat_probe_lite.py —— 三分钟测出你的 NAT 会不会换端口
只用标准库:python nat_probe_lite.py
"""
import random, socket, struct, time
MAGIC = 0x2112A442
SERVERS = [("stun.miwifi.com", 3478), ("stun.chat.bilibili.com", 3478),
("stun.cloudflare.com", 3478), ("stun.l.google.com", 19302)]
def ask(sock, host, port, timeout=1.5):
"""向一台 STUN 服务器要一次 XOR-MAPPED-ADDRESS,返回 (ip, port)。"""
try:
addr = socket.gethostbyname(host)
except OSError:
return None
tid = bytes(random.getrandbits(8) for _ in range(12))
sock.settimeout(timeout)
try:
sock.sendto(struct.pack("!HHI", 1, 0, MAGIC) + tid, (addr, port))
while True:
data, _ = sock.recvfrom(2048)
if len(data) < 20 or struct.unpack("!H", data[:2])[0] != 0x0101:
continue
body, off = data[20:20 + struct.unpack("!H", data[2:4])[0]], 0
while off + 4 <= len(body):
atype, alen = struct.unpack("!HH", body[off:off + 4])
val = body[off + 4:off + 4 + alen]
if atype == 0x0020 and val[1] == 1:
port_ = struct.unpack("!H", val[2:4])[0] ^ (MAGIC >> 16)
ip_ = struct.unpack("!I", val[4:8])[0] ^ MAGIC
return socket.inet_ntoa(struct.pack("!I", ip_)), port_
off += 4 + alen + (-alen % 4)
except (socket.timeout, OSError):
return None
def same_socket_many_dest():
"""同一个本地端口分别问四家,看回到的公网端口是否一样。"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 0))
seen = {}
for host, port in SERVERS:
got = ask(s, host, port)
if got:
seen[host] = got
print(f" {host:<24} 出口 {got[0]}:{got[1]}")
s.close()
return seen
def new_socket_x12():
"""连开 12 个新映射,看公网端口是 +1 递增还是乱跳。"""
ports = []
for _ in range(12):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 0))
got = ask(s, *SERVERS[0])
if got:
ports.append(got[1])
s.close()
time.sleep(0.05)
return ports
if __name__ == "__main__":
print("[1] 同一 socket,四个目的地")
seen = same_socket_many_dest()
ports = {v[1] for v in seen.values()}
if len(ports) == 1:
print(f" => 四个目的地共用端口 {ports.pop()}:端点无关映射(EIM)")
else:
print(f" => 端口跟着目的地变 {sorted(ports)}:至少是地址相关映射")
print("\n[2] 连续开 12 个新映射")
seq = new_socket_x12()
print(f" {seq}")
deltas = [b - a for a, b in zip(seq, seq[1:])]
print(f" 差值 {deltas}")
print(f" => {'端口规律,可以预测' if len(set(deltas)) <= 2 else '端口乱跳,预测无效'}")
print(" 注意:这一项只说明映射之间不连续,不等于对称型。")
我在自己这条电信宽带上跑出来的结果:

第一段是重点:同一个本地 socket,分别问小米、B 站、Cloudflare、Google 四家,回来的是同一个出口端口。这说明我这台路由器(或者它上游那一层)的映射是端点无关的——问到的编号可以直接拿去用,不用做任何修正。
第二段看着像另一个结论:连着开十二个新映射,端口从 62276 跳到 62397 又跳回 62325,差值乱七八糟,一点规律都没有——看上去活像一台对称型 NAT。
很多人到这儿就判定"端口随机,所以是对称型",然后得出一个错误的结论。这两段一点都不矛盾。
映射行为问的是"同一个映射能不能复用",端口随机化说的是"不同映射之间连不连续",两件事正交。RFC 6056 专门建议操作系统和中间设备把端口分配随机化,目的就是防端口预测攻击。
一个 NAT 完全可以既端点无关、又随机分配。这两个性质不但不冲突,还正好是现代设备的推荐配置——随机化负责安全,端点无关负责兼容,两个目标各管各的。
分开看,我这台机器的结论才成立:映射端点无关,可以打洞;端口不可预测,靠猜没用。前者是打洞的前提,后者只影响"猜端口"这类偏门手段能不能用——混在一起判,就会把一台好端端的 NAT1 判成 NAT4。
顺手说一句这个脚本测不了什么,免得被它误导:它不测过滤行为,不测映射超时,也测不出多层 NAT 里每一层各自的行为——你看到的永远是所有层叠加之后的最终结果。
最后那条挺关键:我这台机器测出端点无关,说明整条链路上没有任何一层在做按目的地换端口的事,但它不能告诉我到底是哪一层的功劳。要拆开看,得把机器直接接在光猫上再测一次,做对照。
TCP 是另一套打法
上面讲的全是 UDP。如果你要传文件、要跑 SSH,想直接拿 TCP 打洞,事情会麻烦一层。
UDP 是"随手扔一个包",扔出去就有映射,没什么前置条件。TCP 不行,第一个包必须是 SYN,而 SYN 本来是"我要连你"的意思,NAT 看到内网主机发 SYN 出去,才会建映射。
于是两边得几乎同时发出 SYN,让两个 SYN 在中间交叉而过。这个动作在 TCP 规范里有名字,叫同时打开(simultaneous open),RFC 793 就写了,只是平时几乎用不到。
实现上有两个硬门槛:第一,两个 socket 要绑同一个本地端口,不然 NAT 那边就是两条不同的映射,得开 SO_REUSEADDR 才行,这一步在很多封装好的库里根本没暴露出来。
第二,得赌路上没有设备多管闲事:不少 NAT 看到"没有对应出站连接的 SYN"会直接回一个 RST,连接当场被掐断,这套打法在这些设备面前完全失效。
再加一条:UDP 打洞可以靠重传反复尝试,而 TCP 的 SYN 重传间隔是秒级的,跨网 RTT 一抖窗口就错过了。Ford 他们在 2005 年那篇论文里测出来,UDP 打洞成功率大约 82%,TCP 只有 64%——差距基本来自这里。
现实里的做法基本都绕开了:先用 UDP 把洞打通,再在隧道里跑 TCP。你在 WireGuard、各种组网工具里传的文件,走的都是这条路。真要原生 TCP 打洞,一般只在 UDP 被彻底封死的环境里才值得折腾。别指望它通用。
几个我自己踩过的判断误区
打洞这事最大的坑不在技术,在于你很容易拿到一个"看起来是结论"的东西,然后照着它排查半天——方向从一开始就是歪的。这种事我干过不止一次。
误区一:拿一键测速网站的结论当准。多数只查一次 STUN,那只能看到出口地址。有的网站说是测"类型",实际测的是过滤行为,跟映射行为不是一回事。看到 NAT1 就以为万事大吉,是这类工具最容易制造的错觉。
误区二:以为路由器里的"全锥 NAT"开关能解决一切。它只影响你自己那层,如果上游还有 CGNAT,你把家里调得再宽松也没用——木桶按最严的那块算。
误区三:以为 UPnP 或端口映射有用。同样只作用在家用路由这一层:两层 NAT 的情况下,你在自己路由器上开一个口子,运营商那层根本不认,包照样进不来。
误区四:ping 得通就以为能打洞。ICMP 和 UDP 是两套完全不同的处理路径——很多设备允许 ICMP 出去、把 UDP 掐掉,反过来也有。用 ping 判断连通性,在这件事上没有参考价值。
至于打电话让运营商给公网地址,各地政策不一样,有人说一个电话就办,有人说加钱也不行。这件事我没有可靠数据,就不给建议了——你能打通客服,直接问。
打不通之后,按这个顺序试
确认打不通以后,动作是有优先级的。我按"先做哪个"排了一下,代价从低到高:
| 顺序 | 手段 | 适用 | 代价 |
|---|---|---|---|
| 1 | 先试 IPv6 | 双栈线路,两端都有 v6 前缀 | 零,直接绕开全部 NAT |
| 2 | PCP / NAT-PMP / UPnP 申请映射 | 只对自己家路由器有效 | 零,但对运营商那层没用 |
| 3 | 多端口并发打洞 | 对端端口有规律可循 | 低,注意运营商端口配额 |
| 4 | TCP 同时打开 | UDP 被封、一端是 3 分 | 中,跨网窗口极窄 |
| 5 | 改走 TCP 443 或 QUIC | 跨网 UDP 干扰严重 | 中,需要实现或换协议栈 |
| 6 | 中继兜底 | 所有 7 分和 8 分的组合 | 高,带宽成本和延迟 |
第一条被严重低估:三大运营商的移动网络和大部分家宽已经下发 IPv6 前缀,只要两端都拿到可路由的地址,所有 NAT 问题一次性消失。不用管映射,不用管过滤,不用管端口。连这篇文章讲的整套东西都用不上了。
能走 v6 就走 v6,这是唯一一条从根上绕开的办法,其他五条都是在 NAT 的规则里找空隙。可惜很多人一上来就折腾第二条和第三条,把最简单的跳过。
最后一条不是失败,是设计里就该有的一层。那 82% 和 64% 反过来读,意思是每五六个场景里就有一个必须靠中转。这个比例十几年过去没有本质变化,只是失败的原因从家用路由器挪到了运营商。
所以正经的组网工具都这么设计:先尽力直连,连不上就静默切中继,用户只看到延迟变了一点。你要是自己写,别把中继当兜底方案,把它当正常路径之一——代码会简单很多,出错时的行为也可预期。
差不多就这些
回头看我那次折腾,浪费时间几乎全在"改自己这边"上:换端口、开 DMZ、刷固件。而真正的原因在另外两段我碰不到的网络里。打洞这件事,你能控制的部分比想象中小得多。先把这一点认了,排查效率会高很多。
如果只想做一件事,我建议先跑那个脚本,把自己的映射行为测出来。测出来是端点无关,问题基本就不在你这边,省得再折腾路由器;测出来不是,那说明连家里这层都卡着——先把它理顺,再谈别的。
至于跨运营商那个死结,短期我不觉得有优雅解法——要么等 IPv6 铺完,要么接受中继,中间那些端口预测、生日悖论的招数,能救回来的比例不值得那个复杂度。
剩下的部分,交给运气和运营商。
如果你觉得这篇文章不错,有用的话可以点击下面的“赞赏”支持作者,你的支持是我更新的动力(饿饿吃饭)。


Comments NOTHING