资讯动态

iptables NAT实战:SNAT与DNAT原理、配置及避坑指南

发布时间:2026/10/22 11:56:33 来源:尧图企业网站定制
1. 为什么网络出口总在关键时刻掉链子做运维这行的估计都遇到过这种场景内网一堆机器要访问外网但公网IP就一个怎么让几十台设备同时上网或者公司有个内部服务跑在192.168.1.100的8080端口上外部用户想访问怎么把请求转发进去再或者两台内网机器之间要通信但中间隔了个防火墙数据包怎么才能顺利穿过这些问题的答案都指向同一个东西——iptables的NAT功能。具体来说就是SNAT和DNAT这两个核心机制。我见过太多人在这上面栽跟头。有人配了SNAT结果内网机器上不了网有人设了DNAT但外部访问超时还有人改完规则把自己SSH连接给断了只能跑机房插显示器救急。这些坑我都踩过而且不止一次。这篇文章就是要把SNAT和DNAT这件事彻底讲透。从原理到实操从参数计算到排错技巧从最基础的场景到稍微复杂的组合用法我都会用大白话加实际案例的方式说清楚。不管你是刚接触Linux网络的新手还是想系统梳理NAT知识的老手应该都能从里面找到有用的东西。先明确一下这两个概念到底在干什么。SNAT全称Source Network Address Translation源地址转换解决的是“内网机器怎么访问外网”的问题。DNAT全称Destination Network Address Translation目标地址转换解决的是“外网怎么访问内网服务”的问题。一个管出一个管进方向正好相反。理解这两个方向后面所有的配置逻辑就都顺了。2. 搞懂NAT之前先把iptables的表和链理清楚2.1 四张表五条链NAT到底在哪一层干活iptables里面有四张表filter、nat、mangle、raw。很多人一上来就背命令结果连数据包走哪张表都没搞明白出了问题根本无从下手。filter表管过滤就是允许还是拒绝。nat表管地址转换SNAT和DNAT都在这张表里。mangle表管修改数据包内容比如改TTL。raw表管连接跟踪前的处理用得比较少。nat表里面又有三条链PREROUTING、POSTROUTING、OUTPUT。这里的关键是理解数据包的流向。一个数据包从网卡进来先走PREROUTING链。这时候路由决策还没做所以DNAT必须在这里做因为你要在路由之前就把目标地址改了不然内核不知道该把包往哪送。数据包经过路由决策后如果是发给本机的走INPUT链如果是需要转发的走FORWARD链。转发完之后在数据包离开网卡之前走POSTROUTING链。SNAT必须在这里做因为这时候路由已经做完了你知道包要从哪个网卡出去了才能把源地址改成那个网卡的地址。记住这个顺序进来先PREROUTING做DNAT出去前POSTROUTING做SNAT。搞反了配置肯定不生效。2.2 连接跟踪NAT能工作的幕后功臣很多人不知道NAT能正常工作靠的是内核的conntrack机制。当第一个数据包经过NAT规则时内核会在连接跟踪表里记一笔“这个连接从192.168.1.100:12345发往8.8.8.8:53被SNAT成了公网IP:54321”。后续这个连接的所有数据包都会自动按照这个记录来转换不需要每条都去匹配规则。这解释了一个常见现象你配了SNAT内网机器能上网了但如果你把规则删了已经建立的连接还能继续用一段时间。因为conntrack表里还有记录。要立即生效得清一下连接跟踪表。查看连接跟踪表的命令是conntrack -L如果没装conntrack工具可以直接看/proc/net/nf_conntrack文件。这个文件在排错时特别有用能看到每个连接的转换关系。2.3 内核转发开关不打开这个一切白搭这是新手最容易忽略的一步。Linux默认是不转发数据包的也就是说从eth0进来的包不会从eth1出去。你必须手动打开转发功能。临时打开echo 1 /proc/sys/net/ipv4/ip_forward永久生效在/etc/sysctl.conf里加上net.ipv4.ip_forward 1然后执行sysctl -p。我见过有人配了半天NAT就是不生效最后发现是转发开关没开。这个坑太经典了几乎每个搞网络的人都踩过。所以养成习惯配NAT之前先确认这个开关的状态。3. SNAT实战让内网机器集体上网3.1 最简单的SNAT场景与配置假设你有一台Linux服务器做网关它有两个网卡eth0连内网IP是192.168.1.1eth1连外网IP是203.0.113.5。内网有一台机器192.168.1.100想访问外网的8.8.8.8。在网关上执行iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to-source 203.0.113.5这条命令的意思是在POSTROUTING链上对于源地址是192.168.1.0/24网段、从eth1出去的包把源地址改成203.0.113.5。拆解一下各个参数-t nat指定操作nat表-A POSTROUTING追加到POSTROUTING链-s 192.168.1.0/24匹配源地址-o eth1匹配出接口-j SNAT跳转到SNAT目标--to-source 203.0.113.5指定转换后的源地址配完之后内网机器192.168.1.100的默认网关要指向192.168.1.1。然后它访问8.8.8.8数据包源地址会被改成203.0.113.5外网看到的请求就来自这台网关。3.2 MASQUERADE动态IP场景的救星上面的配置有个前提外网IP是固定的203.0.113.5。但如果你的外网IP是动态获取的比如拨号上网或者DHCPIP随时可能变那写死的SNAT就不行了。这时候用MASQUERADEiptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j MASQUERADEMASQUERADE和SNAT的区别在于它不需要指定--to-source会自动使用出接口当前的IP地址。代价是每次转换都要去查一下接口IP性能比SNAT略差。所以如果IP固定优先用SNATIP动态才用MASQUERADE。实操心得MASQUERADE虽然方便但在高并发场景下它的性能损耗不可忽视。我之前有个项目内网上千台机器用MASQUERADE时网关CPU占用明显偏高换成固定IP的SNAT后降了不少。所以能固定IP就固定IP。3.3 多公网IP的SNAT策略有些场景下网关有多个公网IP比如203.0.113.5和203.0.113.6。你希望不同的内网机器用不同的公网IP出去怎么做可以按源地址区分iptables -t nat -A POSTROUTING -s 192.168.1.0/25 -o eth1 -j SNAT --to-source 203.0.113.5 iptables -t nat -A POSTROUTING -s 192.168.1.128/25 -o eth1 -j SNAT --to-source 203.0.113.6这样192.168.1.1到192.168.1.127的机器走第一个IP192.168.1.128到192.168.1.255的走第二个IP。也可以按目标地址区分比如访问某些特定服务走特定IPiptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 198.51.100.0/24 -o eth1 -j SNAT --to-source 203.0.113.6 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to-source 203.0.113.5注意顺序先匹配特定目标再匹配通用规则。iptables是从上往下匹配的匹配到就跳转不再继续。3.4 SNAT的端口转换细节SNAT在转换源地址的同时也会转换源端口。因为多个内网机器可能同时用同一个源端口访问同一个目标如果不改端口回来的包就分不清是给谁的。内核会自动处理端口转换你不需要手动指定。但有一个细节值得注意默认情况下内核会尽量保持源端口不变如果端口冲突了才换。这个行为可以通过--random参数改变让内核随机选择源端口。iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to-source 203.0.113.5 --random什么时候需要--random当你的内网机器访问的目标服务对源端口有要求时或者你想避免端口预测攻击时。不过大多数场景下默认行为就够了。4. DNAT实战把外部请求引到内部服务4.1 最基础的端口映射配置DNAT的典型场景是端口映射。假设你有一台内网服务器192.168.1.100上面跑了个Web服务在8080端口。现在想让外部用户通过网关的公网IP 203.0.113.5的80端口访问这个服务。在网关上执行iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080这条命令的意思是在PREROUTING链上对于目标地址是203.0.113.5、协议是TCP、目标端口是80的包把目标地址改成192.168.1.100目标端口改成8080。注意几个关键点DNAT在PREROUTING链上做不是POSTROUTING-d匹配的是原始目标地址也就是公网IP--to-destination指定转换后的地址和端口配完这条规则外部用户访问203.0.113.5:80请求就会被转发到192.168.1.100:8080。但是光配DNAT还不够。你还需要确保FORWARD链允许这个流量通过。因为数据包经过DNAT后目标地址变了需要被转发到内网这就要经过FORWARD链。iptables -A FORWARD -d 192.168.1.100 -p tcp --dport 8080 -j ACCEPT很多人配了DNAT发现不生效就是忘了加FORWARD规则。这个坑我踩过不止一次后来养成了习惯配完DNAT立刻检查FORWARD链。4.2 端口映射的几种变体只换目标地址不换端口iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100这样外部访问80端口转发到内网的80端口。换目标地址也换端口iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080端口范围映射iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 8000:8100 -j DNAT --to-destination 192.168.1.100这样8000到8100的端口都会转发到192.168.1.100的对应端口。转发到多个后端iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.101:8080但这样配有个问题iptables是从上往下匹配的第一条匹配到就跳转了第二条永远不会生效。要实现负载均衡得用statistic模块或者random模块iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -m statistic --mode random --probability 0.5 -j DNAT --to-destination 192.168.1.100:8080 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.101:8080第一条规则有50%的概率匹配匹配到就转发到100没匹配到就继续往下走被第二条规则转发到101。这样就实现了简单的负载均衡。注意这种负载均衡是基于连接的不是基于请求的。同一个连接的后续数据包会走conntrack记录不会重新匹配规则。所以对于HTTP这种短连接效果还行对于长连接可能会不均衡。4.3 内网用户访问内网服务的回环问题这是一个非常经典的坑。假设你在网关上配了DNAT把203.0.113.5:80转发到192.168.1.100:8080。外部用户访问没问题。但内网用户192.168.1.200想访问203.0.113.5:80会发生什么数据包从192.168.1.200发出目标地址是203.0.113.5。这个包先到网关走PREROUTING链被DNAT规则匹配目标地址改成192.168.1.100:8080。然后路由决策发现目标地址是内网地址需要从eth0发出去。但是源地址还是192.168.1.200目标地址是192.168.1.100。192.168.1.100收到包后发现源地址是同网段的192.168.1.200就直接回复给192.168.1.200了回复包的源地址是192.168.1.100而不是203.0.113.5。192.168.1.200收到回复发现源地址是192.168.1.100但它请求的是203.0.113.5对不上就把包丢了。这就是所谓的“回环问题”或“NAT回环”。解决方法有两种方法一加SNAT规则iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 8080 -j SNAT --to-source 192.168.1.1这样内网用户访问时源地址被改成网关的内网地址192.168.1.1。192.168.1.100回复给192.168.1.1网关再根据conntrack记录把包转回给192.168.1.200。方法二用MASQUERADEiptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.0/24 -o eth0 -j MASQUERADE效果类似但更通用一些。我个人的习惯是只要配了DNAT就顺手把回环的SNAT也配上。虽然多一条规则但省去了后面排查问题的时间。5. SNAT与DNAT组合使用与进阶技巧5.1 双向NAT同时做SNAT和DNAT有些场景下你需要同时做SNAT和DNAT。比如内网服务器192.168.1.100要访问外部服务198.51.100.50但外部服务只接受来自特定IP的请求。同时你希望外部服务看到的请求来自203.0.113.5而不是内网地址。这时候需要两条规则iptables -t nat -A POSTROUTING -s 192.168.1.100 -d 198.51.100.50 -j SNAT --to-source 203.0.113.5 iptables -t nat -A PREROUTING -s 198.51.100.50 -d 203.0.113.5 -j DNAT --to-destination 192.168.1.100第一条把出去的包源地址改成203.0.113.5第二条把回来的包目标地址改回192.168.1.100。这样双向都做了转换通信才能正常。5.2 用DNAT做端口转发到不同机器有时候你希望根据不同的源地址把请求转发到不同的后端。比如内部用户访问80端口转发到测试服务器外部用户访问80端口转发到生产服务器。iptables -t nat -A PREROUTING -s 192.168.1.0/24 -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.101:8080第一条匹配内网源地址转发到测试服务器第二条匹配所有其他源地址转发到生产服务器。注意顺序内网规则要放在前面。5.3 保存和恢复iptables规则iptables规则默认是存在内存里的重启就没了。所以配好之后一定要保存。在Debian/Ubuntu系apt install iptables-persistent netfilter-persistent save在RHEL/CentOS系service iptables save或者用iptables-save和iptables-restore手动保存恢复iptables-save /etc/iptables/rules.v4 iptables-restore /etc/iptables/rules.v4我个人的习惯是每次改完规则先iptables-save看一眼确认没问题再保存到文件。因为有时候规则写错了保存下去重启就直接失联了。实操心得改iptables规则之前一定要先确认自己有其他方式能连上机器。我一般会开两个SSH会话一个用来改规则另一个留着备用。如果改完第一个会话断了第二个还能进去恢复。这个习惯救过我很多次。6. 常见问题排查与避坑指南6.1 SNAT不生效的排查思路症状内网机器配了网关但上不了网。排查步骤确认转发开关开了cat /proc/sys/net/ipv4/ip_forward应该是1。确认SNAT规则存在iptables -t nat -L POSTROUTING -n -v看规则有没有被匹配到。如果pkts是0说明没匹配到。确认内网机器的网关指向了正确地址。确认FORWARD链允许转发iptables -L FORWARD -n -v。如果默认策略是DROP需要加允许规则。抓包确认在网关的eth1上抓包看源地址有没有被转换。tcpdump -i eth1 -n host 8.8.8.8如果看到源地址还是192.168.1.100说明SNAT没生效。6.2 DNAT不生效的排查思路症状外部访问公网IP的端口连接超时或被拒绝。排查步骤确认DNAT规则存在且被匹配iptables -t nat -L PREROUTING -n -v。确认FORWARD链允许iptables -L FORWARD -n -v。这是最常见的遗漏。确认内网服务在监听ss -tlnp | grep 8080。确认内网服务没有防火墙拦截。在网关的eth0上抓包看请求有没有进来在eth1上抓包看有没有转发出去。tcpdump -i eth0 -n port 80 tcpdump -i eth1 -n port 8080如果eth0有包但eth1没有说明DNAT或FORWARD有问题。6.3 常见问题速查表问题现象可能原因解决方法内网机器上不了网转发开关没开echo 1 /proc/sys/net/ipv4/ip_forward内网机器上不了网SNAT规则没匹配检查-s和-o参数是否正确内网机器上不了网FORWARD链默认DROP加iptables -A FORWARD -j ACCEPT或更精细的规则外部访问DNAT超时FORWARD链没放行加对应的FORWARD ACCEPT规则外部访问DNAT超时内网服务没监听ss -tlnp确认服务状态内网用户访问公网IP失败NAT回环问题加SNAT或MASQUERADE规则规则重启后丢失没保存iptables-save或netfilter-persistent save改规则后SSH断开规则误伤提前开备用会话或加定时恢复任务6.4 几个容易忽略的细节MTU问题NAT转换后数据包大小可能变化如果MTU设置不当会导致大包被丢弃。表现是能ping通但传文件卡住。解决方法是在网关上设置MSS clampingiptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuconntrack表满高并发场景下连接跟踪表可能被占满导致新连接无法建立。可以调大/proc/sys/net/netfilter/nf_conntrack_max或者缩短超时时间。规则顺序iptables是从上往下匹配的匹配到就跳转。所以具体的规则要放在前面通用的规则放在后面。我见过有人把-j ACCEPT放在最前面结果后面的规则全都不生效了。默认策略改默认策略之前一定要三思。把FORWARD默认策略改成DROP如果没加允许规则所有转发流量都会断。我一般会先把允许规则加好再改默认策略。7. 从实际项目中学到的经验7.1 一个典型的网关配置案例之前做过一个项目需要把内网的一台服务器暴露到公网同时内网其他机器也要能上网。网关有两个网卡eth0内网192.168.1.1eth1外网203.0.113.5。完整的配置流程是这样的# 1. 打开转发 echo 1 /proc/sys/net/ipv4/ip_forward # 2. 清空现有规则 iptables -F iptables -t nat -F # 3. 设置默认策略 iptables -P INPUT ACCEPT iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 4. SNAT内网机器上网 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to-source 203.0.113.5 # 5. DNAT外部访问内网Web服务 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080 # 6. FORWARD放行 iptables -A FORWARD -s 192.168.1.0/24 -o eth1 -j ACCEPT iptables -A FORWARD -d 192.168.1.100 -p tcp --dport 8080 -j ACCEPT iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT # 7. NAT回环 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 8080 -j SNAT --to-source 192.168.1.1 # 8. 保存 iptables-save /etc/iptables/rules.v4这套配置跑了一年多没出过问题。关键点在于FORWARD链的规则要精细不要图省事直接-j ACCEPT。我见过有人直接iptables -A FORWARD -j ACCEPT结果内网机器被外部扫描到中了招。7.2 一次生产事故的复盘有一次同事在网关上改规则想加一条DNAT。他直接用了iptables -t nat -A PREROUTING ...结果加完之后所有外部访问都断了。排查发现他加的规则里-d参数写错了写成了内网地址。结果所有目标地址是内网地址的包都被DNAT到了错误的地方。更糟的是这条规则加在了最前面把原来的规则全挡住了。教训改规则之前先用iptables -t nat -L PREROUTING -n --line-numbers看一下现有规则确认新规则加在什么位置。如果拿不准先用-I插入到指定位置而不是-A追加。还有一次另一个同事把FORWARD默认策略改成了DROP忘了加允许规则结果整个内网都上不了网。后来我们定了个规矩改默认策略之前必须先在测试环境验证而且必须两个人同时在场一个人操作一个人盯着。7.3 性能优化的几个方向NAT是CPU密集型操作高并发场景下网关的CPU可能会成为瓶颈。几个优化方向用SNAT代替MASQUERADE前面说过MASQUERADE每次都要查接口IP性能差一些。减少规则数量规则越多匹配越慢。把常用的规则放在前面或者用ipset把多个地址合并成一条规则。调整conntrack参数根据并发量调整nf_conntrack_max和超时时间。开启网卡多队列多核CPU下开启网卡多队列可以让多个核心并行处理网络中断。考虑用nftablesnftables是iptables的下一代性能更好语法更清晰。如果新项目可以考虑直接用nftables。7.4 一些实用的小技巧快速查看NAT规则iptables -t nat -L -n -v --line-numbers-v显示匹配计数--line-numbers显示行号方便删除或插入规则。删除单条规则iptables -t nat -D POSTROUTING 3删除POSTROUTING链的第3条规则。临时禁用所有规则iptables -P FORWARD ACCEPT iptables -F FORWARD这样可以在紧急情况下快速恢复网络但注意这只影响FORWARD链INPUT和OUTPUT的规则还在。用日志排查iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j LOG --log-prefix DNAT: 在规则前面加一条LOG规则可以在/var/log/messages或dmesg里看到匹配情况。排查问题时特别有用。用conntrack查看连接conntrack -L | grep 192.168.1.100查看特定IP的连接跟踪记录能看到NAT转换前后的地址和端口。8. 关于NAT的一些深入理解8.1 NAT对协议的影响NAT不是对所有协议都友好。有些协议在应用层携带了IP地址或端口信息NAT转换后会导致这些信息不一致从而出问题。最典型的是FTP。FTP的PORT和PASV命令会在应用层传递IP和端口。如果做了NATFTP客户端告诉服务器“我在192.168.1.100的12345端口等你”但服务器看到的源地址是203.0.113.5它就会尝试连接192.168.1.100这显然连不上。解决方法是加载nf_conntrack_ftp模块它会自动识别FTP协议并做相应的转换modprobe nf_conntrack_ftpSIP、H.323等协议也有类似的问题需要对应的conntrack模块。8.2 NAT与安全的关系NAT在一定程度上提供了安全性因为内网地址对外部不可见。但这不能替代防火墙。NAT只是地址转换不是访问控制。该做的过滤规则还是要做。另外NAT会隐藏真实的源地址给日志分析和入侵检测带来困难。如果有多层NAT追踪一个连接的真实来源会很麻烦。8.3 IPv6时代的NATIPv6地址空间足够大理论上不需要NAT。但NAT在IPv6中仍然存在主要是为了地址转换和负载均衡。IPv6的NAT和IPv4的NAT在概念上类似但实现方式不同。如果以后要迁移到IPv6这部分知识需要重新学习。不过就目前来说IPv4的NAT还是主流掌握iptables的SNAT和DNAT在日常运维中足够用了。我个人在实际操作中的体会是NAT这东西原理不复杂但细节特别多。每一条规则背后都有它的逻辑每一个参数都有它的作用。配的时候多想想“这个包从哪来到哪去经过哪些链被哪条规则匹配”大部分问题都能想明白。遇到排查不了的抓包是最直接的手段看包到底走到哪一步丢了比瞎猜强得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