NAT 的核心是一张表:每条会话一行,记录内网地址、公网端口和目的地址。 表的容量就是「同时能有多少连接」的上限,而表项会老化。
5.6.1 讲了定义,这一节讲「那张表怎么建、怎么删」。 4.5.3 的 PMTU 黑洞在 NAT 场景更常见,原因也在这里。
一、表里有什么
一行就是一条会话,五列足够:
| 列 | 例子 | 作用 |
|---|---|---|
| 内网地址 | 192.168.3.101:50001 | 回程送给谁 |
| 公网映射 | 203.0.113.7:40001 | 对外长什么样 |
| 目的地址 | 110.242.68.66:443 | 连的是谁 |
| 协议 | TCP | 端口语义不同 |
| 老化时间 | 剩余秒数 | 什么时候删 |
第一、二列是「双向映射」:出去时查第一列改第二列,回来时查第二列改第一列。 所以这张表必须是「双向可查」的,实现上就是两个哈希表。 第三列容易被忽略,但它不能省:因为「同一个内网端口可以连不同目的地」, 如果只按内网地址映射,两条会话就会撞车。 所以「一条会话」的粒度是五元组。
二、容量上限怎么算
一个公网地址最多 65535 个端口,所以理论上限就是 65535 条会话(每台设备可能占好几个)。
| 设备类型 | 典型表容量 | 能用多久 |
|---|---|---|
| 家用路由器 | 几千到几万条 | 日常够用 |
| 企业级设备 | 几十万条 | 要规划 |
| 运营商级 NAT | 上百万条 | 要分池 |
第一行「几千到几万」是内存决定的:每条表项要占几十字节,家用路由器的内存本来就不多。 所以「全公司一起开视频会议」时表会被填满——症状是「新连接建不起来,但已连上的不卡」, 很容易被误判成「带宽不够」。 这也是「大内网要分层 NAT」的原因:一个出口扛不住,就得分多个出口、多个公网地址。
三、表项为什么会消失
因为没有「结束信号」:TCP 正常关闭能删表,但「拔网线」「程序崩溃」不会发 FIN。 所以表项只能靠「超时」删。
| 协议 | 典型老化时间 | 说明 |
|---|---|---|
| TCP 已建立 | 几分钟到几小时 | 看设备配置 |
| TCP 半开 | 几十秒 | 更短,防占用 |
| UDP | 30 秒到几分钟 | 没有连接概念 |
第三行「没有连接概念」是 UDP 的麻烦:NAT 只能靠「一段时间没包」来判断「这条会话结束了」。 所以「UDP 打洞」必须「定期发心跳」——否则表项一过期,洞就白打了。 这也解释了「SSH 挂一会儿就断」:因为中间 NAT 的表项过期了。 解决办法是在客户端开 keepalive,定期发个空包把表项续上。
四、动手 Demo:一张 NAT 表能装多少会话
模拟三台内网设备、六条会话共用一个公网地址,看端口怎么分配。 注意「内网端口」和「公网端口」不是同一个数。
port = 40001
for host in ('192.168.3.101', '192.168.3.102', '192.168.3.103'):
for dst in ('110.242.68.66:443', '8.8.8.8:53'):
print(host, '->', '203.0.113.7:%d' % port, dst)
port += 1
实跑输出:
--------------------------------------------------------------------
一张 NAT 表能装多少会话
--------------------------------------------------------------------
**NAT 的核心是一张表**:**每条会话一行,记录「内网谁、外网哪、多久没动」。**
**表的容量就是「同时能有多少连接」的上限。**
内网 公网(映射后) 目的
----------------------------------------------------------------
192.168.3.101:50001 203.0.113.7:40001 110.242.68.66:443
192.168.3.101:50002 203.0.113.7:40002 8.8.8.8:53
192.168.3.102:50003 203.0.113.7:40003 110.242.68.66:443
192.168.3.102:50004 203.0.113.7:40004 8.8.8.8:53
192.168.3.103:50005 203.0.113.7:40005 110.242.68.66:443
192.168.3.103:50006 203.0.113.7:40006 8.8.8.8:53
**三台机器、六条会话,共用一个公网地址**——**靠的就是「端口不同」。**
**算一下理论上限**:
**一个公网地址有 65535 个端口,所以最多 65535 条会话。**
**但实际远达不到**:**家用路由器的 NAT 表一般只有几千到几万条**(内存限制),**企业级几十万条。**
**表的每一项还有「老化时间」**:**TCP 一般几分钟到几小时,UDP 更短(几十秒到几分钟)。**
**所以「长时间不动的连接会被悄悄断掉」**——**这就是「SSH 挂一会儿就断」的常见原因。**
**解决办法是在客户端开 keepalive,定期发个空包把表项续上。**
「内网端口 50001、公网端口 40001」这种不一致是故意的: 如果一致,就等于「把内网端口暴露出去」,别人可以据此猜你内网开了什么服务。 所以好的实现会「随机化公网端口」,这算一个小的安全收益。 六条会话共用 203.0.113.7,靠端口区分:这就是 NAPT 的全部魔法。 换个角度说:一个公网地址最多能带 6 万多条会话, 而一台电脑同时开几百个连接很常见——所以「一个公网地址带一整个办公室」在会话数上是紧的。 「老化时间」这一列在表里看不到,但必须有:不然「程序崩了没发 FIN」的连接会永远占着表项, 几百次之后表就满了。 所以「超时删除」不是优化,是必需的机制。
30 秒实验
- 算一下:每台设备开 500 个连接,一个公网地址能带几台?
- 想一下:为什么公网端口要和内网端口不一致?
- 想一下:UDP 的 NAT 表项为什么会「悄悄消失」?
常见误区
- 以为「NAT 表是永久的」。 会老化,TCP 几分钟到几小时,UDP 更短。
- 以为「一个公网地址能带无限台设备」。 受端口和内存限制,家用几千到几万条。
- 以为「表项满了会报错」。 通常是静默失败,表现为「新连接建不起来」。
自测三问(点开对答案)
1. NAT 表一行要记哪几样? —— **内网地址端口、公网映射、目的地址、协议、老化时间**,粒度是五元组。 2. 一个公网地址最多多少条会话? —— **理论上 65535 条**(端口数),实际受设备内存限制更少。 3. 为什么 SSH 挂一会儿会断? —— **中间 NAT 的表项老化被删了**,开 keepalive 可以缓解。下一节讲 v4 和 v6 之间的翻译:NAT-PT。
- ☐ (本节完,下一节 5.6.3 NAT-PT(NAPT-PT))