首页 / 资讯中心 / 文章详情

CentOS 7.6下iptables配置SNAT与DNAT实战详解

CentOS 7.6下iptables配置SNAT与DNAT实战详解 ★ FEATURED ARTICLE
1. 为什么 CentOS 7.6 的 NAT 场景里我反而绕开了 firewalld先说个我自己的经历。之前帮朋友公司调试一台 CentOS 7.6 的网关服务器需求很简单内网几十台机器要共用一条宽带上网外网还要能通过公网 IP 访问内网的一台 Web 服务器。听起来就是标准的 SNAT 加 DNAT但我刚开始下意识打开 firewalld 配置时发现这事儿有点绕。CentOS 7 默认把 firewalld 推到了前台很多人上岗后直接就在 firewalld 里加规则压根不碰 iptables 命令。但做 NAT 转发这种场景我个人的经验是iptables 反而更直观、更顺手甚至在一些云厂商的镜像里人家的初始化脚本就是直接写 iptables 规则的。所以这篇我围绕 CentOS 7.6 环境把 SNAT 和 DNAT 从原理到落地的完整过程掰开揉碎讲一遍。1.1 firewalld 和 iptables 并不是二选一的对手很多刚接触 Linux 防火墙的同学会有一个误解觉得 CentOS 7 用了 firewalldiptables 就已经淘汰了。实际上 firewalld 只是一个上层管理工具它把规则组织成 zone 和 service最终下发到内核的 netfilter 框架时仍旧是 iptables 那套规则链在生效。换句话说你可以在 firewalld 里把 certain 端口放行然后立刻执行iptables -L -n -v看到底层规则被打进去了。firewalld 和 iptables 命令操作的是同一个内核防火墙只是表达方式不一样。那为什么做 NAT 我建议直接用 iptables 命令因为 firewalld 的 zone 概念用于管理“哪些端口对哪些区域开放”非常方便但遇到 SNAT、DNAT 这种需要精确指定源地址、目的地址、出接口、目标 IP 的转发规则时firewalld 的富规则语法写起来又长又绕排错也费劲。比如 firewalld 里添加一条 DNAT 要用firewall-cmd --permanent --zonepublic --add-forward-port或者写富规则可读性远不如iptables -t nat -A PREROUTING一眼就能看懂。1.2 什么时候必须 iptables 出场我总结下来下面这几类情况基本绕不开 iptables配置 SNAT 源地址转换让内网机器统一从一个出口 IP 上网。配置 DNAT 端口映射把公网 IP 的某个端口转发到内网服务器的指定端口。需要对数据包做精细的地址或端口改写比如一对一映射、端口重定向。服务器上跑的自动化部署脚本、初始化脚本很多还是直接调用 iptables 命令的。排查 NAT 不生效的问题时你需要看iptables -t nat -L的规则列表而不是看 firewalld 的 zone 配置。所以这篇我就直接基于 iptables 来写同时也会说明怎么和 firewalld 共存或者互相避让保证在生产环境里能落地。提示如果你的系统同时开着 firewalld 和 iptables-services 服务操作前最好确认一下当前实际生效的是哪一套规则避免你花了半小时排错最后发现规则根本没加载进去。后面第五章我会专门讲这个坑。2. 配置 SNAT 和 DNAT 之前先弄清楚数据包在网关里怎么走很多人配置 NAT 失败不是命令拼错了而是根本不知道自己在哪个链上做改写。我见过最典型的情况是有人想做源地址转换随手一条iptables -A POSTROUTING ...却忘了加-t nat参数命令直接报错还有人把 DNAT 规则加到了 POSTROUTING 链上流量死活不进内网。这些都是原理层面没理清造成的。2.1 用前台收发快递来理解 SNAT 和 DNATSNAT 和 DNAT 的区别我用一个生活化的例子来解释。假设公司只有一个对外地址前台统一收发快递。公司员工要寄快递出去快递单上的寄件人地址如果写工位编号快递公司根本没法回寄所以前台会统一把寄件人地址改成公司的对外地址。这个动作就是SNAT当内网主机访问外网时把数据包的源 IP 地址改写成网关的公网 IP。反过来外面的人寄快递到公司快递单上只写了公司地址没有写具体收件人前台收到之后需要根据内部分机号把快递转交给对应的员工。这个动作就是DNAT当外网流量进来时把数据包的目的 IP公网 IP 和端口改写成内网服务器的私有 IP 和端口。2.2 五链四表在 NAT 配置里的真实分工Linux 的 netfilter 框架中数据包经过的关键链主要有五条PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。而 NAT 相关的表有两个关键方向SNAT 只在 POSTROUTING 链上做因为 POSTROUTING 是数据包即将发出网卡前的最后一个环节这时候改写源地址就能让出去的数据包都带上公网 IP。DNAT 只在 PREROUTING 链上做因为 PREROUTING 是数据包进入网卡后最先经过的环节在这里改写目的地址后续路由决策才能把包正确地送到内核转发路径上。所以配置 NAT 时规则链的选择是有严格语义的。你如果在别的链上做了 NAT要么不生效要么行为完全不符合预期。下面这张表是我平时排错时经常用来对照的转换类型所在表所在链核心动作典型场景SNATnat 表POSTROUTING改写源 IP内网共享上网MASQUERADEnat 表POSTROUTING动态改写源 IP拨号、动态公网 IPDNATnat 表PREROUTING改写目的 IP/端口端口映射、内网服务发布REDIRECTnat 表PREROUTING改写目的端口到本机透明代理、端口重定向还有一个很容易忽略的点NAT 的规则只关心“新连接的第一个包”。一旦一个连接被 NAT 映射过内核的 conntrack 机制就会记住这个映射关系后续属于同一连接的数据包会自动按同样的方式改写不需要逐包匹配规则。这也是为什么 NAT 配置完成之后你看到的回包过程非常“顺滑”——因为 conntrack 已经把正反方向的地址转换都记下来了。2.3 回包为什么能正确回到内网主机再往深一层说conntrack 不仅仅记住了源和目的地址它同时记录了“内网主机 192.168.1.100:5000 访问外网 8.8.8.8:80被改写成了公网 IP:5000 访问 8.8.8.8:80”。当外网的回包到达网关时conntrack 发现这个包属于已记录连接的回包就会自动把目的地址改回 192.168.1.100:5000再送进内网。这个机制让 NAT 的配置看起来“不用管回包”但代价是你需要关心 conntrack 表的大小。如果服务器上并发连接数巨大conntrack 表被打满新连接就会被丢弃现象就是“网络时不时卡一下过一会儿又好了”。这个坑我后面详细说。3. SNAT 实战让整个内网通过一台 CentOS 7.6 网关出网现在进入正题。假设你手上有一台 CentOS 7.6 的服务器双网卡一张接内网交换机IP 是 192.168.1.1另一张接运营商宽带IP 是公网地址比如 203.0.113.10。内网网段是 192.168.1.0/24下面的机器都通过这台服务器上网。目标很明确让 192.168.1.0/24 网段的所有机器访问外网的时候都伪装成 203.0.113.10 这个公网 IP。3.1 环境准备与内核转发开关CentOS 7.6 默认是不允许 IP 转发的也就是说数据包到了你这台服务器如果目的地址不是本机内核会直接丢弃。这一步是绝大多数 NAT 配置失败的首因。检查当前内核是否开启了转发sysctl net.ipv4.ip_forward如果输出是net.ipv4.ip_forward 0说明没开启。临时开启可以执行echo 1 /proc/sys/net/ipv4/ip_forward或者用 sysctl 命令sysctl -w net.ipv4.ip_forward1但这种方法重启之后会失效所以生产环境一定要写进配置文件。编辑/etc/sysctl.conf找到或者添加这一行net.ipv4.ip_forward 1保存后执行sysctl -p让它立即生效。3.2 配置 SNAT 并验证内核转发打开之后就可以配置 NAT 规则了。标准命令如下iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10我来拆解一下这条命令的每个字段很多人抄命令容易漏-t nat指定操作 nat 表。没有这一步规则会写到 filter 表POSTROUTING 链在 filter 表里根本不存在命令会报错。-A POSTROUTING追加到 POSTROUTING 链。-s 192.168.1.0/24匹配源地址为内网网段的数据包。这里可以用具体 IP 来限定某台机器。-o eth0匹配从 eth0 网卡出去的数据包。-j SNAT --to-source 203.0.113.10把匹配到的数据包源地址改写为 203.0.113.10。配置完成后先用内网的一台机器 ping 外网 IP 测试。假设内网机器的 IP 是 192.168.1.100在它上面执行ping -c 4 8.8.8.8如果通了说明基本链路没问题。但这时候还要确认它是不是真的走了 SNAT。你可以在网关服务器上看 conntrack 表conntrack -L | grep 192.168.1.100或者直接查看 nat 表的规则计数iptables -t nat -L -n -v看到 POSTROUTING 那条 SNAT 规则的 Packets 数量在增长就说明流量确实命中规则了。3.3 动态 IP 场景改用 MASQUERADE有些环境里出口 IP 不是固定的比如家用宽带拨号上网公网 IP 每次拨号都会变。这时候再用--to-source 203.0.113.10就麻烦了——IP 一变规则就得跟着改。这种情况下建议直接换成 MASQUERADEiptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADEMASQUERADE 会自动获取出口网卡的当前 IP 作为源地址IP 变了也不影响。代价是每次新连接建立时内核需要额外查询一下出口网卡的 IP性能上比 SNAT 稍微差一点点。但在一般的小型网络环境里这个差别可以忽略不计。我自己在服务器上用固定公网 IP 时更倾向写显式的 SNAT因为规则可预期排错的时候一眼能看明白出口地址是哪个如果是测试环境、拨号环境直接 MASQUERADE 省心。4. DNAT 实战公网端口映射到内网服务器SNAT 解决的是“出去”的问题DNAT 解决的是“进来”的问题。同样是在刚才那台 CentOS 7.6 服务器上假设内网有一台 Web 服务器IP 是 192.168.1.100监听 80 端口。你希望外网用户通过访问网关的公网 IP 203.0.113.10 的 8080 端口就能打开内网 Web 服务器的页面。这就是典型的端口映射标准命令如下iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80同样拆解一下-t nat还是 nat 表。-A PREROUTING追加到 PREROUTING 链这是 DNAT 唯一正确的链。-d 203.0.113.10匹配目的地址为公网 IP 的数据包。-p tcp --dport 8080匹配 TCP 协议的 8080 端口。-j DNAT --to-destination 192.168.1.100:80把目的地址改写为 192.168.1.100目的端口改写为 80。4.1 别忘了放行 FORWARD 链这是 DNAT 配置里最容易踩的坑。PREROUTING 链改写完目的地址之后数据包经过路由决策发现目的地址已经不是本机了于是进入 FORWARD 链。如果 FORWARD 链的默认策略是 DROP或者没有放行对应流量数据包会直接在这里被丢掉。很多人在 nat 表里加了半天规则外网就是访问不通一查发现是 FORWARD 链拦了。所以还要加一条放行规则iptables -A FORWARD -d 192.168.1.100 -p tcp --dport 80 -j ACCEPT顺便把回程流量也放行掉iptables -A FORWARD -s 192.168.1.100 -p tcp --sport 80 -j ACCEPT如果你的 FORWARD 链默认策略是 ACCEPT那这两条不加也能通但为了安全起见内网服务器只对特定端口开放显式放行更稳妥。4.2 验证 DNAT 是否生效配置完成后从外网环境访问一下curl http://203.0.113.10:8080如果打开了说明 DNAT 生效。如果没通排查步骤我一般这样走先在网关本机上试一下能否直接访问 192.168.1.100 的 80 端口curl http://192.168.1.100如果这一步都不同问题出在内网链路跟 DNAT 没关系。检查 nat 表的 PREROUTING 规则有没有匹配计数iptables -t nat -L -n -v如果规则计数一直为 0说明数据包可能没走到这条规则常见原因是目的 IP 匹配不上或者 FORWARD 链拦截发生在匹配之前。检查 FORWARD 链的计数是否增长。如果 PREROUTING 有计数但 FORWARD 没有说明数据包在更早的环节被丢弃了比如外部接口自身的防火墙策略。4.3 内网机器通过公网 IP 访问自己服务的特殊场景还有一个很多教程不会讲的细节如果你在内网机器上通过公网 IP 去访问自己映射的服务往往会不通。这种现象叫做 NAT loopback 问题。原因是内网机器发出的数据包目的地址是公网 IP数据包到达网关后PREROUTING 规则做 DNAT 改写成 192.168.1.100:80然后数据包从内网网卡送出。但是源地址还是内网机器自己的 IP而目标服务器 192.168.1.100 收到请求后回包的目标地址会直接指向内网机器而不是走网关。因为目标服务器认为源和目标在同一网段就不经过网关。可内网机器发起连接时它期望的是“和公网 IP 通信”收到的回包源地址却是 192.168.1.100——连接状态对不上访问就卡死了。解决这个问题的标准做法是在 POSTROUTING 链上加一条 SNAT让内网访问公网 IP 映射的流量源地址也统一改写成网关内网地址iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 80 -j SNAT --to-source 192.168.1.1这样回包就会回到网关再由 conntrack 记录把地址还原整个链路就通了。这个小细节在很多生产环境里都会遇到建议收藏。5. iptables 做 NAT 最容易踩的五个坑前面把 SNAT 和 DNAT 的核心配置讲完了但一套规则真正能在生产环境里稳定跑起来靠的不只是几条命令。我把这几年实际运维中遇到的坑集中列一下每一个都是真实导致过线上故障的。5.1 FORWARD 链默认策略把流量挡死前面 DNAT 部分已经提到了。SNAT 场景里同样存在这个问题。内网机器通过网关上网数据包路径是先到网关路由判断后发现目标在外网于是走 FORWARD 链发出去。如果 FORWARD 默认策略是 DROP你的 SNAT 规则再正确数据包也出不去。排查技巧在网关上看 FORWARD 链的计数变化。执行以下命令后让内网机器发一个 pingiptables -L -n -v | grep -i forward计数不涨大概率是被丢弃了结合iptables -L -n -v里每条规则的匹配次数就能定位。5.2 规则没保存重启全丢iptables 的规则是运行时配置重启服务器或者重启 iptables 服务后就会清空。CentOS 7 默认只装了 iptables 命令但没有保留规则的开机加载机制。你需要额外安装 iptables-servicesyum install -y iptables-services安装后停用 firewalld 并启用 iptables 服务systemctl stop firewalld systemctl disable firewalld systemctl enable iptables systemctl start iptables再用iptables-save把当前规则保存下来iptables-save /etc/sysconfig/iptables之后重启系统规则会自动从/etc/sysconfig/iptables加载。很多人以为执行过iptables命令就万事大吉结果一重启全没了这种情况我见得太多了。5.3 回程路由缺失导致“单向通”做 DNAT 映射时公网流量能被网关正确转发到内网服务器但内网服务器回包却可能丢失。原因通常是内网服务器的默认网关没有指向这台防火墙网关。比如内网服务器的网关写成了 192.168.1.2而不是 192.168.1.1回包直接发给别的设备流量自然断了。排查方法很简单在内网服务器上执行ip route看看默认路由是不是 192.168.1.1如果不是改回来。很多 NAT“时通时不通”的问题最后都定位到这里。5.4 conntrack 表满连接突然卡死之前提到过 conntrack 机制。系统默认的 conntrack 表大小是有限制的如果并发连接数过高或者有一些异常流量大量占表表满了之后新连接就会被丢掉。现象就是服务器负载不高网络却完全不通等表里的老连接超时释放后又“莫名其妙”恢复了。可以查看当前的 conntrack 使用情况cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max如果使用量接近上限临时调大sysctl -w net.netfilter.nf_conntrack_max655350生产环境建议写入/etc/sysctl.conf或单独放在/etc/sysctl.d/下的配置文件中。另外用一些不必要的跟踪规则可以降低压力但这是我后面想单独展开的话题。5.5 firewalld 和 iptables 同时启动引发的混乱CentOS 7 上firewalld 和 iptables-services 最好不要同时启用。我在一台测试服务器上遇到过firewalld 的规则里放行了 8080 端口但 iptables 的规则里没放行firewalld 停掉后iptables 服务接管端口立刻不通反过来重启 firewalld原来 iptables 的 NAT 规则又失效了。两头打架定位问题花了很长时间。现在我的习惯是一台新服务器做 NAT 网关先明确自己要用的管理工具然后停掉另一个服务。如果公司规范要求必须用 firewalld那就老老实实学习它的富规则语法如果网上的教程和你的脚本都是 iptables 风格的那就停 firewalld统一交给 iptables-services 管理。混用只会让排错成本翻倍。5.6 顺手分享一个规则备份的习惯除了保存规则我还建议在改动规则前先导出一份备份文件以便随时回滚iptables-save /etc/sysconfig/iptables.bak.$(date %F_%H%M%S)恢复也很简单iptables-restore /etc/sysconfig/iptables.bak.20250101_120000这两个命令我用了很多年够用且踏实。规则出问题不用靠回忆一条条猜直接恢复到改动前的状态就行。最后再补一句个人体会iptables 的 NAT 配置本质上就是两件事——理解数据包流向记住每条规则放在哪个链。原理通了命令自然就记住了原理不通背再多命令也会在换一个网络环境的时候翻车。建议你拿到一台测试机从 SNAT 开始再到 DNAT最后加上 NAT loopback 的场景完整跑一遍这几个坑都踩过之后很多网络问题你一眼就能看出症结在哪。
阅读完成 · 觉得有帮助?
咨询建站