本文是《计算机网络学习笔记》系列的第九篇。上一篇讲完了 IPv4 和 IPv6 的报文格式,留下了一个悬念:IPv4 只有 32 位地址空间,理论上限约 43 亿个,全球几百亿台设备怎么够用?答案是 NAT——一种让数千台设备共享同一个公网 IP 的技术。它悄无声息地运行在你家路由器里,是每一次上网背后都绕不开的机制。

一、问题的根源:IPv4 地址不够用

IPv4 地址是 32 位整数,总共约 43 亿个。这个数字在互联网诞生之初看起来绰绰有余,但没有人预料到互联网会扩张得如此之快。

真正可分配的公网 IP 甚至比 43 亿还少,因为有一批地址段被预留作私有用途,永远不会出现在公网上:

私有地址范围常见用途
10.0.0.0/8大型企业内网
172.16.0.0/12中型网络
192.168.0.0/16家庭路由器(最常见)

你家里每台设备拿到的 IP(比如 192.168.1.5)就属于私有地址,它在全球数以亿计的局域网里大量重复存在。公网上的服务器根本无法区分"哪个 192.168.1.5"——私有地址不能直接暴露在公网上。

这就是 NAT 要解决的核心矛盾:用一个公网 IP,代表背后整个局域网的所有设备对外通信。


二、NAT 的工作原理

NAT(Network Address Translation,网络地址转换)运行在家用路由器中,核心动作只有两个:改写数据包维护映射表

出站:内网 → 公网

以你用手机访问百度为例:

手机                         家用路由器                      百度服务器
192.168.1.5:54321  ──►  改写源地址和端口  ──►  221.11.22.33:40001
  1. 手机发出数据包,源地址是私有地址 192.168.1.5,源端口是随机分配的 54321
  2. 数据包到达路由器,路由器将源 IP 改为自己的公网 IP(如 221.11.22.33),并重新分配一个端口(如 40001);
  3. 路由器在内部维护一张映射表(NAT Table),记录下这条替换关系:
公网端口     ←→     内网 IP:端口
40001        ←→     192.168.1.5:54321
40002        ←→     192.168.1.8:52110   (同时,另一台电脑在看视频)
40003        ←→     192.168.1.5:54400   (手机同时打开了另一个连接)
...
  1. 改写后的数据包发往百度服务器,百度只看到来自 221.11.22.33:40001 的请求,完全不知道你家里有多少台设备。

入站:公网 → 内网

百度的响应数据包目的地是 221.11.22.33:40001,到达路由器后:

  1. 路由器查映射表,找到 40001 对应的内网地址 192.168.1.5:54321
  2. 将数据包的目的 IP 和端口改回内网地址,转发给手机;
  3. 手机收到数据,整个过程对手机完全透明。

这就是 NAT:一进一出,靠映射表在公网地址和内网地址之间做翻译


三、NAT 带来的副作用:无法被外网主动连入

NAT 解决了地址不够用的问题,但也打破了互联网最初"任意两端可以互相通信"的设计原则——局域网内的设备无法被外网主动连接

原因很直接:外网的机器只知道你路由器的公网 IP,不知道你家里有哪些设备,也不知道该把数据发给哪个内网 IP。路由器的映射表里没有对应的条目,直接丢包。

这带来了一系列现实问题:

  • 无法在家里搭建可被外网访问的服务器;
  • P2P 软件(BT 下载、视频通话、游戏联机)中,两端都在 NAT 后面时,无法直接建立连接;
  • 远程桌面、SSH 到家里的机器,需要借助中间服务器中转。

针对这些问题,有两种常见的解法。


四、解法一:UPnP(主动端口映射)

UPnP(Universal Plug and Play,通用即插即用)允许局域网内的设备主动告诉路由器:"请在你的公网 IP 上打开某个端口,并把流量转发给我。"

流程大致如下:

内网设备  ──► "我要用端口 6881,请帮我映射"  ──►  路由器
路由器    ──► 在映射表中添加:公网 6881 → 内网 192.168.1.5:6881
外网设备  ──► 连接 221.11.22.33:6881  ──►  路由器转发  ──►  内网设备

BT 下载客户端(如 qBittorrent)通常会尝试使用 UPnP 来让别的 peer 主动连进来,提升下载速度。

缺点:需要路由器支持并开启 UPnP 功能;在安全性要求较高的环境中,UPnP 通常会被关闭。


五、解法二:UDP 打洞(Hole Punching)

当两端都在 NAT 后面,且没有 UPnP 可用时,打洞(Hole Punching)是让两端直接建立连接的标准方法。

三个角色

          公网服务器 S
         /             \
        /               \
  [NAT-A]             [NAT-B]
     |                   |
  局域网机器 A        局域网机器 B

A 和 B 都各自和公网服务器 S 建立了连接,但 A 和 B 之间无法直接通信——它们互相不知道对方的 NAT 出口地址。

打洞过程

第一步:收集 NAT 出口地址

A  ──►  S   发送 UDP 报文(A 的 NAT 路由器为此分配了出口端口,S 能看到)
B  ──►  S   同上

