在CentOS 7及后续版本中,firewalld作为默认防火墙管理工具,本质上是对iptables的封装。很多人以为两者是二选一的关系,其实它们可以共存,甚至在某些复杂场景下必须共存。当firewalld和iptables同时运行时,规则冲突、流量被意外丢弃、服务莫名其妙不可用,是运维人员最头疼的问题。核心原因在于两者都在向netfilter内核框架写入规则,但firewalld并不感知直接通过iptables命令添加的规则,反之亦然。这就导致了一个看似混乱的局面:你明明在firewalld里放行了端口,流量却过不来,因为iptables里有一条更早匹配的DROP规则;或者重启firewalld后,手动添加的iptables规则全部消失。

要理清这团乱麻,必须从规则的处理顺序和存储区域入手。netfilter的规则链有固定的优先级:raw表、mangle表、nat表、filter表。firewalld默认只操作filter表,但它的直接规则可以插入到其他表。而iptables命令可以直接操作所有表。当两者共存时,谁先生效取决于规则被插入到哪个表的哪个链,以及插入的位置。firewalld的规则存储在内存中的firewalld数据库,iptables命令直接写入内核。firewalld重启或重新加载时,会清空由它管理的链,重新写入自己的规则集,这个过程中会覆盖掉部分iptables规则,但不是全部。

理解firewalld的规则结构

firewalld将规则组织成区域(zone)和服务(service),最终转换成iptables规则。执行 firewall-cmd --list-all 可以看到当前区域的配置。这些规则被写入到iptables的特定链中:INPUT_ZONES、FORWARD_ZONES等。firewalld创建的链名称带有 fw 前缀,比如 fw_public、fw_public_allow。当你用iptables -L -n查看时,会发现INPUT链的第一条规则通常是跳转到INPUT_ZONES,然后由INPUT_ZONES分发到各个具体区域的链。这意味着,如果你直接用iptables -I INPUT添加规则,这条规则会插入到INPUT链的最前面,优先级高于firewalld的所有区域规则。

直接规则(direct rules)是桥接两者的关键

firewalld提供了direct接口,允许用户添加原始的iptables规则,这些规则会被firewalld管理并持久化。使用direct规则的好处是,它们会随firewalld一起保存和恢复,不会在重启后丢失。添加direct规则的命令格式如下:

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 8080 -j ACCEPT

这条规则会被插入到INPUT链的最前面,优先级编号0表示最优先。direct规则存储在 /etc/firewalld/direct.xml 文件中,重启后依然有效。需要注意的是,direct规则的生命周期与firewalld绑定,如果firewalld服务停止,这些规则也会被清除。这是很多人踩过的坑:以为加了direct规则就万事大吉,结果firewalld一重启或者异常退出,规则全没了,服务暴露在公网上。

共存时的规则优先级实战分析

假设一个场景:服务器上同时运行着firewalld和自定义iptables脚本。firewalld默认区域为public,开放了22和80端口。同时有一个iptables脚本在开机时执行,添加了一条针对443端口的DROP规则。执行顺序是:系统启动,firewalld先启动并写入规则,然后iptables脚本执行。脚本用 iptables -A INPUT -p tcp --dport 443 -j DROP 添加规则,这条规则被追加到INPUT链末尾。此时访问443端口的流量,会先经过INPUT链前面的firewalld规则。由于firewalld没有放行443,流量继续向下匹配,最终被这条DROP规则拦截。表面上看是iptables的DROP生效了,实际上是两者共同作用的结果。

如果把脚本改成 iptables -I INPUT -p tcp --dport 443 -j ACCEPT,规则插入到INPUT链最顶端。这时443端口的流量在进入任何firewalld规则之前就被ACCEPT了,firewalld的配置完全被绕过。这就是为什么有些服务器上,明明firewalld配置很严格,但某些端口却能通,排查半天才发现是启动脚本里偷偷加了iptables规则。

规则持久化的混乱局面

CentOS上iptables规则的持久化通常依赖iptables-services或netfilter-persistent。如果安装了iptables-services并启用了服务,它会在系统启动时从 /etc/sysconfig/iptables 加载规则,在关机时保存规则。这就会和firewalld产生冲突:firewalld启动时写入自己的规则,然后iptables服务启动,又覆盖了一部分规则。更糟糕的是,firewalld重新加载时(比如执行firewall-cmd --reload),会重置所有由它管理的链,但不会动那些不在它管理范围内的链。如果你在iptables里自定义了一个MY_CHAIN,firewalld不会去动它,但引用这个链的规则可能会因为主链被重置而失效。

解决这个混乱局面的方法有两种:一是彻底停用iptables-services,所有规则都通过firewalld的direct接口管理;二是停用firewalld,完全回退到iptables。对于必须共存的情况,需要建立严格的规则管理规范。

