📚 第五章 IP协议相关技术
📄 一句话概括

NAT 的问题修不好,只能绕:静态映射绕「不可入」,打洞绕「回不来」。 打洞成不成看 NAT 类型,对称型是死结,所以最后一定要有中转兜底。

📝 本节和前面几节的关系

5.6.4 列的四个问题,这一节给对策,而且要说清「哪个能修、哪个只能绕」。 5.7 的隧道是兜底手段的技术底座,TURN 中转本质就是一条隧道。

一、先分清能修和不能修

四个问题里,两个能缓解,两个只能绕开:

问题 性质 对策
外面连不进来 结构性 静态映射或打洞
端口耗尽 容量性 加地址、限并发
载荷里的地址 协议性 改被动模式或 ALG
身份不可见 记录性 补映射日志

第一行「结构性」的意思是「改配置没用」:只要还有 NAT,外面的第一个包就一定进不来, 必须有人先把一个包送出去,把表项建起来。 这个「先送一个包」的动作,就是打洞的全部思想。

二、静态映射:把洞钉死

最直白的办法是「事先在表里钉一条」: 把公网地址的某个端口,永久映射到内网的某台机器的某个端口。 这样外面连进来时,表里已经有项了。 它的代价是「钉死的门永远开着」:只要映射在,扫描就不会停, 所以「用完就关」不是洁癖,是必需的卫生习惯。 家用场景更常见的是一种自动开洞: UPnP、NAT-PMP、PCP 这几个协议,让内网程序向路由器「申请一个映射」。 好处是不用进管理页面点鼠标, 坏处是「哪个程序开了哪个门」你未必知道——这也是很多安全建议让人把 UPnP 关掉的原因。

三、NAT 的四种类型

打洞能不能成,取决于 NAT「给不同目的分配端口」的规则:

类型 映射规则 过滤规则 打洞
全锥型 所有目的共用一个端口 谁都能进 总能成
受限锥型 共用一个端口 只收我发过的 IP 通常能成
端口受限 共用一个端口 只收我发过的 IP 加端口 常能成
对称型 每个目的一个端口 只收我发过的 基本打不通

前三行的共同点是「所有目的共用一个公网端口」: 所以「我在服务器上看到的端口」就是「别人连我时该用的端口」。 第四行破了这个前提:每个新目的都会分到一个新端口, 于是「学到的端口」和「连对方时用的端口」不是同一个,对方按学到的端口打,永远打不进来。 这也是「家用宽带打洞常成功、手机热点常失败」的原因: 运营商级 NAT 为了省地址,普遍是对称型。

四、打洞的四步与三件套

打洞(hole punching)的动作只有四步:

步 谁做 做什么
1 两边都连服务器 服务器记下各自的公网地址端口
2 服务器把对方的地址告诉它 这一步叫「交换候选」
3 两边同时向对方发包 各自的 NAT 里就都有了对方
4 后续直连 服务器可以退场

第 3 步的「同时」很关键:如果只有一边发,另一边的 NAT 里没有项,包还是会被丢。 所以严格说不是「同时」,而是「都发过一次」—— 只要有先后,先发的那一边的包会被丢,但表项建起来了,后发的就能进。 围绕这四步,长出了三个东西:

手段 做什么 什么时候用
STUN 问服务器「我的公网地址是什么」 锥型,最便宜
TURN 打不通就全部走服务器中转 对称型,兜底
ICE 挨个试,能直连就直连 现在的标准做法

第三行是「框架」不是「协议」: 它把 STUN 和 TURN 都用上,按优先级一个个试——先试直连,不行再中转。 所以「视频通话为什么有时连不上」的答案往往是: 两边都是对称型 NAT,只能走 TURN 中转,而中转服务器满了或离得远。

五、真正的解法是 v6

上面这些全是「在 NAT 存在的前提下想办法」,而 NAT 存在的前提是「地址不够」。 所以根治的办法只有一个:让地址够用。 v6 地址够用到「每台设备一个公网地址」, 于是不需要 NAT,不需要端口映射,不需要打洞,不需要 STUN 和 TURN。 但要分清两件事: NAT 消失不等于「没有防火墙」—— 防火墙是「策略」,NAT 是「地址转换」,以前它们凑巧装在同一台设备上,所以容易被混为一谈。 v6 时代依然要有策略,只是不再靠「没有地址」来当安全措施。