S 现在手里有 A 和 B 各自对外的 NAT 地址(公网 IP + 端口)。

第二步:S 交换双方信息

S 把 B 的 NAT 地址告诉 A,把 A 的 NAT 地址告诉 B。双方现在互相知道对方的"门牌号"了。

第三步:同时互打

A  ──►  B 的 NAT 地址   (A 的路由器:记下,A 给 B 方向留了一个"洞")
         B 的路由器:不认识 A,丢包 ✗

B  ──►  A 的 NAT 地址   (B 的路由器:记下,B 给 A 方向留了一个"洞")
         A 的路由器:认识 B!因为 A 刚给 B 发过包,有洞,放行 ✓

经过这两次互发,A 的路由器和 B 的路由器各自都留了一个对方的"洞",后续数据包就可以双向直接通过了。

完整时序:

S                    A                         B
|                    |                         |
|  ← A 的 NAT 地址  |                         |
|                    |   ← B 的 NAT 地址       |
|                    |                         |
|  通知 A:B 在 ...  |                         |
|  通知 B:A 在 ...  |                         |
|                    |  ──►  B(丢包,但打了洞)|
|                    |  ◄──  B(A 有洞,放行)  |
|                    |  ──►  B(B 有洞,放行)  |
|                    |       双向通信建立        |

打洞的前提:端口一致性

打洞能否成功,取决于一个关键前提:

A 给 S 发包时,路由器分配的出口端口,必须和 A 给 B 发包时分配的端口一致。

大多数家用路由器(锥型 NAT)满足这个条件——对同一个内网地址:端口,无论发往哪个公网目的地,路由器都分配同一个出口端口。

但有一类路由器(对称型 NAT,Symmetric NAT)每次换目的地就换一个新端口。A 给 S 发包时用的是 40001,给 B 发包时变成了 40002——S 告诉 B 的地址是 40001,B 敲的却是一个不存在的洞,打洞失败。

失败时的处理:中继(Relay)

打洞失败时,降级为让 S 中转双方的流量:

A  ──►  S  ──►  B
B  ──►  S  ──►  A

这正是 WebRTC 中 STUN/TURN 协议的工作分工:STUN 服务器帮助打洞,TURN 服务器在打洞失败时充当中继。


六、NAT 超时:一个容易踩坑的细节

路由器的映射表不是永久存储的,每一条映射都有超时时间。如果某个连接长时间没有数据通过(通常 5 分钟或更短),路由器会认为连接已经结束,直接删除这条记录。

这被称为 NAT 超时(NAT Timeout)

超时后再发数据包会发生什么?路由器在映射表里找不到这条连接,会向发送方返回 RST 包,导致连接被意外断开。

这也是为什么长连接应用(游戏、即时通信、数据库连接池)都需要实现应用层心跳包——定期发一个小包,保持映射表里的条目"活着",不让路由器把它删掉。


七、IPv6:彻底告别 NAT

NAT 本质上是对 IPv4 地址不足的打补丁。它破坏了互联网端到端通信的原始设计,引入了打洞、UPnP、心跳包等一系列复杂机制。

IPv6 用 128 位地址彻底解决了地址短缺问题——地址空间约为 $3.4 \times 10^{38}$,地球上每一粒沙子都能分到一个公网 IPv6 地址。有了足够的公网地址,每台设备都可以直接拥有全球唯一的公网 IP,NAT 便失去了存在的意义。

IPv6 恢复了互联网最初"任意两端可以直接通信"的端到端(End-to-End)能力。

# 查看自己是否有真正的公网 IPv6 地址(Windows)
ipconfig

# Linux
ip a

# 注意:以 fe80 开头的是链路本地地址,不是公网地址
# 真正的公网 IPv6 以 2 开头,例如:
#   2408:xxxx   联通
#   2409:xxxx   移动
#   240e:xxxx   电信

不过 IPv6 的普及是一个漫长的过程,现阶段家里的 NAT 路由器还将长期存在。


小结

概念一句话总结
NAT路由器改写 IP+端口,让内网多台设备共享一个公网 IP 出口
映射表NAT 的核心数据结构,记录公网端口与内网地址的对应关系
UPnP内网设备主动向路由器申请端口映射,适合服务端场景
UDP 打洞借助公网中介服务器,让两个 NAT 后的设备直连
NAT 超时映射表条目会因空闲而过期,长连接需要心跳保活
IPv6地址空间足够大,彻底消灭 NAT,恢复端到端通信

本系列前八篇:
· 第一篇:《TCP 协议格式详解》
· 第二篇:《TCP 三次握手与四次挥手》
· 第三篇:《TCP 可靠数据传输》
· 第四篇:《TCP 流量控制与拥塞控制》
· 第五篇:《TLS:HTTPS 背后的加密握手》
· 第六篇:《HTTP 协议进化史》
· 第七篇:《DNS:互联网的电话簿》
· 第八篇:《IPv4 与 IPv6:网络层的寻址与路由》


参考资料:《计算机网络:自顶向下方法》

标签: none

添加新评论