ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

网络地址转换NAT:会话表、端口复用与排障实战

网络地址转换NAT:会话表、端口复用与排障实战

翻到这一章,说明前面的路由表、子网划分、报文转发流程已经打过一遍底了。网络地址转换这个题目,初学者往往觉得简单——不就是把私网地址换成一个公网地址吗?可真到生产环境里出问题,能让人连加三个小时班:语音通了视频不通、内部员工访问不了自己公司发布在公网的业务、大并发下莫名其妙丢连接、抓包看到源地址怎么都对不上。我做了这么多年网络与系统这块,见过太多"配置看起来没问题、就是不通"的现场,九成情况下问题不在路由,而在对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 拓扑与地址规划

纸上谈兵没意义,我给一套你在自己电脑上就能搭出来的环境。用虚拟机或者容器都行,三台就够:一台当"内网终端",一台当"出口设备",一台当"外部服务端"。

地址规划我全部用文档保留地址段,避免和你现有环境冲突:

角色接口地址说明
内网终端eth0192.168.10.15/24,网关 192.168.10.1模拟普通办公终端
出口设备eth0(内网侧)192.168.10.1/24内网网关
出口设备eth1(外网侧)203.0.113.10/24,网关 203.0.113.1模拟公网地址
内部服务器eth0192.168.10.20/24,网关 192.168.10.1模拟对外发布的服务
外部服务端eth0198.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是会话状态的重写,不是简单的地址替换。你把会话表当成主线去理解每一个现象,绝大多数问题都能自己推出来。剩下的那些推不出来的,抓三个点的包,基本也就清楚了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进