iptables实战:局域网共享上网与端口映射配置全解析
做网络运维的人基本都绕不开iptables这个老朋友。标题里提到的“局域网共享上网”和“端口映射”是Linux网关类应用里最常用、也最考验基本功的两个场景。前者解决的是“一堆内网机器怎么通过一台Linux主机上网”后者解决的是“内网某个服务怎么暴露到外网让别人访问”。这篇文章不是复读教科书是我在几台设备上实际配置、踩坑之后的完整记录把原理、命令、验证方法和常见故障一次讲透。适合刚接触Linux网络、需要给办公室或实验室搭共享上网环境或者想把自己内网服务发布到公网的朋友参考。1. 项目整体思路与方案选型1.1 需求拆解不只是两条命令的事先还原一个我遇到过的真实场景。某公司临时办公区运营商只给了一条静态公网线路和一个公网IP但办公室里有三十多台电脑、打印机、测试服务器都要上网。与此同时内网里有一台Web服务需要使用公网IP的某个端口对外提供服务。这个需求拆开看其实包含三个子任务第一让内网所有设备通过Linux主机上网也就是做网络地址转换。内网设备用的是私有IP比如192.168.1.0/24公网设备不认识这些私有地址所以数据包出去的时候网关要把源地址改写成公网IP回来的时候再改回去。这是“源NAT”干的事情。第二把内网服务映射到公网。也就是当外部用户访问公网IP的某个端口时网关要把这个访问请求转发给内网某台服务器上对应的端口并保证响应能原路返回。这是“目的NAT”干的事情。第三所有策略在重启后还要能自动生效不能每次手动敲一遍。这涉及规则持久化的问题。很多人只记住了两条iptables命令一条SNAT一条DNAT但实际配置时遇到的问题往往出在转发链、防火墙默认策略、数据包回流这些边角上。这些正是这篇文章想重点展开的内容。1.2 为什么用iptables老但可靠做Linux网关可选方案其实不少。比如用firewalld它底层封装了iptables用nftables它是新一代内核框架甚至可以用一些图形化软路由系统。但我个人在服务器和离线环境里依然优先选择iptables原因有三个。一是兼容性极好。不管是CentOS 7、Ubuntu还是Debianiptables几乎都预装或在基础软件源里版本差异带来的命令差异非常小排查问题时网上能查到的资料也最丰富。二是排错直观。iptables所有规则都挂在明确的链上用iptables -t nat -L -n -v就能看到每条规则的命中次数定位问题比firewalld那套zone概念直接得多。我见过不少人在firewalld里把端口添加了还是不生效最后发现是rich rule写错了而iptables命令一行一行检查很快就能找到问题。三是核心能力足够。局域网共享上网和端口映射本质上是内核Netfilter框架的NAT能力iptables只是控制接口。只要内核支持性能完全够用一般千兆内网跑满带宽没有问题。当然如果是新项目、完全从零开始我也会考虑nftables。但如果是接手既有服务器、或者要在生产环境快速解决问题iptables依然是最稳妥的选择。1.3 网络拓扑与IP规划为了方便说明我定义一个典型的网络拓扑后面所有命令都以这个拓扑为基准。网关是一台双网卡的Linux主机发行版建议用Debian/Ubuntu系。外网网卡eth0IP为203.0.113.10/24网关为203.0.113.1对应运营商分配的静态公网地址。内网网卡eth1IP为192.168.1.1/24负责连接内网设备。内网网段192.168.1.0/24内网所有设备把默认网关指向192.168.1.1。内网需要映射的Web服务器192.168.1.88提供HTTP服务监听80端口。注意203.0.113.0/24是文档保留地址段实际使用请替换成你的真实公网IP。公网地址和私网地址不要混用否则NAT规则会非常混乱。先把网卡、IP、路由理顺再写iptables规则。很多人上来就敲iptables命令结果内网机器ping不通网关或者网关能ping通外网但内网不行最后发现是网卡IP配错了。网络基础没打好防火墙规则写得再漂亮也没用。2. 核心原理拆解NAT两张表与三条链2.1 数据包在Netfilter中的流转路径理解iptables的NAT不能只背命令得先看数据包在内核里是怎么走的。Netfilter框架在协议栈里埋了多个钩子点iptables把这些钩子组织成不同的链。对于NAT来说最核心的钩子有三个PREROUTING、FORWARD、POSTROUTING。数据包从内网设备进入网关的eth1网卡后首先经过PREROUTING链这里的主要作用就是判断这个包是不是要送往本机的如果不是就会走FORWARD链进行转发如果是发往本机服务的才走INPUT链。我第一段配置的端口映射DNAT就挂在PREROUTING链上因为要在路由决策之前把目的地址改写成内网服务器地址这样内核才会把它当成“需要转发”的包而不是发给本机的包。数据包经过FORWARD链之后到达POSTROUTING链。这里是做源NAT的时机也就是把内网设备的私有源地址改写成公网地址这样数据包才能在外网正常往返。共享上网的SNAT规则就挂在POSTROUTING上。换句话说PREROUTING管的是“来的包要去哪”POSTROUTING管的是“走的包从哪来”。FORWARD链则是转发的关卡负责决定哪些数据包允许被路由转发。这三条链配合才构成了完整的NAT网关。2.2 SNAT与MASQUERADE到底怎么选POSTROUTING链上最常见的两条源NAT规则是SNAT和MASQUERADE。它们的共同作用是改写数据包的源IP地址区别在于改写的灵活程度。SNAT是固定的源地址转换需要明确指定--to-source。比如iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10这条规则的意思是凡是来自192.168.1.0/24、从eth0出去的包把源IP改成203.0.113.10。SNAT的优点在于稳定因为转换后的源地址是固定的适合静态公网IP场景。出网流量总是这一个IP做访问控制、日志审计都方便。MASQUERADE则不需要指定具体IP它会在每次处理数据包时自动获取出接口的当前IP作为源地址。典型场景是PPPoE拨号这种动态IP环境iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE这句话的意思是凡是来自内网、从eth0出去的包自动把源IP改成eth0当前的IP。IP变了也不用改规则适用性更强。缺点是每次数据包都要去查询接口IP性能略低于SNAT。如果是固定IP我更推荐SNAT少一层动态查询规则也更明确。2.3 DNAT端口映射的工作原理端口映射用的是DNAT挂在PREROUTING链上。还是以我的场景为例要让公网用户访问203.0.113.10:8080时转发到内网192.168.1.88:80规则如下iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.88:80这条规则干的活是当外网进来的TCP包目的地址是203.0.113.10、目的端口是8080时把目的IP改成192.168.1.88目的端口改成80。经过这个改写数据包的路由决策就变成了“发往内网192.168.1.88”于是走FORWARD链转发出去。但这里有一个容易忽略的点数据包离开网关的时候源地址还是外部访问者的公网IP。内网服务器收到这个包后发现自己和源地址“不熟”响应包会尝试直接发给外网访问者而不是经过网关回传。如果外部访问者的网络环境不允许直接返回或者内网服务器没有外网路由这个流量就断了。解决办法是在POSTROUTING链上给这些去往内网服务器的数据包补一条源NAT把源地址改写成网关的内网IP也就是内网服务器的“默认网关”192.168.1.1。这样内网服务器看到的是网关在访问自己响应包就会老老实实发回网关再由网关逆向转换后返回给外网用户。这个细节就是“NAT回流”问题的根源后面实操部分我会专门讲。3. 实操过程搭建局域网共享上网3.1 环境准备与内核参数在配置iptables之前先确认三件事内核是否支持转发、网卡和IP是否正确、iptables命令是否可用。第一开启IP转发。Linux默认不转发数据包即使iptables规则写好了也不会干活。临时生效用这条命令echo 1 /proc/sys/net/ipv4/ip_forward永久生效就修改/etc/sysctl.conf文件加入或取消注释这一行net.ipv4.ip_forward 1然后执行sysctl -p让它立刻生效。我强烈建议直接在sysctl.conf里改而不是每次开机后手动echo否则你会在重启之后发现内网突然断网排查半天才想起来是转发没开。第二检查网卡配置。用ip addr确认eth0和eth1都有正确的IP用ip route确认默认路由从eth0走。内网网段和网关自身的IP不要冲突否则路由会变得非常奇怪。第三确认iptables可用iptables --version iptables -t nat -L -n -v如果没有安装Ubuntu/Debian上执行apt install iptables即可。执行第二条命令时如果提示没加载nat表先试试modprobe iptable_nat。3.2 编写NAT与FORWARD规则我建议在动手之前先看一眼工具箱里的原始策略避免新规则和旧规则互相干扰iptables -t nat -L -n -v iptables -L -n -v如果iptables规则是空的那很好。如果之前有规则注意不要删除正在使用的规则。下面以典型的“默认放行、只加必要限制”的方式配置网关。先做源NAT。假设是静态公网IP用SNATiptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10接着配置FORWARD链允许内网发起的外出连接以及它们对应的回程连接iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.0/24 -j ACCEPT iptables -A FORWARD -i eth0 -o eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT第一条允许内网设备从eth1进入、从eth0出去。第二条允许外网进来的“已建立连接”回包从eth0进入、从eth1出去。ESTABLISHED,RELATED很关键它只放行那些内网发起连接对应的返回数据包而不会放行外网主动向内网发起的新连接。如果不加这条外网数据包到了网关根本不会转发到内网。注意如果你手动把系统默认FORWARD策略设成了DROP那么上面两条都要保留。如果默认策略是ACCEPT这些规则主要起到收口过滤的作用但不建议在大规模生产环境依赖“默认放行”规则能明确就尽量明确。3.3 内网客户端配置网关配置完成后内网设备的配置同样重要。每台内网电脑需要设置三样东西IP地址在192.168.1.0/24网段内、默认网关指向192.168.1.1、DNS指向一个可用的DNS服务器可以用运营商下发的地址也可以配置一个内网自建的DNS。这里有个常见误区内网电脑能ping通网关和外网IP但就是打不开网页。这种情况通常不是iptables的问题而是DNS没配好。可以先在网关本机测试DNS解析是否正常比如nslookup或者dig确认后再给内网设备配同样的DNS来源。还有一点内网设备的子网掩码不要写错。如果网关是192.168.1.1/24客户端也得是/24写作255.255.255.0。有人不小心把掩码写成255.255.0.0可能导致跨网段的奇怪行为非常难排查。3.4 规则持久化与开机自启iptables规则默认不会保存重启后全部丢掉。生产环境必须做持久化。Debian/Ubuntu系统上最简单的方式是安装iptables-persistentapt update apt install iptables-persistent安装过程中它会询问是否保存当前规则选是即可。之后每次修改规则后手动保存iptables-save /etc/iptables/rules.v4 ip6tables-save /etc/iptables/rules.ipv6对于其他的systemd发行版也可以把iptables-save写成一个systemd服务或者直接把命令写进rc.local。我的习惯是配置完成后先手动重启一次验证规则能正常加载再交给持久化机制。避免出现“配置好了一开机就失效”的坑。4. 实操过程端口映射发布内网服务4.1 映射场景与规则设计接下来配置端口映射。我的目标是外网用户访问http://203.0.113.10:8080时实际访问到内网192.168.1.88的80端口。先加DNAT规则iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.88:80如果你的公网IP不固定但外网访问入口是某个域名也可以使用-i eth0来限定入接口减少误匹配。我一般更建议同时指定-d 公网IP和-i eth0双保险更安全。接着在FORWARD链上允许这个转发iptables -A FORWARD -i eth0 -o eth1 -d 192.168.1.88 -p tcp --dport 80 -j ACCEPT iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.88 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT第一条放行外部访问内网Web服务器80端口的数据包。第二条放行该服务器返回给外部用户的响应数据包。这里用ESTABLISHED而不是ESTABLISHED,RELATED是因为HTTP响应就是已建立连接的一部分没必要放行额外的新连接。很多人漏了FORWARD链上的放行DNAT写完之后用外网机器一测发现连接超时。其实PREROUTING已经把包改写了但FORWARD链规则不允许转发数据包被丢弃表现就是“服务无响应”。排查的时候先看nat表再看filter表绝不放过FORWARD链。4.2 解决内网访问“回流”问题连好外网访问之后很快会暴露一个新的问题内网用户通过公网IP访问自己映射的服务经常不通或者通了但页面加载异常。前面说过原因在于响应包回程路径错误。内网Web服务器收到内网用户发来的请求时如果这个请求的源地址是内网用户的IP服务器会尝试把响应包直接发给那个内网用户。因为服务器和内网用户在同一个二层网络这样勉强还能通但TCP连接的源目端口可能对不上导致连接被卡住。更规范的做法是在内网用户访问公网IP映射服务时也经过网关转换一次。所以在POSTROUTING链上加一条规则凡是去往192.168.1.88:80的包把源地址改为网关内网IP 192.168.1.1iptables -t nat -A POSTROUTING -d 192.168.1.88 -p tcp --dport 80 -j SNAT --to-source 192.168.1.1这样内网用户访问公网IP时数据包先到达网关DNAT改成去往192.168.1.88POSTROUTING再把源IP改成192.168.1.1。Web服务器看到的请求来源变成了网关它回包就回给192.168.1.1再由网关把响应传回内网用户。链路就完整了。这条规则要注意顺序。它和共享上网的那条SNAT --to-source 203.0.113.10都挂在POSTROUTING链上如果先匹配了共享上网规则源地址就变成公网IP了回流问题依然存在。所以我把“去往内网服务器的回流规则”放在POSTROUTING链的靠前位置或者干脆独立成一条更精确匹配的规则避免被其他规则大包大揽。4.3 防火墙放行与安全收口在把网关当成一台正经防火墙来用的时候我建议把filter表的默认策略收严至少INPUT链要DROP。下面是典型的收口方式iptables -P INPUT DROP iptables -P FORWARD DROP如果执行了这两条前面说的FORWARD放行规则就都不是摆设了必须逐条保留。同时还要放行必要的管理流量比如SSH远程管理iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT iptables -A INPUT -i eth1 -p tcp --dport 22 -j ACCEPT以及内网访问网关本身的流量、回环流量iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -i eth1 -s 192.168.1.0/24 -j ACCEPT外部访问映射服务时因为DNAT发生在PREROUTING数据包在路由决策后是走FORWARD链而不是INPUT链所以INPUT链上其实不需要专门放行8080端口。这一点要注意别在INPUT和FORWARD里重复放行搞出安全隐患。如果你想记录被丢弃的包方便事后审计可以加一条LOG规则放在DROP之前iptables -A FORWARD -j LOG --log-prefix iptables-forward-drop: --log-level 4日志会进系统日志配合日志服务一起分析。注意LOG规则本身不会阻断数据包只是记录真正的丢弃还是要靠DROP规则。5. 常见问题与排查技巧实录5.1 内网设备无法上网的排查顺序这个问题出现频率最高。我总结了一套固定排查顺序建议按顺序走千万不要跳。第一先看网关本机能不能上网。ping 223.5.5.5或者访问一个公网地址。如果网关本机都不通那是外网连接或路由的问题和iptables无关。第二确认IP转发已开启。执行sysctl net.ipv4.ip_forward输出为1才行。第三看内网设备能否ping通网关。如果ping不通检查网卡IP、掩码、交换机端口。第四看内网设备能否ping通公网IP。如果ping不通公网IP说明NAT或FORWARD规则有问题。用iptables -t nat -L -n -v看POSTROUTING规则命中次数用iptables -L -n -v看FORWARD命中次数。如果规则根本没人命中说明数据包没走到这条链比如源地址匹配写错了。第五如果ping公网IP通但域名不通那就是DNS问题。检查内网设备的DNS配置。我曾经遇到过一台内网机器网关配成192.168.1.2而不是192.168.1.1结果所有规则都正确但就是上不了网。网卡没报错、链路正常直到我用tcpdump -i eth1 host 192.168.1.10抓包才发现这台机器把网关指到了隔壁一台打印服务器上。所以排查的时候ip route和ip addr的输出永远值得先看一眼。5.2 端口映射失效的常见原因映射失败时不要急着怀疑DNAT命令写错先按下面几个方向排查。第一确认公网IP到网关的链路是通的。如果外网访问者在国内你的公网IP在机房中间任何一跳断开都可能导致访问失败。在网关本机用curl访问一下映射地址试试如果本机访问都失败那就是网关自身的问题。第二确认数据包是否真的经过PREROUTING链。用iptables -t nat -L PREROUTING -n -v看命中次数。如果命中次数为0可能是访问者访问的IP不是203.0.113.10或者匹配条件写错了。第三确认FORWARD链放行规则。这是最常见的一处坑。默认策略是DROP时只写了DNAT没写FORWARD放行包必然被丢。查看iptables -L FORWARD -n -v的计数如果DROP计数在涨就知道是这里断了。第四确认内网服务本身可用。内网服务器上先自己访问一下curl http://127.0.0.1如果本地都打不开映射当然不会生效。还要检查Web服务监听的地址如果Nginx只监听了127.0.0.1:80内网其他机器访问不了网关映射了也没用必须让服务监听0.0.0.0:80或192.168.1.88:80。5.3 conntrack与性能调优网关上的NAT依赖Linux内核的连接跟踪机制conntrack。每一条经过NAT的TCP连接都会生成一条状态记录占用一定内存。如果是大流量场景连接表满了之后就会出现“明明规则没错但新连接建立失败”的现象。查看当前连接跟踪表的使用情况cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max如果发现连接数经常逼近上限可以适当调大比如修改/etc/sysctl.confnet.netfilter.nf_conntrack_max 655360同时可以缩短连接超时时间让不活跃的连接更快释放net.netfilter.nf_conntrack_tcp_timeout_established 600在压低内存占用和保障长连接之间找到平衡。一个常规的办公室网关默认参数通常够用但如果内网有大量P2P下载或者长时间TCP连接就很有必要关注这个表。还有一个容易忽略的点NAT网关不要随便开启TCP timestamps相关的优化参数比如tcp_tw_reuse之类的它虽然能在一定程度上复用连接但可能会和部分NAT场景冲突导致连接异常。在没有明确需求时保持内核默认更省心。5.4 我踩过的一些经验细节写几条常规文档不会讲、但实际很管用的细节供你参考。第一规则顺序很重要。iptables规则是按顺序匹配的一旦某条规则匹配成功后续同链规则不再执行。所以特别精确的规则要放在前面宽松的规则放后面。比如回流SNAT要放在共享上网SNAT之前否则会被先匹配掉。第二用-m state和-m conntrack跟上连接状态时千万别把方向搞反。ESTABLISHED只放行已建立连接的数据包如果忘记在回程方向放行外网回应你的包永远进不来。第三每次改完规则建议先保存再验证尤其是生产环境。我见过有人调规则时手滑把默认策略改成了DROP然后SSH瞬间断开只有在机房现场才能救回来。调试NAT规则时如果担心自己手误可以暂时不要把默认策略设成DROP等规则全部验证通过后再收严。第四遇到诡异问题tcpdump是排障利器。比如在内网网卡上抓包tcpdump -i eth1 host 192.168.1.88 and port 80 -nn能看到数据包是不是真的到达内网服务器也能看到源地址和目的地址是否被正确改写。有了抓包结果很多争论就能一锤定音。6. 回看整个配置流程我的体会是iptables配置局域网共享上网和端口映射本身的技术门槛不高真正值钱的是对整个数据链路流的理解。SNAT管出口、DNAT管入口、FORWARD管转发三者各司其职配合起来就是一台功能完整、稳定可靠的软路由网关。从项目角度看这类需求在公司、实验室、临时网络环境里非常常见一套规则模板改改IP就能复用。建议把常用规则整理成shell脚本用变量定义外网卡、内网卡、网段和公网IP这样下次配置新环境只需要改几个变量不用逐条重敲。我自己的脚本里固定了备份、加载、查看三个子命令运维起来省力不少。最后再分享一个小技巧规则全部调通后把完整的规则文件留一份注释版把每条规则的作用写清楚。这个东西过几个月再看你会发现它比任何文档都实在因为上面记录的就是这台网关真正的“行为逻辑”。即使换了人来维护照着注释也能快速接手不至于面对一屏规则发怵。