翻到这一章,说明前面的路由表、子网划分、报文转发流程已经打过一遍底了。网络地址转换这个题目,初学者往往觉得简单——不就是把私网地址换成一个公网地址吗?可真到生产环境里出问题,能让人连加三个小时班:语音通了视频不通、内部员工访问不了自己公司发布在公网的业务、大并发下莫名其妙丢连接、抓包看到源地址怎么都对不上。我做了这么多年网络与系统这块,见过太多"配置看起来没问题、就是不通"的现场,九成情况下问题不在路由,而在对NAT的转换时机、转换方向和会话状态理解得不够细。
这一章我想做的事情,是把网络地址转换从"背概念"拉到"能干活"的层面。不管你是刚入门、手上只有一台虚拟机做实验的新手,还是已经在维护出口设备、被会话数和端口耗尽折腾过的老手,下面这些内容都应该能对得上号。我会先从它究竟改写了什么讲起,再把会话表这个核心机制拆开,然后给一套完全可以在自己电脑上复现的实验配置,接着按场景拆解选型和配置思路,最后拿一整节讲排障——那部分是我个人觉得最值钱的东西,因为教科书里基本不写。
先给个整体判断:NAT不是"地址翻译",它是"会话状态的重写"。你把这句话记牢,后面九成的疑难杂症都能顺着这条线摸出来。
1. 拆开NAT的外壳:它到底在解决什么、改了什么
1.1 地址不够用只是表层原因
一说NAT,几乎所有资料第一句都是"为了缓解IPv4地址枯竭"。这话没错,但它只解释了一半。如果只是地址不够,那我们把内网全部用保留地址段、出口做一对一映射就够了,为什么实际工程里绝大多数用的是端口级复用(PAT/NAPT),几十上百个内网终端挤在同一个公网地址上?因为真正的驱动力是三件事叠在一起:公网地址成本、内网编址的自由度、以及隐藏内部拓扑带来的附加好处。
我先说地址成本。早年公网地址还算宽裕,一条出口给一个地址段不稀奇;后来地址段越来越紧,运营商给小企业客户的往往就是一个固定地址,甚至动态地址。在这种条件下,一对一映射根本撑不住几十台终端的并发访问,必须靠端口维度做复用。这是PAT成为默认形态的直接原因。
再说编址自由度。这条常被忽略,但工程价值极高。内网你想怎么规划就怎么规划——按部门划段、按楼层划段、按业务划段,完全不用考虑和公网地址冲突。将来换运营商、换地址段,只需要动出口那几条转换规则,内网一行配置都不用改。我经历过一次出口迁移,就是因为前期内网规划得干净、NAT规则集中在出口设备上,整个过程只花了半小时;相反,我见过内网直接拿公网段编址、没有做任何转换的环境,改一次地址等于把整个园区重做一遍。
第三点是隐藏拓扑。外部只能看到出口地址,看不到里面有几台机器、怎么分段。这属于附带收益,不该当成安全机制来宣传——真正做访问控制还得靠策略,但客观上它确实减少了被扫描到的面。
所以你在做方案的时候,别只盯着"地址够不够",要把这三条一起摆上桌:够不够用、要不要保留内网规划的弹性、出口地址是固定还是动态。这三条基本决定了你后面该选哪种形态。
1.2 NAT改写的三个字段
理解NAT,最有效的方式不是记类型,而是记住它动了报文里的哪几个字段。翻来覆去就三个:源IP、目的IP、以及传输层的端口号。任意一种NAT形态,都是这三个字段的重写组合。
先说源地址转换(SNAT)。内网终端发包出去,出口设备把源IP从私网地址改成公网地址,同时——注意这个同时——把源端口也改掉,并在自己的会话表里记一条映射:"内网 192.168.10.15:51234 对应 公网 203.0.113.10:40001"。回包回来时,目的地址是 203.0.113.10:40001,设备查表,改回 192.168.10.15:51234 送进内网。整个过程对两端主机完全透明,它们都以为自己只是在和一个普通对端通信。这也是为什么很多人第一次抓包会懵:在终端上抓到的源地址,和在出口外侧抓到的源地址根本不是一个,中间那一跳就是NAT。
再说目的地址转换(DNAT)。外部用户访问 203.0.113.10:8443,出口设备按规则把目的地址改成 192.168.10.20:443,转发进内网。这里有个关键点容易被忽略:DNAT之后回程必须做SNAT,否则内网服务器看到源地址是外部真实地址,它会直接把回包交给自己的默认网关——如果网关恰好不是做转换的那台设备,回包路径就断了。所以你在设备上做发布服务,通常要成对配置:进来的做目的转换,出去的回程做源转换。这条规则我在第五章还会再展开,因为它是"服务发布不通"最常见的原因之一。
端口重写则是PAT的精髓。一个公网地址理论上能提供六万多个端口(严格说是 65535 减去保留段),这就是单个地址能承载的并发会话上限。后面 2.3 节我会把这条边界算给你看。
1.3 四种形态与选型对照
把三个字段的组合穷举一下,工程上常用的就是下面四类。我用一张表把它们的适用面、代价和典型误用摆出来,这张表你可以直接拿去当选型参考。
| 形态 | 改写的字段 | 映射关系 | 适合场景 | 常见误用 |
|---|---|---|---|---|
| 静态一对一 | 源或目的IP | 固定IP对固定IP | 需要固定出口地址的服务器、需被外部主动访问的主机 | 用在小办公出口,白白浪费公网地址 |
| 动态地址池 | 源IP | 多内网地址对多公网地址,先到先得 | 公网地址略多于并发主机数的场景 | 地址池比并发数少,导致后来者无法建连 |
| 端口级复用PAT | 源IP+源端口 | 多内网地址对同一公网地址的不同端口 | 绝大多数互联网出口 | 大并发下端口耗尽却不自知 |
| 目的转换(端口映射) | 目的IP+目的端口 | 公网IP:端口 对 内网IP:端口 | 内网服务对外发布 | 只做DNAT不做回程SNAT,服务发布不通 |
这里头需要多说两句的是动态地址池。它现在用得越来越少,原因很现实:地址池的本质是"分时复用",池子里有几个地址,同时最多就只能有几个内网主机上网。一旦池子被占满,后面的主机建连直接失败,而且失败得很隐蔽——用户只看到"网页转圈",没有任何报错。我见过一个环境配了 8 个地址的池子,部门扩到 40 人之后天天有人投诉"上不了网",查了半天才发现是这个原因。所以我个人的做法是:除非明确需要多个固定出口地址(比如对接只认地址白名单的外部系统),否则一律直接上PAT,简单、稳定、不给自己挖坑。
还有一种情况要单独提醒:目的转换和源转换可以同时出现在一条会话上,也就是所谓双向NAT。最典型的场景是内网用户通过公网地址访问内网服务器(专业上叫hairpin或回流),以及两侧地址段重叠时的互访。这两块我放在 4.5 节细讲,因为配置上差一个细节就不通,而且不通的表现很容易误导排查方向。
2. 摸清内部机制:会话表、转换方向与端口分配
2.1 会话表是NAT的心脏
如果让我只保留一个关于NAT的知识点,我会选会话表(也叫连接跟踪表)。它是一条条记录,形如"五元组A 映射到 五元组B,状态、超时时间、使用计数"。所有转换动作,本质都是查这张表、改字段、再转发。
这张表有三个特性,直接决定了你在生产环境里会遇到什么问题。
第一,它是有状态的。第一个包进来时创建表项,后续包命中表项直接转发,不再逐包做规则匹配。这意味着两件事:一是性能上比逐包查规则高得多;二是表项一旦被异常清除,正在传输的连接会立刻断掉。我遇到过有人为了"清理一下"直接重启了防火墙模块,结果整个园区的视频会议全断,就是因为会话表被清空了,而重新建表需要时间。
第二,它是有容量上限的。设备的内存和会话数是有对应关系的,几万到几百万不等。超限之后新连接直接被丢弃,老连接可能被强制老化。这个上限在中小设备上很容易被触碰,尤其是在有扫描流量、P2P流量、或者大量短连接的业务场景下。
第三,它有老化时间。不同协议的老化时间不一样,TCP建立后通常较长(常见几小时),TCP半开较短,UDP更短(几十秒到几分钟)。这个差异带来一个经典问题:长连接的UDP应用(比如某些心跳类业务)如果心跳间隔大于老化时间,中间的映射表项会被清掉,后续包就被当成新连接处理,表现为"每隔一段时间就闪断一次"。这个现象特别难查,因为你在终端侧什么都看不出来。
所以排查NAT问题,第一个动作永远是看会话表,而不是猜。
2.2 转换发生在哪一跳
转换发生的时机,比转换规则本身更容易出错。原则上是:源地址转换发生在报文离开内网侧接口时,目的地址转换发生在报文进入内网侧接口前。这句话听着绕,画到具体路径上就清楚了。
假设设备有两个接口:内网侧(连接 192.168.10.0/24)和外网侧(203.0.113.10)。内网终端访问外网,报文先到内网侧接口,此时做的是"判断是否需要进行源转换"的标记;报文准备从外网侧发出时,才真正执行源地址重写。反过来,外网进来的报文,先在外网侧接口被目的转换规则命中,改成内网地址,然后转发到内网侧接口。
为什么顺序这样设计?因为设备需要先知道"这个报文要去哪里",才能决定往哪个方向做转换。如果顺序搞反,会出现转换了但路由不出去、或者路由出去了但没转换的情况。
工程上有两个由此衍生的坑,值得单独提。
坑一:转换规则和路由必须对得上。我见过一种配置,DNAT把目的地址改成了 192.168.20.20,但设备上的路由表里 192.168.20.0/24 指向的是另一台设备,结果报文被转换后又被送回了错误的方向,形成环路或者直接丢弃。查这种问题,先看路由再看规则,顺序别反。
坑二:同一台设备上的接口方向标记要准确。有些设备的配置里需要显式指定内网侧和外网侧,标错了就会出现"CPU跑满但流量不通"的诡异现象——因为报文在两个方向上都匹配不到转换规则,一直走慢路径。
2.3 PAT端口分配与耗尽的真实边界
这一节我想算点具体的数,因为"端口会耗尽"这句话大家都会说,但多数人不知道边界在哪,也就不知道该不该担心。
一个公网IPv4地址,传输层端口是16位,范围 0 到 65535,共 65536 个。实际可用要扣掉 0 到 1023 这段知名端口(虽然做转换时技术上可以用,但为了避免和对端服务端口冲突,绝大多数实现会避开)、以及实现上的保留段。粗略按64000 个可用端口估算,是工程上常用的口径。
关键在这里:这 64000 个端口是按"目的地址+目的端口"分别计数的,不是全局共享。也就是说:
- 内网终端访问 198.51.100.10:443,这一组映射最多约 64000 条并发;
- 同时访问 198.51.100.11:443,又是另外 64000 条;
- 访问同一台服务器的不同端口,也是分开算的。
所以真正会出现端口耗尽的场景,是大量内网终端同时挤向同一个目的IP、同一个目的端口。最典型的是所有人都访问同一个云服务商的同一个服务地址,或者大量终端同时向同一个更新服务器拉包。
再叠加一个因素:TCP的TIME_WAIT。连接正常关闭后,端口不会立刻释放,要等一段时间(通常几十秒到两分钟)。如果业务是"大量短连接、高频建连"的模式,比如老式的短轮询接口、某些监控上报,那么有效可用端口会被大量TIME_WAIT占用,实际并发能力远低于 64000。我见过一个极端案例:某业务每秒建几百个短连接访问同一个外部接口,不到半小时就把出口地址的端口打满了,表现是新连接请求全部超时,而链路带宽只用了不到 5%。
怎么缓解?我按优先级给几个手段:一是加公网地址,用地址池把压力摊开,这是最直接的;二是优化应用侧建连方式,改成连接复用、长连接、或者加本地缓存减少外部请求,这治本;三是调整TIME_WAIT回收策略,这个要谨慎,改过头会影响连接可靠性;四是限制单主机的并发,防止个别终端把端口吃光。
这里顺带说一句排查手法:当你怀疑端口耗尽时,看设备上"当前会话数/规格上限"以及"单个公网地址的映射数"这两个数字,比看带宽利用率有用得多。
2.4 ALG:那些被协议坑过的场景
有一类协议,光改IP和端口是不够的,因为它把地址信息写在了应用层的载荷里。最典型的是FTP:控制连接建立后,双方会通过PORT或PASV命令协商数据连接的地址和端口。如果只做三层四层的转换,控制连接能通,但载荷里的地址还是私网地址,对端根本没法连——于是就出现了那个经典现象:能登录,但列不出目录、传不了文件。
这种场景就需要ALG(应用层网关)介入,它会在转换报文头的同时,顺带改写载荷里的地址字段。类似的协议还有SIP(语音信令里带着媒体地址)、某些数据库或管理协议、以及一些老式的多媒体协议。
ALG这东西,我的态度很明确:能用就别关,但一定要知道它会带来什么副作用。
副作用主要有两个。一是它增加了设备对应用层内容的解析负担,某些实现里ALG会显著拉低转发性能;二是它对协议的解析不一定完备,遇到加密的信令(比如加密的SIP)就完全失效,这时候只能靠其他办法——比如让服务器侧直接配置公网地址、或者用专门的会话边界设备。
还有一个非常隐蔽的坑:ALG改写了载荷,但应用层有自己的完整性校验。某些协议会对载荷做签名或者哈希,ALG一改,校验就失败,连接被对端拒绝。这种问题抓包看是"连接建立成功、随后立刻被RST",非常难定位。我的经验是:新上一类业务,先在测试环境单独验证,重点看它有没有在载荷里带地址,有的话就专门测一遍。
到这里,第一章的"为什么"和"改了什么"就算讲完了。接下来我把它落到具体操作上。
3. 动手搭一套可复现的NAT实验
3.1 拓扑与地址规划
纸上谈兵没意义,我给一套你在自己电脑上就能搭出来的环境。用虚拟机或者容器都行,三台就够:一台当"内网终端",一台当"出口设备",一台当"外部服务端"。
地址规划我全部用文档保留地址段,避免和你现有环境冲突:
| 角色 | 接口 | 地址 | 说明 |
|---|---|---|---|
| 内网终端 | eth0 | 192.168.10.15/24,网关 192.168.10.1 | 模拟普通办公终端 |
| 出口设备 | eth0(内网侧) | 192.168.10.1/24 | 内网网关 |
| 出口设备 | eth1(外网侧) | 203.0.113.10/24,网关 203.0.113.1 | 模拟公网地址 |
| 内部服务器 | eth0 | 192.168.10.20/24,网关 192.168.10.1 | 模拟对外发布的服务 |
| 外部服务端 | eth0 | 198.51.100.50/24,网关 198.51.100.1 | 模拟互联网上的对端 |
这套规划的好处是:地址段是标准保留的,不会和你家里的路由器撞;同时把"内网服务器"和"内网终端"放在同一网段,方便后面演示hairpin回流。
第一步永远是打开转发。这一步漏了,后面所有配置都白做,而且现象是"内网能ping通网关,但出不去",非常容易误判成NAT规则写错了。
sysctl -w net.ipv4.ip_forward=1 # 永久生效写进配置文件 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p注意:在容器里做实验时,转发开关可能是隔离的命名空间级配置,宿主机开了不代表容器里开了,要在容器内部再确认一次。
3.2 用netfilter实现SNAT与DNAT
我选Linux上的netfilter做演示,原因很简单:它规则透明、日志清晰、每台机器都能跑,不像专用设备那样黑盒。理解了这里的机制,换到任何设备上都是同一套思路,只是命令写法不同。
先做最基础的源地址转换,让内网终端能上网:
# 老一点的写法,用iptables iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth1 -j SNAT --to-source 203.0.113.10 # 出口地址是动态的时候,用MASQUERADE自动取接口地址 iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth1 -j MASQUERADE这两条的区别值得说清楚:SNAT --to-source是写死一个地址,适合固定地址出口;MASQUERADE是每次动态读取出口接口的地址,适合拨号或动态分配的场景,代价是每次都要查接口地址,性能略低一点点。出口地址固定就用SNAT,动态才用MASQUERADE,这是个习惯问题,但养成之后能避免"地址变了规则不跟着变"的故障。
同样的规则用nftables写,语法更现代:
nft add table ip nat nft add chain ip nat postrouting '{ type nat hook postrouting priority srcnat; }' nft add rule ip nat postrouting ip saddr 192.168.10.0/24 oifname "eth1" snat to 203.0.113.10接着做目的转换,把外部对 203.0.113.10:8443 的访问转到内网服务器的 443:
iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8443 -j DNAT --to-destination 192.168.10.20:443配完这条,从外部服务端去访问 203.0.113.10:8443,理论上应该能通。如果通不了,按这个顺序查:一是确认外部服务端的路由能不能到 203.0.113.10(实验环境里经常忘了给外部服务端配网关);二是确认内网服务器有没有开监听、防火墙有没有放行;三是确认回程——内网服务器的默认网关必须是 192.168.10.1,如果它把回包发到别处,连接就建立不起来。
第三种情况我要重点提醒,因为它和直觉相反。很多人以为DNAT做完就完事了,实际上报文被送进内网后,服务器看到的源地址是 198.51.100.50,它回包的目的地址就是 198.51.100.50,走默认网关出去,正好经过出口设备,此时出口设备查到会话表里有这条记录,会把源地址改回 203.0.113.10——这个过程是自动的,不需要额外配SNAT,前提是回程路径经过同一台设备。
但如果内网服务器有多个网卡、或者回程走了另一条链路,会话表就查不到,回包会带着内网地址直接出去,被上游丢弃,表现为"外部访问超时、服务器侧看连接已建立"。这种"单通"现象极其常见,我的处理原则是:做DNAT的环境,一定要保证回程对称,做不到对称就得显式加一条SNAT把回程拉回来。
3.3 hairpin回流与端口映射
现在有个新需求:内网终端 192.168.10.15 想访问自己公司发布在公网的业务,输入的是 203.0.113.10:8443。按直觉,这条流量从内网侧进、又要从内网侧出,应该能被处理。但默认情况下,它不通。
原因还是会话表和转换顺序。报文从内网侧接口进来,目的地址是 203.0.113.10,设备按目的转换规则把它改成 192.168.10.20,然后要转发回内网侧接口。此时报文源地址还是 192.168.10.15,服务器收到后回包给 192.168.10.15,走二层直接送达,根本不经过出口设备——所以内网终端收到的是一个源地址为 192.168.10.20 的响应,而它期望的是 203.0.113.10,TCP握手直接失败。
解决办法是让回程也必须经过设备,也就是在进入内网时再做一次源地址转换,把源地址改成设备的内网侧地址,这样服务器回包就送到设备,设备查会话表再改回去。配置上加一条:
# 对来自内网的、目的为公网地址的流量,做源转换,强制回程经过设备 iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -d 192.168.10.20 -p tcp --dport 443 -j SNAT --to-source 192.168.10.1这条规则的作用是"内网访问内网服务器时,把源地址改成网关地址"。看起来多此一举,实际上是hairpin能通的关键。
注意:这条规则要写得足够精确,只匹配需要回流的那部分流量。写宽了会波及正常的内网互访,导致内网通信全部绕行设备,性能下降不说,还会引入额外的单点故障。
端口映射这块还有个实操细节:尽量做端口对端口,别做端口对端口段的批量映射。有些管理员图省事,把 8000-8100 整段映射进去,结果暴露了一堆自己都不知道在跑的服务。我的习惯是一个服务一条规则,写清楚注释,定期清理。
3.4 网络设备侧的配置思路对照
前面用netfilter讲机制,是因为它把每一步都摊开了。换到专用网络设备上,命令长得不一样,但逻辑是同一套。我把对应关系列出来,你照着套就行:
| 要做的事 | 通用配置思路 | 关键关注点 |
|---|---|---|
| 内网出口上网 | 定义内网侧/外网侧接口,配源转换规则,指定出口地址或地址池 | 接口方向标记别搞反;地址池容量要大于并发主机数 |
| 服务对外发布 | 配目的转换(公网IP:端口 到 内网IP:端口),必要时同步配回程源转换 | 回程对称性;安全策略要放行转换后的真实地址 |
| 双向互访 | 同时配源转换和目的转换 | 转换顺序、匹配精确度 |
| 多出口选路 | 按源地址或按目的地址分流,配合不同转换规则 | 会话粘性,避免同一连接来回切链路 |
这里有个经验:设备上的安全策略匹配的是转换后的地址。这一点和很多人的直觉相反——他们写了允许访问 203.0.113.10:8443 的策略,结果不通,因为报文进到策略检查点时目的地址已经被改成 192.168.10.20 了。所以策略要放行真实的内网地址。这个坑我在至少三个不同环境里见过,每次都要花时间解释。
还有一个部署顺序的建议:先在设备上把转换规则和安全策略都写好,再做连通性测试,不要一边加规则一边测。因为每加一条规则可能改变前面测试的结论,混着测很容易得出错误判断。
4. 场景化拆解:不同需求下怎么选、怎么配
4.1 小型出口与家庭网关
这个场景的典型特征是:出口地址可能是动态的、终端数量在几十台以内、没有人专职维护。这种情况下我的建议非常明确——只用PAT,不要碰地址池和静态映射。
理由有三条。第一,动态地址下做静态映射根本做不了,规则会随地址变化而失效,除非配合地址变化时的自动更新机制,但那又增加了复杂度。第二,几十台终端的并发压力完全在单地址的能力范围内,前面算过,单个地址对单个目的地址的并发上限是六万多,日常办公根本摸不到边。第三,越简单的配置越不容易出故障,而这类环境最怕的就是"出了故障没人会修"。
配置上就是把内网段整体做源转换,出口用自动取地址的方式。唯一需要注意的是别把管理地址也一起转换了,否则从外部访问管理界面会失效。做法是在规则里排除掉管理网段,或者把管理口单独放在一个不做转换的接口上。
另外提一句动态地址带来的一个实际问题:地址变化后,原有的会话表项会失效,表现为"网断了十几秒又自己好了"。这在拨号环境里是正常现象,不用当成故障去查。如果业务对断线敏感,那就该考虑固定地址了。
4.2 多出口链路与地址池
企业有一定规模之后,常见的是两条出口链路,一条主用一条备用,或者两条按业务分流。这时候NAT的设计要跟着路由策略一起考虑,不能单独看。
先说地址池的容量问题。假设两条链路各有一个公网地址,内网有两百台终端。如果你给每条链路各配一个地址的池子,那么每条链路最多承载六万多条到同一目的的并发,正常办公够用;但如果业务里有大量短连接访问同一个外部服务,就要警惕TIME_WAIT堆积。这种情况我建议直接上多地址池,把压力摊开。
再说分流。按源地址分流的做法是:把内网按部门划成两段,一段走链路A、一段走链路B,各自的转换规则绑定各自的出口地址。这种做法简单、会话粘性好,缺点是流量分布不均时不好调。按目的地址分流更精细,但需要维护地址列表,而且新业务上线容易漏配。
我踩过的一个坑是:链路切换时会话中断。主链路断了,路由切到备链路,但原有的会话表项还绑在主链路的出口地址上,这些连接就废了。表现是"切换后一部分人能用、一部分人不行",非常迷惑。解决办法一是让会话表随链路状态一起清理,二是应用侧做重连重试。前者是设备能力问题,后者是应用健壮性问题,两边都得考虑。
4.3 内网服务对外发布
服务发布看起来是最简单的需求:一条目的转换规则的事。但实际工程里要考虑的东西不少。
第一个是端口选择。能不占80/443就不占,尽量用高位端口,减少被自动扫描命中的概率。这不是安全机制,但能显著降低噪音。
第二个是只映射必要端口。我见过把整台服务器的所有端口都映射出去的环境,理由是"方便调试",结果半年后发现有台机器上跑着测试用的管理服务,一直暴露在外。我的做法是列一张清单,写清楚每个映射对应的业务、负责人、开通时间,定期复核。
第三个是回程的可靠性。前面说过,回程必须对称。如果内网服务器有多个网卡做业务隔离,一定要确认它的默认路由指向做转换的那台设备,或者在做转换的设备上显式加回程源转换。这条我建议写进上线检查清单,因为它的故障表现(外部超时、内部看连接正常)太容易误判了。
第四个是日志。服务发布的流量一定要能追溯到源地址。如果做了源转换,原始源地址就丢了,排查攻击或者异常访问时会很被动。能让设备记录转换前的源地址就一定要记,实在不行就在业务侧做应用层日志。
4.4 容器与虚拟化环境的NAT
容器环境的网络模型,本质上就是把NAT做进了宿主机:容器有自己的地址空间,出宿主机时做源转换,外部访问容器时做目的转换。理解了前面几节,这里的现象就都好解释了。
最常见的困惑是"容器里看不到真实客户端地址"。因为出宿主机时做了源转换,服务端看到的源地址是宿主机的地址。如果业务需要真实客户端地址(比如做限流、做地域统计),就得改网络模式,或者让上游把真实地址通过应用层头部带过来。
第二个困惑是"容器访问自己映射出去的地址不通"。这就是 3.3 节讲的hairpin问题在容器场景的翻版,解决方案的思路完全一样:让回程也经过做转换的那一层。
第三个是端口冲突。宿主机的端口是全局资源,两个容器抢同一个宿主机端口就起不来。这个不是NAT的问题,但排查时容易往NAT上想。我的经验是:容器起不来先看端口占用,别急着怀疑网络配置。
4.5 地址重叠与双向NAT
这个场景比较特殊,但一旦遇上就很棘手:两个内网用了相同的地址段,现在要互访。
比如某公司有两个分部,都用 192.168.10.0/24,现在要打通。直接路由是不行的,因为地址冲突。这时候就要在中间做双向转换:把A方的 192.168.10.0/24 转换成一个虚拟网段(比如 172.16.10.0/24),B方访问 172.16.10.x 时,中间设备把它转换成对应的 192.168.10.x;反过来B方的地址也要做类似转换。
配置上有两个要点。一是转换要成对:去方向改目的地址,回方向改源地址,两条规则必须严格对应,任何一条写错都会导致单向不通。二是转换后的虚拟网段不能和现有任何网段冲突,规划时要留足余量。
这种方案我不太推荐长期使用,因为它让地址规划变得复杂,后续每加一个网段都要维护一堆规则。如果条件允许,还是推动一侧做地址重规划,一次性解决问题。但在不能改动现有环境的前提下,双向NAT是可行的过渡方案。
5. 排障实录:症状、定位路径与速查表
5.1 两个必会动作:看会话、抓包
遇到NAT相关问题,我固定做两个动作,顺序不能乱。
第一个动作是看会话表。在Linux上就是看连接跟踪表:
# 看当前的连接跟踪记录 conntrack -L # 只看某个地址相关的 conntrack -L | grep 192.168.10.20 # 统计当前会话总数 conntrack -C在专用设备上通常是类似的"会话/连接"查看命令。这一步能回答三个关键问题:连接有没有建立(有表项说明至少有一个方向的包到达过)、转换关系对不对(表项里的原始五元组和转换后五元组)、状态是什么(是否卡在半开)。
第二个动作是在三个点上抓包:内网侧、外网侧、以及服务端。三个点的抓包对比,基本能把问题锁死。判断逻辑很简单:
| 内网侧 | 外网侧 | 结论 |
|---|---|---|
| 有请求,源地址是内网地址 | 无 | 转换或转发没生效,查规则和转发开关 |
| 有请求,源地址是内网地址 | 有请求,源地址是公网地址 | 转换正常,问题在对端或回程 |
| 有请求 | 有请求,但回包没有从外网侧进来 | 回程路由问题 |
| 有请求,无回包 | 有请求,无回包 | 对端没响应,或中间被拦 |
抓包的时候记住一件事:在做过转换的一侧抓,看到的地址和终端上看到的不是一回事,这是正常的,别以为自己抓错了接口。我见过有同事因为这个怀疑抓包工具坏了,浪费了不少时间。
5.2 高频故障速查表
下面这张表是我这些年整理出来的,基本覆盖了八成以上的现场问题。你可以直接拿去当检查单。
| 现象 | 大概率原因 | 快速验证 | 处理方向 |
|---|---|---|---|
| 内网能ping网关,上不了外网 | 转发未开启,或源转换规则未命中 | 查转发开关;看外网侧抓包有无转换后的流量 | 开启转发;核对规则的源地址范围和出接口 |
| 外部访问服务超时,内部看已连接 | 回程路径不对称 | 在服务端抓包看回包目的地址 | 修默认网关,或显式加回程源转换 |
| 服务发布后,内网用户访问公网地址不通 | hairpin未处理 | 内网侧抓包看回包源地址 | 增加内网进入时的源转换规则 |
| 能登录但传不了文件(FTP类) | 应用层载荷地址未改写 | 抓包看载荷里的地址字段 | 开启对应协议的ALG,或改用被动模式、服务端配公网地址 |
| 大并发下新连接失败,带宽却很低 | 端口耗尽或会话数超限 | 统计单个公网地址的映射数 | 加公网地址、优化建连方式、限制单机并发 |
| 每隔固定时间闪断 | UDP老化时间短于心跳间隔 | 对比心跳间隔和老化时间 | 调整老化时间,或缩短心跳间隔 |
| 转换规则看着没问题但不通 | 接口方向标记错误,或安全策略匹配的是转换后地址 | 检查接口角色定义;检查策略里的地址 | 修正方向;策略放行真实内网地址 |
| 换运营商后部分业务不通 | 出口地址变化,静态映射失效 | 核对当前出口地址和规则里的地址 | 静态场景改固定地址,或改用动态适配方式 |
5.3 不报错但很折磨人的细节
有些问题不会给你任何错误提示,只能靠经验。
第一个是MTU。做隧道封装或者某些转换场景时,报文长度会超出链路MTU,需要分片,但分片报文可能被中间设备丢弃。表现是"小包能通、大包卡住"——ping得通,网页打开一半、文件传到一定大小就断。标准做法是在转发路径上做MSS钳制:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu这条规则的作用是在握手阶段就把双方协商的MSS值压到路径MTU以下,避免后续大包被丢。这个坑我在做跨网络互联时遇到过好几次,每次现象都一样:登录正常、页面加载到一半卡死。
第二个是分片报文的转换。非首片报文里没有传输层头,只有IP头,如果转换规则依赖端口匹配,非首片就匹配不上。正常实现会用会话表把分片关联起来,但某些情况下(比如分片乱序、或者会话表被清)会关联失败。表现是"大数据传输丢包、小请求正常"。遇到这种情况,先确认路径MTU是否合理,能不让它分片就别让它分片。
第三个是时间同步。这个听起来和NAT没关系,但排查时会要命:如果设备和日志服务器时间不同步,你按时间戳去对齐两边的日志,会发现完全对不上。我现在的习惯是,做任何跨设备排查之前,先确认时间同步正常。
第四个是"配置正确但顺序错"。规则是有匹配顺序的,先命中的生效。如果前面有一条宽泛的规则,后面的精确规则永远不会被执行。这种问题在规则越加越多之后特别容易出现。我的做法是:新增规则时,先确认它应该插在哪个位置,而不是无脑追加到末尾;同时定期做一次规则梳理,把失效的、重复的清掉。
5.4 我踩过的坑与避坑清单
最后分享几条实打实的经验,都是踩过之后才记住的。
第一条:不要在生产环境边配边测。我有一次在出口设备上加服务发布规则,加一条测一次,结果测到第四条的时候前面三条的表现全变了——因为规则之间的匹配顺序被改变了。后来我改成先把规则全部写完、再统一验证,效率反而高。这个习惯一直保留到现在。
第二条:所有转换规则都要有注释和台账。一条规则写"映射8443到内网某服务器",半年后没人知道是哪台、干什么用的。我的做法是规则里写清楚业务名,另外维护一张表记录业务、负责人、开通时间、审核记录。听起来麻烦,但清理和审计的时候能救命。
第三条:改之前先备份,改之后先看会话。配置备份是基本操作,但"改完看会话"这一步很多人省了。改完立刻看会话表,能第一时间发现"规则写反了导致所有连接重建"这种问题,比等用户投诉快得多。
第四条:别用NAT去实现本该用策略做的事。有人喜欢用地址转换来做访问控制,比如"只有转换到这个地址的才能访问"。这种做法隐蔽、难维护、而且容易出安全漏洞。访问控制就该在策略层做,转换层只负责转换。
第五条:留出排障通道。做服务发布的时候,一定给自己留一条能进去的路,别把所有入口都封死。我见过有人配错了规则,把自己锁在外面,最后只能去机房接console。这个教训值一次现场跑腿。
第六条:关注老化时间和业务心跳的匹配。这个前面提过,但值得再说一次。上线任何长连接业务之前,对一下心跳间隔和设备的老化时间,别等出问题再回头查。一般建议心跳间隔不超过老化时间的一半,留足余量。
这套东西讲下来,其实核心就一句话:NAT是会话状态的重写,不是简单的地址替换。你把会话表当成主线去理解每一个现象,绝大多数问题都能自己推出来。剩下的那些推不出来的,抓三个点的包,基本也就清楚了。