六、动手 Demo:打洞为什么有时候打不通

把四种 NAT 类型和三种穿透手段摆在一起看。 先看「映射规则」那一列,再看「打洞结果」。

# 对称型的关键:公网端口随目的变化
stun_port = 40001          # 从 STUN 服务器学到的
peer_port = 40002          # 连对方时实际用的
print('学到的端口', stun_port, '实际用的端口', peer_port)
print('对方按', stun_port, '打过来 -> 打不通')

实跑输出:

--------------------------------------------------------------------
打洞为什么有时候打不通
--------------------------------------------------------------------
**打洞(hole punching)的原理**:**两边都先向对方的公网地址发一个包**,
**各自的 NAT 表里就都有了对方,后续就能直连。**

**但能不能成,取决于 NAT 的类型**:
  NAT 类型             映射规则                         打洞结果
  -----------------------------------------------------------------------
  **全锥型**           **任何外部都能进来**             **几乎总能成功**
  **受限锥型**         **只接受我发过的 IP**            **通常能成功**
  **端口受限锥型**     **只接受我发过的 IP 加端口**     **常能成功**
  **对称型**           **每个目的一个端口**             **基本打不通**

**第四行是「对称型」的死结**:**它给「每个不同的目的地址」分配不同的公网端口**,
**所以「你从 STUN 服务器学到的端口」和「你连对方时用的端口」不是同一个。**
**对方按学到的端口打,永远打不进来。**

**于是有了三种手段**:
  手段         做什么                                 代价
  ---------------------------------------------------------------------------
  **STUN**     **问服务器「我的公网地址是什么」**     **最便宜,锥型够用**
  **TURN**     **打不通就全部走服务器中转**           **一定能通,但费带宽**
  **ICE**      **挨个试,能直连就直连**               **现在的标准做法**

**第三行是「框架」不是「协议」**:
**它把 STUN 和 TURN 都用上,按优先级去试**——**先试直连,不行再中转。**
**所以「视频通话为什么有时连不上」的答案往往是**:
**两边都是对称型 NAT,只能走 TURN 中转,而中转服务器满了。**

上面三行代码把「死结」写清楚了:打洞的前提是「别人能猜到你用哪个端口」, 而对称型恰恰让这个前提不成立。 所以「对称型 NAT 用户」在 P2P 网络里是二等公民:他们只能靠中转,而中转要花服务器的带宽, 这也是「免费的视频通话软件为什么要限制时长」的一个原因。 最后一个判断口诀:「两边至少有一边是锥型,才有直连的可能」; 两边都是对称型,别折腾了,直接上中转。

30 秒实验

  1. 想一下:为什么打洞必须「两边都先发一次」?
  2. 算一下:一个 TURN 服务器要给 1000 路通话中转,按每路 1 Mbps 需要多少带宽?
  3. 想一下:为什么「有 NAT 所以安全」这句话是错的?

常见误区

  1. 以为「打洞是一种协议」。 它是一种技巧,STUN、ICE 只是把它工程化。
  2. 以为「打洞能穿透任何 NAT」。 对称型穿不过,必须中转兜底。
  3. 以为「上了 v6 就不需要防火墙」。 NAT 和防火墙是两件事,v6 只是不再需要地址转换。
自测三问(点开对答案) 1. 打洞的核心动作是什么? —— **两边都先向对方的公网地址发一个包**,把各自的 NAT 表项建起来,之后就能直连。 2. 哪种 NAT 打洞一定失败? —— **对称型**:它给每个目的分配不同的公网端口,「学到的端口」和「实际用的端口」不是同一个。 3. STUN、TURN、ICE 三者什么关系? —— **STUN 负责「问地址」,TURN 负责「打不通就中转」,ICE 是把两者按优先级串起来的框架。**

下一节讲一种「把整个包塞进另一个包」的技术:IP 隧道。

相关笔记