建立清晰的规则管理策略

第一步,确定哪个工具作为主管理工具。推荐以firewalld为主,因为它提供了更友好的管理界面和动态更新能力。所有常规的端口开放、服务放行,都通过firewall-cmd操作。第二步,将必须用iptables原生语法表达的复杂规则,通过direct接口添加,并做好文档记录。例如,需要对特定源IP进行限速:

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -s 192.168.1.100 -m limit --limit 10/min -j ACCEPT

第三步,禁止直接使用iptables命令添加临时规则。如果确实需要临时测试,测试完毕后立即删除,并记录在案。第四步,在 /etc/firewalld/direct.xml 中统一管理所有direct规则,这个文件就是你的规则文档。第五步,定期检查规则的一致性。可以用 iptables-save 导出当前内核中的规则,与firewalld的配置进行对比,发现不一致的地方立即排查。

排查规则冲突的实用技巧

当服务不可用时,首先要定位流量被哪条规则拦截了。最直接的方法是在iptables的各个链上添加日志规则。在INPUT链最前面插入一条日志规则,记录所有到达的流量:

iptables -I INPUT 1 -j LOG --log-prefix "INPUT-DEBUG: " --log-level 4

然后用 tail -f /var/log/messages 观察日志,看流量是否到达了INPUT链。如果日志里没有记录,说明流量在更早的阶段被拦截了,可能是raw表或者网卡层面。如果日志里有记录但服务还是不通,就在后续的链上继续加日志,逐步缩小范围。另一个实用工具是iptables的计数器,iptables -L -n -v 可以看到每条规则匹配的包数量和字节数,通过观察计数器的变化,可以快速定位是哪条规则在处理流量。

firewalld的富规则(rich rules)与iptables的关系

firewalld的富规则提供了一种更人性化的语法来生成复杂的iptables规则。富规则最终也会被转换成iptables规则,写入到对应区域的链中。例如,允许特定源IP访问特定端口,并记录日志:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" log prefix="MYSQL-ACCESS: " level="info" accept'

这条富规则会被转换成多条iptables规则,包括日志记录和ACCEPT。富规则的好处是语法清晰,易于维护,而且完全受firewalld管理,不会出现与iptables手动规则混淆的情况。对于能用富规则表达的需求,优先使用富规则而不是direct规则,可以降低管理复杂度。

容器和虚拟化环境下的特殊考量

Docker和libvirt等服务会直接操作iptables,创建自己的链和规则,例如DOCKER链、LIBVIRT_FWX链等。这些服务与firewalld共存时,冲突更加复杂。Docker默认会修改FORWARD链的策略为DROP,并添加自己的转发规则。如果firewalld也配置了转发规则,两者可能互相干扰。解决方案是在firewalld中配置正确的转发策略,或者将Docker的网络接口绑定到firewalld的特定区域。例如,将docker0网桥绑定到trusted区域:

firewall-cmd --permanent --zone=trusted --change-interface=docker0

这样firewalld就会对docker0接口的流量采取信任策略,不再进行拦截,由Docker自己的iptables规则全权处理。对于libvirt的虚拟网络,处理方式类似,将virbr0接口绑定到trusted区域或配置特定的区域规则。

自动化运维中的规则管理

在Ansible、Puppet等自动化工具中管理防火墙规则时,必须明确操作的对象是firewalld还是iptables。混用会导致每次执行playbook都产生不同的结果。推荐的做法是:在playbook中统一使用firewalld模块管理规则,对于firewalld模块不支持的复杂规则,使用shell或command模块调用firewall-cmd的direct接口。绝对避免在同一个playbook中既用firewalld模块又用iptables模块。如果必须使用iptables命令,应该在任务中添加检查步骤,确保规则添加到了正确的位置,并且在playbook的handlers中配置firewalld的reload操作,以保持一致性。

安全审计与合规

从安全审计的角度看,firewalld和iptables共存时,必须能够清晰地回答“当前生效的防火墙规则是什么”这个问题。iptables-save的输出是最终答案,因为它直接反映了内核中的规则。但审计人员也需要看到firewalld的配置,以确认哪些规则是持久化的、哪些是临时的。建议定期将两者的配置导出并存档:firewall-cmd --list-all-zones 导出firewalld配置,iptables-save 导出内核规则,两者一起保存。对于合规要求严格的环境,应该建立变更管理流程,所有防火墙规则的修改都必须通过firewalld进行,并记录在direct.xml或zone文件中,禁止直接操作iptables。

当firewalld和iptables共存时,规则管理的核心原则是:明确主从关系,统一管理入口,避免直接操作iptables,善用direct和富规则接口,建立文档和审计机制。理解了两者之间的转换关系和优先级顺序,就能在这个看似混乱的局面中建立起秩序,让防火墙真正成为安全的屏障而不是故障的源头。