NAT 打洞(P2P 穿透)原理是什么?STUN、TURN、ICE 分别是什么?
简化版
NAT 打洞是让两个都在 NAT 后面的内网节点直接建立点对点(P2P)连接的技术。核心难点:双方都没有公网 IP,都无法主动连对方。打洞的思路是借助一台公网上的协调服务器,让双方先各自弄清「自己经过 NAT 后对外呈现的公网 IP:端口」,交换给对方,然后双方同时向对方的公网映射地址发包,「撞开」各自 NAT 上的映射,从而建立直连。三个关键角色:STUN——帮节点探测「我经过 NAT 后的公网地址是什么」;TURN——打洞失败时的中继兜底服务器,双方都把流量发给它转发(牺牲直连,保证能通);ICE——一个框架/流程,收集各种候选地址(本地、STUN 得到的公网、TURN 中继),逐一尝试连通,优先直连、失败回退中继,选出最优通路。WebRTC 用的就是 ICE。
详细版
一、为什么打洞难:双方都在 NAT 后
内网穿透如果只有一方在 NAT 后,可以靠公网中转服务器解决。但 P2P 场景两个节点都在 NAT 后(比如两个家庭网络里的用户视频通话),双方都无公网 IP、都无法被对方主动连接。要让它们直连(不浪费中转带宽、低延迟),就得打洞。
二、打洞的关键:NAT 的映射是「出方向创建」的
NAT 的规则:内网设备主动往外发包时,NAT 会创建一条「私有地址:端口 ↔ 公网地址:端口」的映射,此后发到这个公网地址:端口的回包能被送回该内网设备。打洞正是利用这点——让双方都主动往对方发包,各自在自己 NAT 上「打出」一个洞(映射),使对方的包能进来。
三、三个角色
| 角色 | 作用 |
|---|---|
| STUN | 节点向 STUN 服务器发包,服务器告诉它「你在我这看到的公网 IP:端口是 X」,节点由此知道自己的公网映射地址 |
| TURN | 打洞失败时的中继服务器:双方都把数据发给 TURN,由它转发(不再是直连,但保证连通) |
| ICE | 总流程框架:收集所有可能的地址候选(本地/STUN 公网/TURN 中继),配对逐一连通性测试,优选直连、兜底中继 |
四、打洞的大致流程
① A、B 各自向 STUN 服务器发包,得到自己的公网映射地址
② 通过信令服务器(业务自己的服务器)交换彼此的公网映射地址
③ A、B 【同时】向对方的公网映射地址发包
—— A 发给 B 的包,在 A 的 NAT 上打出映射;B 发给 A 的包同理
—— 于是双方 NAT 上都有了「允许对方进来」的映射,直连打通
④ 若打洞失败(如对称 NAT),回退用 TURN 中继
五、NAT 类型决定成败
- 锥形 NAT(Full Cone / 端口受限等):同一内网端口出去,对外映射相对固定/可预测,容易打通。
- 对称型 NAT(Symmetric):去往不同目标用不同的映射端口,STUN 探到的端口和实际发给对方时用的端口不一样,对方猜不到,打洞常失败,只能走 TURN 中继。
完整版教学
一、场景设定:两个内网节点想直连
先想清楚打洞解决的问题。假设 A 在家(NAT_A 后),B 也在家(NAT_B 后),两人要视频通话,希望音视频流直接在 A、B 之间走(P2P),不经过服务器中转(省服务器带宽、延迟低、隐私好)。
难点:A、B 都没有公网 IP,都在各自 NAT 后面,谁也无法主动连上谁。这就是 NAT 打洞要攻克的。
二、原理基石:NAT 映射由「主动外发」创建
理解打洞,必须先牢记 NAT 的行为:
- 内网
A_priv主动向外发包,NAT_A 会创建映射:A_priv → A_pub(一个对外的公网 IP:端口)。 - 之后,发往
A_pub的包,NAT_A 认为是「A 主动通信的回包」,会放行并转回 A。 - 但没有对应映射时,外来包一律丢弃。
打洞的核心花招就是:让 A 和 B 都主动往对方发包,从而各自在自己的 NAT 上「预先打出」一个针对对方的映射(洞)。一旦两边的洞都打好,A 发给 B 的包能穿过 NAT_B(因为 B 已经往 A 发过包、NAT_B 上有 A 的映射),反之亦然——直连就通了。
三、STUN:先搞清「我的公网长相」
要往对方的公网地址发包,首先双方得知道自己经过 NAT 后对外是什么公网 IP:端口——内网设备自己是不知道的(它只知道自己的私有地址)。
STUN(Session Traversal Utilities for NAT) 就干这个:
A → 向公网 STUN 服务器发一个请求
STUN 服务器 → 看到这个包的源地址(即 NAT_A 转换后的公网地址),
把「我看到你的地址是 A_pub:port」告诉 A
A → 于是知道了自己的公网映射地址
B 同理。这样双方都拿到了自己的公网映射地址,接下来通过信令服务器(业务自己的服务器,负责撮合双方、交换信息)把各自的公网地址告诉对方。
四、打洞:双方同时发包,互相撞开映射
拿到对方的公网地址后,A、B 几乎同时向对方的公网映射地址发包:
A 向 B_pub 发包:
- 出 NAT_A 时,NAT_A 记下映射「A 正在和 B_pub 通信」
- 到 NAT_B 时,若 B 还没往 A 发过包,NAT_B 可能先丢弃(洞还没打好)
B 向 A_pub 发包(同时):
- 出 NAT_B 时,NAT_B 记下映射「B 正在和 A_pub 通信」
- 到 NAT_A 时,因为 A 刚才发过、NAT_A 上已有 B 的映射,放行 → 到达 A
之后 A 再发的包,NAT_B 也已有 A 的映射 → 放行
⇒ 双向的洞都打好,P2P 直连建立
关键是**「同时发」**:让双方几乎在同一时间往对方发包,使各自 NAT 上的映射都建立起来,后续互发的包就都能穿过对方的 NAT 了。这就是「打洞(Hole Punching)」这个名字的由来——在 NAT 墙上互相凿出一个洞。
五、NAT 类型:为什么有时打不通
打洞成败高度依赖 NAT 的类型,重点区分:
- 锥形 NAT(Cone):内网某个
私有IP:端口出去,NAT 分配一个固定的公网端口,无论发给哪个外部目标都用这个映射(或映射可预测)。这样 STUN 探到的公网端口,就是 A 实际发给 B 时用的端口——B 能命中,容易打通。 - 对称型 NAT(Symmetric):内网同一个
私有IP:端口,发给不同的外部目标会分配不同的公网端口。于是 A 发给 STUN 服务器时用端口 P1(STUN 告诉 A 是 P1),但 A 发给 B 时 NAT 又分配了新端口 P2——B 收到的来自 A 的端口是 P2,而 B 按 STUN 给的 P1 去发根本打不中。双方端口互相预测不了,打洞通常失败。
现实中还有更细的分类(Full Cone / Restricted / Port-Restricted / Symmetric),以及「一方锥形一方对称」等组合,成败各异。只要有一方是对称 NAT,打洞成功率就大幅下降。
六、TURN:打洞失败的中继兜底
打洞不能保证 100% 成功(对称 NAT、复杂网络)。为了「无论如何都能通」,需要兜底方案——TURN(Traversal Using Relays around NAT):
- TURN 是一台公网中继服务器。
- 打洞失败时,A 和 B 都把数据发给 TURN 服务器,由 TURN 转发给对方。
- 这不再是直连(回到了「中转」模式),牺牲了 P2P 的低延迟和省带宽,但保证连通性。
所以 TURN 是「实在打不通时的保底」,代价是流量都过中继、消耗服务器带宽(TURN 服务器成本较高)。
七、ICE:把这一切编排起来
前面这些(本地地址、STUN 探到的公网地址、TURN 中继地址)到底用哪个?由谁决定?——ICE(Interactive Connectivity Establishment) 是把它们统一编排的框架/流程:
① 收集候选地址(Candidates):
- host 候选:本地网卡地址(同一内网直接可用)
- srflx 候选:STUN 得到的公网映射地址(用于打洞)
- relay 候选:TURN 中继地址(兜底)
② 双方通过信令交换各自的候选列表
③ 对候选两两配对,做【连通性检查】(互发探测包看能不能通)
④ 按优先级选出能通的最优路径:
优先【直连 host】> 【打洞 srflx】> 最后才用【TURN relay 中继】
⑤ 用选中的通路传输数据(媒体流等)
ICE 的精髓:不预设哪种方式,而是把所有可能的路都列出来,实测哪条通、哪条最优,优先直连、兜底中继。WebRTC(浏览器实时音视频)用的正是 ICE + STUN + TURN 这套组合,这也是面试高频结合点。
八、三者关系理清
- STUN:告诉你「你的公网地址」,是打洞的前提。
- TURN:打洞失败时的中继兜底。
- ICE:总指挥,收集 STUN/TURN/本地等所有候选,实测选路,优先直连、兜底中继。
ICE ⊃ 使用 STUN 和 TURN。STUN/TURN 是工具,ICE 是用这些工具的完整策略。
九、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| STUN | 探测公网映射地址和 NAT 类型 |
| TURN | 直连失败时通过中继转发流量 |
| ICE | 收集候选地址并选择最优连通路径 |
candidates:
host 192.168.1.10
srflx 203.0.113.8:40000 from STUN
relay 198.51.100.9:50000 from TURN
ICE checks pairs -> select working path
STUN 负责发现,TURN 负责兜底转发,ICE 负责把候选路径试出来并选最合适的。
- 误区:STUN 能穿透所有 NAT。 STUN 只能帮助发现映射,对称 NAT 或严格防火墙下常需 TURN。
- 误区:TURN 只是备用信令。 TURN 是媒体/数据中继,流量真的会经过 TURN 服务器,成本和延迟更高。
- 误区:ICE 是一个单独中继服务。 ICE 是连接建立框架,组合 host、STUN、TURN 候选并做连通性检查。
- 追问:为什么需要 NAT 穿透? 双方都在 NAT 后面时,公网无法直接主动连接内网地址。
- 追问:WebRTC 为什么离不开 ICE? WebRTC 要在复杂 NAT 环境下建立 P2P 媒体路径,需要自动候选和探测。
- 追问:TURN 成本为什么高? 所有媒体流量经服务器转发,消耗带宽并增加路径延迟。
十、加强记忆
NAT 打洞 = 让两个都在 NAT 后的节点直连,利用「NAT 映射由主动外发创建」这一点,让双方同时向对方的公网映射地址发包,互相在 NAT 墙上凿洞,之后互发的包就能穿过。三角色:STUN(探测「我的公网 IP:端口」,打洞前提)、TURN(打洞失败时的中继兜底,牺牲直连保连通)、ICE(总框架:收集 host/STUN 公网/TURN 中继候选,实测选路,优先直连 > 打洞 > 中继)。成败看 NAT 类型:锥形易通,对称型常失败(发不同目标用不同端口,对方猜不到)→ 回退 TURN。WebRTC = ICE + STUN + TURN。核心一句:STUN 找到自己的公网门牌,双方同时对撞打洞,打不通就让 TURN 中转,全程由 ICE 编排选最优路。