iptables 防火墙指南:表与链怎么理解、规则怎么写、误封怎么救

刚接触服务器的人常常把 iptables 当成一个“防火墙软件”:装上、启动、它就开始工作了。这个理解一开始就会把人带偏。iptables 本身不拦截任何东西,真正干活的是内核里的 netfilter 框架;iptables 只是你和内核对话的工具,你写一条规则,它就把这条规则交到内核里去执行。规则写完就生效,但只存在内存里,机器一重启就没了——这个特点决定了后面很多操作的意义:查看、保存、恢复、自救,都是围绕“规则活在内存里”展开的。
先把这层关系想清楚,后面的表、链、顺序、默认策略就都有了落点。
表与链:包走过的房间
理解 iptables 最好的办法,是想象一个数据包走进服务器要经过的房间。每个房间都有自己的职责,包按固定路线穿过去,每经过一个房间,就可能被里面贴着的规则拦下、放行或者改写。
表就是房间的种类,一共四张。filter 表负责过滤,也就是大家通常说的“防火墙”干的事:这个包让不让进、让不让出。nat 表负责改地址:内网机器借公网地址上网、公网端口映射到内网机器,都是它做的。mangle 表负责改包本身的一些标记,比如服务类型、生存时间,属于比较精细的活,日常配置很少碰到。raw 表负责在连接跟踪开始之前做处理,最常见的用途是让某些流量跳过连接跟踪,普通服务器也很少用到。
链是房间里的检查点。数据包有五条链可走:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。包从网卡进来,先到 PREROUTING;接下来路由做一次判断:如果这个包是给本机的,就走 INPUT,进到本地进程;如果只是路过,要转发给别的机器,就走 FORWARD;本地进程自己发出的包,走 OUTPUT;不管哪条路,最后都要经过 POSTROUTING 再从网卡出去。
不是每张表都挂在每条链上。filter 表只出现在 INPUT、FORWARD、OUTPUT 这三条链上——它只管“放不放行”,不管地址改写。nat 表出现在 PREROUTING、POSTROUTING 和 OUTPUT 上。mangle 和 raw 挂得全一些,但日常用得少。记住这个对应关系有个实际好处:当你发现一条规则没生效,第一反应不应该是“规则写错了”,而是先问“这条规则挂在对的表和链上了吗”。把过滤规则写进 nat 表,或者把地址转换写进 filter 表,都是新手最常见的无效操作,命令本身不会报错,只是永远匹配不到包。
一张表里如果有多条链,包经过它们的顺序也是固定的。以进入本机的包为例,它在 PREROUTING 阶段依次经过 raw、mangle、nat 三张表,到了 INPUT 阶段再经过 mangle 和 filter。这个顺序意味着,地址改写发生在过滤之前:一个包在 PREROUTING 被改了目的地址,到 INPUT 时看到的已经是改写后的地址。做端口映射的时候,这个细节会直接影响过滤规则该怎么写——过滤条件要按改写后的地址和端口来写,而不是按包最初的样子。
规则匹配:从上到下,首条决断
链里的规则是一张按顺序排列的清单。数据包进来,内核从第一条开始往下比对,命中哪条就按哪条的动作处理,然后停止——后面的规则不再看。如果一路比对到最后都没命中,就按这条链的默认策略处理。默认策略只有两种常见选择:ACCEPT 是放行,DROP 是丢弃。
这个“首条决断”的机制,决定了写规则时最重要的一件事:顺序比数量重要。一条放行规则写在一条丢弃规则前面,包就被放行;反过来写,包就被丢弃。两条规则单独看都对,合在一起顺序错了,结果就反了。排查“为什么这条规则不生效”的时候,先看它前面有没有一条更宽泛的规则提前把包处理掉了,这种情况比规则本身写错要常见得多。
三个动作的区别也值得讲清。ACCEPT 是放行,包继续往下走。DROP 是静默丢弃,包消失了,对方收不到任何回复,只能等超时。对外暴露的服务端口,用 DROP 可以让扫描者更难判断端口状态。REJECT 是明确拒绝,内核会回一个拒绝信息,对方立刻知道此路不通。内网环境或者你希望对方快速失败的场合,REJECT 更友好;但对公网上的扫描行为,REJECT 等于告诉对方“这里有个活着的服务在拒绝你”,信息暴露得更多。选哪个不是风格问题,而是你想让对方知道多少。
匹配条件是规则的另一半。最常见的几类是:协议(tcp、udp、icmp),端口(目的端口 --dport、源端口 --sport、连续范围用冒号表示),地址(源地址 -s、目的地址 -d、网段用斜杠表示),接口(入接口 -i、出接口 -o),以及连接状态。状态匹配是最值得单独说的一类:一个 TCP 连接建立之后,后续的包都属于 ESTABLISHED 状态;与已有连接相关的包(比如某些协议协商出来的数据连接)属于 RELATED;无法识别的属于 INVALID。防火墙配置里最常见的一条规则,就是放行 ESTABLISHED 和 RELATED 状态的包——它的意思是“只要是已经建立的连接的回包,一律放行”。有了这条规则,你就不需要为每个出方向的请求单独写回程放行规则,规则表能短一大截。INVALID 的包通常直接丢弃,因为它往往意味着扫描或者协议异常。
常用操作:查看、添加、删除、保存
查看是最高频的操作。iptables -L 列出默认 filter 表的规则,加上 -n 让地址和端口以数字形式显示,不做反向解析,输出更快也更稳定;加上 -v 能看到每条规则匹配到的包数和字节数,这个计数器是排查的利器——一条规则的计数器始终是零,说明它从来没有匹配到包,要么是写错了位置,要么是被前面的规则抢了先;加上 --line-numbers 显示行号,删除规则时要靠行号定位。想看别的表,就用 -t 指定,比如 -t nat -L。还有一个很多人忽略的查看角度:iptables -S 以命令原样输出规则,适合把现有规则直接存成脚本或者贴给别人看。
添加规则有两个动词,含义不同。-A 是追加,放到链的末尾;-I 是插入,默认插到链的开头,也可以指定插到第几条之前。新手最容易在这里踩坑:链末尾往往有一条默认丢弃或者一条宽泛规则,用 -A 追加的放行规则排在它后面,永远轮不到执行。往已有规则表里加放行规则时,养成用 -I 指定位置的习惯,或者先看一眼现有顺序再决定。删除规则也有两种方式:按行号删(-D INPUT 2)和按内容删(把当初添加的整条命令换成 -D 再执行一遍)。按行号删的时候注意,删掉一条之后,后面规则的行号会往前挪一位,连续删多条要从后往前删,或者每次删之前重新看一眼行号。
清空规则用 -F,后面可以跟链名,只清一条链;不跟链名就是全清。还有两个配套操作容易被忽略:-X 删除用户自定义的空链,-Z 把计数器清零。排查时先 -Z 清零,再复现问题,看哪些规则的计数器动了,能快速定位包走到了哪一步。
改默认策略用 -P,比如把 INPUT 的默认策略设为 DROP。这里有个远程操作的铁律:先放行 SSH,再收默认策略,顺序反过来就会把自己锁在外面。而且改默认策略这个动作本身不会立刻把你踢下线——已经建立的 SSH 连接属于 ESTABLISHED 状态,只要规则里放行了已建立连接,当前会话就不会断;真正危险的是断开重连之后进不来。所以每次改完,先不要关闭当前会话,另开一个窗口试一次能不能重新登录,确认无误再收尾。
保存和恢复是把内存里的规则变成持久配置。iptables-save 把当前规则以文本形式输出,重定向到文件就是备份;iptables-restore 从文件读回。不同发行版的持久化方式不一样:Debian 和 Ubuntu 一系常用 iptables-persistent 这个包,安装时它会把当前规则存下来,开机自动加载;RHEL 一系传统上把规则存在系统配置文件里,由服务在启动时加载。具体路径和服务名以你所用发行版的现行文档为准,这里不写死。关键习惯只有一个:每次改完规则,先 iptables-save 备份一份,再谈持久化。没有备份的规则表,改崩了连回滚的起点都没有。
自定义链是规则多起来之后值得用的组织手段。iptables -N 创建一条自己的链,规则可以跳转到这条链上处理,处理完再回来。它的好处是把一类规则收拢到一起:比如把所有日志规则放进一条日志链,把某种防护逻辑放进一条专用链,主链保持干净,排查时也更容易定位。自定义链用完要删的时候,必须先确认没有规则还在跳转到它,否则删不掉——这个报错不是故障,是内核在提醒你还有引用关系没清理。
一套可用的基线:先放行,再收紧
讲了这么多机制,最后落到一套可以直接用的基线思路上。注意这只是一个思路,不是照抄的脚本:每个人的服务端口不一样,照抄别人的基线而不理解每一行的含义,是防火墙配置里最危险的做法。
顺序是基线的灵魂。第一步永远是先放行 SSH,而且是先验证放行生效,再动默认策略。第二步是放行回环接口,也就是本机自己访问自己的流量,很多服务依赖回环通信,把它误封了会出现各种莫名其妙的故障。第三步是放行已建立和相关连接的回包,这是前面讲过的状态匹配规则。第四步是按需放行对外服务的端口,比如网站的 80 和 443,只放你确实在跑的服务,不跑的服务不要顺手放行。第五步是把 INPUT 和 FORWARD 的默认策略收成 DROP,OUTPUT 保持 ACCEPT——内网服务器通常不需要限制出方向,收紧出方向反而容易把更新、时间同步这类后台流量误伤。
这套基线里有几个容易被忽略的点。ICMP 也就是 ping,要不要放行是个取舍:放行可以让网络排障更方便,禁掉则能让服务器在扫描面前更安静。折中的做法是放行但限速,后面讲日志时还会提到限速的思路。无效包直接丢弃,放在靠前的位置,避免它们走到后面的逻辑里浪费匹配。还有一个习惯:给重要的规则写注释。iptables 规则本身没有注释字段,但可以在脚本里用注释行说明每条规则的用途,三个月后回来看,或者交给别人接手时,注释就是唯一的上下文。
NAT 的部分单独说,因为它和过滤是两件事,思路也不一样。内网机器共享一个公网地址上网,靠的是 POSTROUTING 链上的源地址转换:出去的包把源地址改成公网地址,回来的包再改回来。公网地址固定的时候用 SNAT 指定改成哪个地址,地址会变的场合(比如拨号)用 MASQUERADE,让内核自动取出口地址。端口映射走的是另一条链:PREROUTING 上的目的地址转换,把公网某个端口的流量改到内网某台机器的某个端口,同时要在 FORWARD 链上放行这条转发流量——只做地址转换不放行转发,包一样过不去。这两类规则的共同点是:做之前先确认你真的需要它。单机服务器不需要 NAT,顺手加上 NAT 规则只会让排查更复杂。
日志是基线里最容易被滥用的部分。LOG 目标不会放行也不会丢弃,它只记一笔然后让包继续往下匹配,所以日志规则要放在真正起作用的规则之前才有意义。记录被丢弃的包能帮你发现误封和扫描行为,但不加限制地记录所有丢弃包,日志很快就会被扫描流量刷爆。做法是给日志规则加上限速,比如每分钟只记几次,超过的部分不再记录。这样既能看到异常的苗头,又不会让日志本身变成问题。日志默认写到系统日志里,具体文件以发行版为准,排查时先确认日志落到了哪个文件,再去翻。
基线立起来之后,还有一件事容易被忽略:规则是会变质的。服务下线了,放行它的端口规则还留在表里;临时排障加的放行规则,问题解决之后忘了删;同事离职前加的测试规则,没人知道是干什么的。时间一长,规则表里就攒下一堆没人敢动的条目,审计的时候每条都要重新猜用途。养成定期复查的习惯,成本其实很低:隔一段时间看一次带计数器的规则表,计数器长期为零的规则,先确认它是不是季节性服务或者备用通道,确认不是就删掉;每次改规则都同步更新脚本里的注释和备份文件,让内存里的规则、脚本里的规则、备份文件里的规则三处一致。规则表和代码一样,怕的不是改动,而是没人记得为什么这样写。基线不是一次写好就一劳永逸的东西,它是你和服务器之间的一份约定,约定要定期对账才作数。
误封自救:改规则之前先留后路
防火墙配置最常见的事故不是规则写错,而是把自己锁在服务器外面。远程改规则之前,先把后路留好,比事后找办法更重要。
最稳妥的办法是定时回滚:动手之前先设一个几分钟后自动执行的任务,到时把规则清空、把默认策略改回放行。改完之后确认能正常登录,再把这个任务取消。如果改的过程中出了问题,人不需要做任何事,等几分钟规则就自动回滚了。设任务的工具以系统现行为准,关键是养成“先设回滚、再改规则”的顺序,而不是凭信心直接改。
如果已经锁在外面了,还有几条路。第一条是云服务商提供的控制台或者 VNC,那是独立于网络的带外通道,不受防火墙规则影响,从那里登录进去把规则改回来。第二条是重启:如果规则没有保存,重启之后内存里的规则就消失了,服务器回到没有防火墙的状态,你可以重新登录再慢慢配。这条路成立的前提恰恰是“规则没保存”——平时觉得麻烦的“重启丢规则”,在事故里反而成了救命稻草。第三条是检查规则到底封了什么:从控制台进去之后,先看规则表的计数器,再看系统日志,往往能立刻发现是哪条规则挡住了登录流量,而不是盲目地全清重配。
还有一种更隐蔽的误封:规则本身没问题,但改完之后某个服务不通了。这时候的排查顺序是先看计数器,确认包有没有走到预期的规则;再看日志,确认包是被哪条规则丢弃的;最后才怀疑服务本身。很多“服务挂了”的报修,最后查出来都是防火墙规则顺序问题,而不是服务配置问题。计数器和日志这两个工具,比反复重启服务管用得多。
边界:iptables 管什么、不管什么
最后讲清几条边界,免得对 iptables 有不切实际的期待。
第一,iptables 工作在网络层和传输层,它看得到地址、端口、协议和连接状态,看不到应用层的内容。指望它识别 HTTP 请求里的恶意字符串,是一种误用:对明文流量它勉强能做简单的字符串匹配,对 HTTPS 流量则完全无效,因为内容是加密的。应用层的防护应该交给专门的工具,比如反向代理和 WAF,而不是在防火墙规则里堆字符串匹配。
第二,新系统上 iptables 命令和 nftables 的关系容易让人困惑。不少发行版已经把底层换成了 nftables,但仍然提供 iptables 命令作为兼容前端,规则最终翻译成 nftables 的规则集执行。日常使用中你不需要关心这个翻译过程,但要知道两件事:用 iptables 命令看到的规则,和直接用 nftables 工具看到的规则,可能是同一套东西的不同视图;以及某些发行版的文档会建议你直接学 nftables,那是更长远的选择,但不影响你现在用 iptables 把服务器管好。
第三,iptables 和 ufw、firewalld 的关系是前端和底层的区别。ufw 和 firewalld 都是把常用操作包装成更简单的命令,底层走的还是同一套内核机制。用哪个取决于你的习惯和发行版的默认配置,但不要在同一台机器上混用多个前端同时改规则——它们对规则表的理解方式不一样,互相覆盖之后,规则表会变成谁也说不清的状态。选定一个前端,就用它管到底;如果决定换前端,先把旧的规则理干净。
第四,防火墙只是服务器安全的一层。它管的是“谁能连进来”,不管“连进来之后能干什么”。SSH 密钥、Fail2ban、最小权限、及时更新,这些是另外几层,缺了哪一层,单靠防火墙都补不回来。把 iptables 放到整个安全体系里看,它的位置是第一道门,而不是整座房子。
回到开头那句话:iptables 是你和内核对话的工具。表与链告诉你包走过的路,匹配顺序告诉你规则生效的规矩,基线思路告诉你从哪里开始,误封自救告诉你搞砸了怎么办。把这四件事想清楚,剩下的就是在自己的机器上多练几次——先在测试机上把基线跑通,把误封和自救都演练一遍,再动生产环境的规则。防火墙这个东西,练的时候出问题是好事,上线之后出问题才是事故。
相关阅读: