nftables 规则的原子化更新
简单来说,nftables 规则的原子化更新,是指你提交的整套规则变更,要么全部生效,要么一个也不生效。这能消除更新过程中的“中间状态”,避免规则集不完整导致的安全漏洞或网络中断。
🔄 原子化更新 vs. 非原子化更新
相比之下,非原子化更新(比如一个执行多条nft命令的 Bash 脚本)带来的风险就大得多。
| 特性 | 非原子化更新 (如 Bash 脚本) | 原子化更新 (如nft -f) |
|---|---|---|
| 执行方式 | 顺序执行多条独立的nft命令。 | 解析完整文件后,作为一个整体提交给内核。 |
| 中间状态 | 存在。先执行的命令生效,后执行的尚未执行,规则集不完整。 | 不存在。新旧规则集瞬间切换,没有中间状态。 |
| 错误处理 | 部分生效。某条命令出错时,之前成功的命令已经生效,无法自动回滚。 | 全有或全无。任何解析或提交错误都会使整个事务失败,现有规则集保持不变。 |
| 对生产影响 | 风险高。可能导致服务中断或安全漏洞。 | 安全。确保规则集始终处于一个完整、预期的状态。 |
🚀 实现原子化更新的方式
实现原子化更新的方式主要有两种:
原子替换:
nft -f <规则文件>
这是最直接、推荐的方法。nft会先解析并验证文件中的全部规则,如果一切正确,就将它们作为一个整体提交给内核。然后内核会原子地执行切换,新规则集一次性生效。这个过程常被比作iptables的iptables-restore。如果文件中有任何错误,整个事务会中止,当前运行的规则集保持不变。原子替换的核心在于利用了内核的事务机制,将文件中所有规则命令作为一个整体进行原子提交。
调试追踪:
nft -f --check <规则文件>
如果想先验证一个规则集文件是否能被安全地加载,可以使用--check选项:nft-f--checkmy-new-ruleset.nft这个命令会在不实际更改当前规则集的情况下,执行完整的解析、验证和事务准备流程。它和原子替换的区别在于最终是否执行提交。
⚙️ 背后的机制:世代 ID 与位掩码
nftables 的原子更新能力源自其创新的内核设计。
其核心思想是使用一个世代 ID (generation ID)来为每个规则集版本进行标记。世代 ID (generation ID)是一个单调递增的计数器,用于标识系统中的每个规则集版本。新的规则集会先在与主规则表分离的“预备区”构建。当用户提交新规则集时,内核中的nf_tables子系统会执行一个操作:
- 增加一个全局的
genid(世代计数)。 - 对每个规则使用一个
genmask(世代掩码)的位掩码来标记它在当前和下一个世代的有效性。 - 在对数据包进行处理时,内核只检查
genmask中与当前世代相对应的位。如果该位为1,就表示此规则在当前世代是激活的。 - 当需要切换到新规则集时,原子性地更新
genid,让新的genmask生效。新规则集被激活,旧规则集被标记为无效并随后被垃圾回收。
在这种设计下,完整替换和增量更新都具备原子性。因为整个提交过程拥有事务语义,任何失败都会导致回滚,并保持genid不变。
⚠️ 注意事项:确保真正原子化
使用
nft -f文件,而不是 Bash 脚本!
这是最常见的误区。将多条nft命令写入 Bash 脚本并执行,不是原子操作。当脚本执行nft ...时,规则立即生效,将网络置于部分配置状态,可能误拦了合法的 SSH 连接,让你彻底被锁在服务器外面。必须使用nft -f这个原生机制以实现原子化。重要实践
- Include 文件与原子性:原子替换是作用于命令整体的。如果主文件包含了其他文件 (
include),所有文件规则都在一次事务中加载。但如果文件中有flush命令(如flush table ...),它只会在整个事务最终提交时执行一次。 - 避免规则重复:使用
nft -f重新加载一个未修改的规则集文件,可能会追加新规则,导致重复。需要清空整个规则集,即编辑你的规则文件,在开头添加flush ruleset命令,就能确保每次加载的是全新、干净的规则集。 - 注意清空的范围:
flush table仅清空指定表的内容。而flush ruleset则是清除所有表中的全部定义。
- Include 文件与原子性:原子替换是作用于命令整体的。如果主文件包含了其他文件 (
nft接受的两种文件格式
以上提到的nft -f命令支持两种文件格式。要生成一种安全的通用格式,可以先导出当前配置,再修改导出的文件:nft list ruleset>my-firewall.nft编辑
my-firewall.nft,在文件开头加上一行flush ruleset,确保每次加载都从干净的状态开始。之后,就可以通过nft -f my-firewall.nft原子化地加载它了。另一种格式是脚本格式(Scripting Format),包含
add、create等命令,通常也需要flush配合使用。
🎯 总结:如何正确地原子化更新
原子化更新是 nftables 最安全、最可靠的操作方式,建议遵循以下最佳实践:
- 文件操作:不要使用一个包含多条
nft命令的 Bash 脚本来更新防火墙规则。 - 远程管理:在远程管理防火墙时,绝对不要在 SSH 会话里直接执行
flush ruleset之类的清空命令。务必先准备好一个包含flush ruleset的完整规则文件,再用nft -f加载,以防把自己锁在外面。 - 首选命令:使用
nft -f <规则集文件>来执行你的更新操作。 - 验证工具:在真正应用新规则集前,先用
nft -f --check <规则集文件>验证一下其语法。 - 习惯做法:让你的防火墙规则文件成为一个“自包含”的完整规则集,最简单的方法就是将其顶部加上
flush ruleset。
