NAT 与端口映射:内网访问外网的关键机制
家里的手机、电脑、平板,地址往往都是 192.168.x.x 这种私有地址——而这种地址在公网上是不可路由的,理论上根本出不了网。可它们偏偏都能顺畅刷视频、打游戏、发消息。这中间到底发生了什么?答案就是 NAT。很多人对 NAT 的印象停留在「把内网地址换成公网地址」这一句,但这远没讲清它的运转方式:它靠一张表把多台内网设备的连接复用到一个公网地址上,回包时再凭这张表精确还原——而这张表怎么建、报文怎么被改写,才是 NAT 真正的核心。
所以这一篇我们先把机制讲透:一个内网包出网时,NAT 设备具体改写了哪些字段、在表里记下了什么;外网的回包到达时,它又是怎么凭这张表把流量准确送回原来那台内网机器的。 把这套「建表—改写—还原」的流程看清楚,再回头解释端口映射、NAT 的副作用,以及为什么它既是妥协又无处不在。
NAT 的本质不是「改个地址」那么简单,而是「用一张连接跟踪表,把多条内网连接复用到少量公网地址上,并保证回包能原路还原」。盯住那张表,整件事就清楚了。
一、先分清私有地址和公网地址
机制的前提,是理解为什么内网包不能直接出门。IPv4 划出了几段私有地址专供内网使用:
10.0.0.0/8172.16.0.0/12192.168.0.0/16
这些地址段约定只在内网有效、公网路由器一律不转发——全世界无数家庭和公司都在重复用 192.168.1.x,它们当然不能在公网上直接寻址,否则就乱套了。于是内网设备要访问外网,必须在出口处把私有源地址换成一个全球唯一、可路由的公网地址。干这件事的,就是网关上的 NAT。这就像公司内部有几百个分机号(私有地址),对外只露出一个总机号码(公网地址)。
二、NAT 到底改写了什么:从 NAT 到 NAPT
「地址转换」具体改什么,要分两种:
- 基本 NAT:只改 IP 地址。一个内网 IP 对应一个公网 IP,一对一。这需要的公网地址和内网设备一样多,并不省地址,现实里很少单独用。
- NAPT(网络地址端口转换):同时改 IP 和端口。这才是家用路由器、云网关里真正在跑的(平时说的 NAT 基本都指它)。
为什么必须连端口一起改?因为仅靠一个公网 IP,无法区分背后成百上千条内网连接。设想内网两台机器都访问同一个网站的 443 端口,出网后源 IP 都被改成同一个公网 IP——回包回来时,NAT 怎么知道该给哪台机器?答案就是用端口来区分:给每条内网连接在公网侧分配一个不同的源端口,于是「公网 IP + 公网端口」就能唯一对应回某条内网连接。
NAPT 的精髓:公网 IP 可以共用,但 NAT 给每条连接分配独占的公网端口作为区分标识。端口,才是「一个公网 IP 撑起千百条连接」的真正功臣。
三、核心机制:NAT 转换表的建立与报文改写
现在进入最关键的部分——一次出网请求,NAT 一步步做了什么。设内网主机 192.168.1.10 用源端口 5000 去访问外网服务器 203.0.113.5:443,网关公网地址是 1.2.3.4。
出网方向(SNAT,改源):
- 包到达网关,NAT 检查这条连接(由五元组标识:协议、源 IP、源端口、目的 IP、目的端口)在转换表里有没有记录;
- 没有,就新建一条表项,并从可用端口池里挑一个公网端口(比如
40001)分配给它; - 改写报文:把源 IP
192.168.1.10→1.2.3.4,源端口5000→40001(目的地址端口不变); - 重新计算受影响的校验和,把包发往外网。
此时转换表里记下的核心映射是:
1 | 内网 192.168.1.10:5000 ⟷ 公网 1.2.3.4:40001 (→ 203.0.113.5:443) |
回网方向(还原):
- 外网服务器看到的源是
1.2.3.4:40001,于是它的回包目的地址就是1.2.3.4:40001; - 回包到达网关,NAT 拿「目的 IP + 目的端口」(
1.2.3.4:40001)去查转换表,反向找到原始内网地址192.168.1.10:5000; - 改写报文:把目的 IP/端口从
1.2.3.4:40001还原成192.168.1.10:5000; - 发往内网,主机收到回包,浑然不觉中间被改过。
用一张时序图把这套「建表—改写—还原」串起来:
这张图的要害在两点:出网时 NAT 在表里「埋」下映射并改源地址,回网时全靠这张表把目的地址「还原」回去。 没有这张表,回包就成了无主的孤儿。也正因为依赖表,NAT 是有状态的——它必须记住每一条活跃连接,连接结束或长时间空闲后,表项才会被老化清除(这也是为什么长连接需要心跳保活,否则 NAT 表项超时后回包就找不到归途了)。
四、端口映射:让外网能主动访问内网
上面整套流程有个隐含前提——连接是内网主动发起的,NAT 才有机会在出网时建表。可如果反过来呢?你在内网跑了一个 Web 服务,想让外网用户主动访问,问题就来了:外网用户的包先到达,此时转换表里根本没有对应映射,NAT 不知道该把这个包转给哪台内网机器,只能丢弃。
解决办法是端口映射(也叫端口转发、DNAT):预先在 NAT 设备上配一条静态规则,告诉它:
1 | 凡是发到 公网 1.2.3.4:8080 的包,一律转发到内网 192.168.1.10:80 |
这样外网包一来,NAT 查到这条预设规则,直接做目的地址改写转进内网。家用路由器里的「端口转发 / 虚拟服务器」、把游戏主机设成「DMZ 主机」,本质都是这个机制。云上的「公网负载均衡 → 后端实例」、Kubernetes 的 NodePort,骨子里也是同一套「外部端口 → 内部地址」的 DNAT 映射。
一句话区分:普通 NAT(SNAT)服务「内网主动出去」,靠出网时动态建表;端口映射(DNAT)服务「外网主动进来」,靠预先配置静态映射。前者表是连接触发自动生成的,后者表是你手工钉死的。
五、看清机制后,再谈 NAT 的副作用
理解了「NAT 靠改写地址端口 + 维护状态表」来工作,它的种种副作用就都能解释了——它们全都源于「NAT 动了本该端到端不变的地址」:
- 破坏端到端寻址:经典 IP 模型里每台主机有唯一可达地址,任何人能直接找到任何人。NAT 之后,内网主机藏在公网地址背后,外部无法主动发起连接找到它。这是 P2P、VoIP、实时音视频都要做 NAT 穿透(STUN/TURN/打洞)的根本原因——「对方怎么直接找到我」变难了。
- 应用层携带地址会出问题:有些协议(如传统 FTP、SIP)会把自己的 IP/端口写在数据载荷里。NAT 只改了 IP 包头的地址,却改不到载荷里那个地址,对端拿着那个内网地址当然连不上。所以这些协议需要 NAT 设备做 ALG(应用层网关),连载荷一起改。
- 连接跟踪表是有限资源:每条连接占一个表项,高并发下表可能被打满(
nf_conntrack: table full),新连接直接被丢。这是高流量网关的常见故障点。 - 源地址被改写:服务端看到的客户端 IP 是 NAT 后的地址,不是真实客户端 IP——这会让基于 IP 的白名单、限流、审计全部失真。
六、工程视角:后端 / SRE 天天和 NAT 打交道
NAT 不是教科书名词,它在你的生产环境里无处不在:
- 拿不到真实客户端 IP:经过 NAT / 负载均衡 / 反向代理后,服务端
remoteAddr是中间设备的地址。要拿真实 IP,得靠X-Forwarded-For、X-Real-IP这类应用层头,或 Proxy Protocol。基于 IP 的限流、风控、白名单一定要考虑这层改写,否则可能误把整个 NAT 出口当成一个用户给封了。 - 容器网络的 SNAT:Kubernetes Pod 访问集群外,默认会在节点上做 SNAT(把 Pod IP 换成节点 IP);这也是为什么外部看到的源 IP 是节点 IP。
conntrack表满、SNAT 端口耗尽是 K8s 大流量下的典型坑。 - 端口耗尽:一个公网 IP 的可用端口约 6 万个。当大量连接都从同一公网 IP 出去访问同一个目标时,可用的
(公网IP, 端口)组合会耗尽,新连接建不起来。解法是扩公网 IP 池或优化连接复用。 - 长连接保活:NAT 表项有空闲超时。长连接(数据库、消息队列、WebSocket)如果长时间没流量,表项被老化后,回包就回不来了,表现为「连接莫名其妙断了」。TCP Keepalive / 应用层心跳正是为了定期刷活 NAT 表项。
- 回包走错出口 / 不对称路由:多出口环境里,出去和回来走了不同 NAT 设备,回包在没有对应表项的设备上被丢,是排查难点。
七、学习这一部分最容易踩的坑
1. 以为 NAT 只改 IP
家用和云网关跑的几乎都是 NAPT,IP 和端口一起改。正是靠改端口,一个公网 IP 才能复用给成百上千条连接。只想着改 IP,就理解不了回包是怎么区分的。
2. 忘了 NAT 是有状态的
NAT 靠连接跟踪表工作,表项会老化。长连接断流、conntrack 表满、回包找不到归途,根因都在这张表。把 NAT 当成「无状态地改个地址」,这些故障你都解释不了。
3. 分不清 SNAT 和 DNAT / 端口映射
内网出网是 SNAT(出网动态建表),外网进内网要 DNAT / 端口映射(预先静态配置)。两者方向相反、配置方式不同,混了就配不通服务暴露。
4. 在 NAT 后还直接信任源 IP
经过 NAT,服务端看到的源 IP 已被改写,不是真实客户端 IP。基于它做白名单、限流、审计会出错,必须结合 X-Forwarded-For 等手段还原。
总结
这一篇我们先讲机制、后讲设计,核心是看清 NAT「靠一张表把连接复用并还原」的运转方式:
- 私有地址公网不可路由,内网出网必须在出口把源地址换成可路由的公网地址;
- 现实用的是 NAPT——同时改 IP 和端口,靠给每条连接分配独占公网端口,让一个公网 IP 复用千百条连接;
- 核心流程是「出网建表 + 改写源地址,回网查表 + 还原目的地址」,NAT 因此是有状态的,表项会老化;
- 端口映射(DNAT)靠预先配置的静态规则,让外网能主动访问内网服务,与动态建表的 SNAT 方向相反;
- NAT 的副作用(破坏端到端、需要穿透/ALG、表项有限、源 IP 失真)都源于它改动了本应不变的地址;
- 工程上要时刻记得 NAT 的存在:真实客户端 IP、conntrack 表、端口耗尽、长连接保活,都是它带来的实际课题。
当你能对着一次内网访问外网,说清「出网时 NAT 改了哪些字段、表里记了什么,回包时又是怎么凭表还原的」,NAT 这部分就真正属于你了。
参考资源:
- 《计算机网络:自顶向下方法》
- 《TCP/IP 详解 卷一:协议》
- RFC 3022 - Traditional IP Network Address Translator (NAT)
- RFC 5389 - Session Traversal Utilities for NAT (STUN)