做网络运维的人,基本都绕不开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还是Debian,iptables几乎都预装或在基础软件源里,版本差异带来的命令差异非常小,排查问题时网上能查到的资料也最丰富。
二是排错直观。iptables所有规则都挂在明确的链上,用iptables -t nat -L -n -v就能看到每条规则的命中次数,定位问题比firewalld那套zone概念直接得多。我见过不少人在firewalld里把端口添加了还是不生效,最后发现是rich rule写错了,而iptables命令一行一行检查很快就能找到问题。
三是核心能力足够。局域网共享上网和端口映射,本质上是内核Netfilter框架的NAT能力,iptables只是控制接口。只要内核支持,性能完全够用,一般千兆内网跑满带宽没有问题。
当然,如果是新项目、完全从零开始,我也会考虑nftables。但如果是接手既有服务器、或者要在生产环境快速解决问题,iptables依然是最稳妥的选择。
1.3 网络拓扑与IP规划
为了方便说明,我定义一个典型的网络拓扑,后面所有命令都以这个拓扑为基准。网关是一台双网卡的Linux主机,发行版建议用Debian/Ubuntu系。
- 外网网卡eth0:IP为203.0.113.10/24,网关为203.0.113.1,对应运营商分配的静态公网地址。
- 内网网卡eth1:IP为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,用SNAT:
iptables -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-persistent:
apt 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.1:
iptables -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.88,POSTROUTING再把源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.conf:
net.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,这样下次配置新环境只需要改几个变量,不用逐条重敲。我自己的脚本里固定了备份、加载、查看三个子命令,运维起来省力不少。
最后再分享一个小技巧:规则全部调通后,把完整的规则文件留一份注释版,把每条规则的作用写清楚。这个东西过几个月再看,你会发现它比任何文档都实在,因为上面记录的就是这台网关真正的“行为逻辑”。即使换了人来维护,照着注释也能快速接手,不至于面对一屏规则发怵。