Linux防火墙内核实现深度解析:从iptables到nftables的完整技术图谱
你所敲下的每一个
iptables命令,在新系统上可能早已不是你以为的那个iptables了。这不是一次简单的版本升级,而是一场从内核数据结构、算法设计到命令行工具的彻底重构。
一、引言——一次”正常”的报错引发的思考
2026年春天,一位系统管理员像往常一样登录新交付的Arch Linux服务器,熟练地敲下一行维护了十年的防火墙命令:
iptables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
然而,这次迎接他的不是熟悉的”OK”,而是一段令他困惑的报错信息。他第一反应是:”linux系统的iptables是不是改了?”
答案是肯定的。这背后涉及的是Linux防火墙框架二十年来最深刻的一次变革——从xtables到nftables的内核级替换。这场变革不是一次简单的版本升级,而是从内核数据结构、算法设计到命令行工具的彻底重构。
二、从内核到应用——先看全貌
2.1 宏观架构图
在深入细节之前,我们先从整体上理解:当你敲下iptables命令时,数据流是如何在内核和用户态之间流转的。

这张图的核心信息:目前Linux系统内实际上并存着两套防火墙实现:
- 新路径(绿色):
iptables命令默认指向iptables-nft兼容层,底层实际执行的是nftables内核模块 - 旧路径(灰色):
iptables-legacy作为备选,直接操作传统的xtables内核模块
2.2 第一问:iptables到底还能不能用?
结论先行:能。在新内核上,旧有的iptables命令依然可用,绝大多数脚本和命令都能正常运行。
但”能用”的背后有一个重要变化:你敲下的iptables命令,底层可能已经不再是你以为的那个iptables了。
怎么知道我的iptables是”新”还是”旧”?
iptables --version
- 输出
iptables v1.8.4 (nf_tables)→ 你正在使用新内核的兼容层,底层是nftables。 - 输出
iptables v1.8.4 (legacy)→ 你还在使用传统的xtables内核模块。
如果遇到兼容问题,大多数发行版都提供了回退方案,可以安装iptables-legacy包切换回旧模式。
三、用户态到内核态的两条路径
理解了”能用”之后,我们需要更细致地看清整个系统的架构。目前,Linux系统中实际上并存着两套完整的工具链:
| 工具名称 | 真实身份 | 通信对象 | 当前角色 |
|---|---|---|---|
iptables | iptables-nft | nftables内核 | 兼容层(翻译官) |
iptables-legacy | iptables原版 | xtables内核 | 旧后端(备选) |
nft | nftables原生工具 | nftables内核 | 新原生(主力) |
3.1 内核中的多种iptables实现
你提到的”iptables在内核不是有好几种吗”完全正确。在旧内核(xtables框架)中,针对不同的网络协议层,存在完全独立的内核模块和用户态工具,各自为政。
| 协议族 | 内核模块 | 用户态工具 | 管理对象 |
|---|---|---|---|
| IPv4 | ip_tables.ko | iptables | IPv4 数据包过滤/NAT |
| IPv6 | ip6_tables.ko | ip6tables | IPv6 数据包过滤/NAT |
| ARP | arptables.ko | arptables | ARP 报文过滤 |
| 网桥/以太网帧 | ebtables.ko | ebtables | 二层(以太网帧)过滤 |
这四套系统各自完全独立:
- 各自维护自己的表(filter/nat/mangle…)
- 各自挂载到netfilter的不同钩子
- 规则互不共享,无法跨协议复用
例如,你需要为双栈服务器开放80端口时,必须分别执行:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
3.2 内核源码中的对应路径
如果你翻阅Linux内核源码,会看到这种”分立”在目录结构上的直观体现:

3.3 nftables如何统一
而 nftables 用一个统一的 nf_tables.c 核心模块,配合地址族参数(NFPROTO_IPV4 / NFPROTO_IPV6 / NFPROTO_INET)来区分处理对象,代码复用率极高。inet地址族甚至可以同时覆盖IPv4和IPv6,一套规则、双重生效:
# nftables方式:一条规则同时生效于IPv4和IPv6
nft add rule inet filter input tcp dport 443 accept
四、”表”的架构革命——从静态内置到动态可定义
“表(table)”是iptables和nftables中最核心的概念之一。在这次底层变革中,”表”的角色发生了根本性的变化。
4.1 iptables的”静态内置表”架构
在旧内核(xtables)中,表是预先定义好的、固定的,你无法增删,只能使用内核编译时决定的那几张。

关键特征:
- 表的种类固定:永远就是这5张表(filter/nat/mangle/raw/security)。
- 表与钩子绑定固定:每张表能挂载到哪些钩子,是内核写死的。例如
nat表无法挂载到INPUT钩子。 - 用户无法自定义表:只能往现有表里加规则,不能新建一张自定义表。
4.2 nftables的”动态可定义表”架构
在新内核(nftables)中,表变成了用户可以自由创建和删除的容器,不再有内置限制。

关键特征:
- 表由用户定义:创建的表叫什么名字、处理哪个地址族(IPv4/IPv6/inet),完全由你决定。
- 表与钩子解耦:创建链(chain)时可以自由指定它挂载到哪个钩子。
- 一个钩子可以有多个表:不同用户可以创建不同的表,各自独立管理。
4.3 新旧”表”的内核存储结构对比

4.4 “表”的对比总结
| 维度 | iptables (xtables) | nftables |
|---|---|---|
| 表的创建 | 内核编译时固定,用户无法增删 | 用户可通过 nft add table 自由创建 |
| 表的种类 | 仅5种(filter/nat/mangle/raw/security) | 名称自定义,无限种 |
| 地址族绑定 | 每个表隐式绑定到IPv4或IPv6(需独立工具) | 创建时可指定ip/ip6/inet(同时支持双栈) |
| 表与钩子关系 | 内核写死:每张表只能挂载到特定钩子 | 用户自由指定 |
| 多租户隔离 | 不支持,所有规则混在同一张表里 | 支持:不同用户/容器可创建独立表 |
| 内核数据结构 | 全局静态数组/链表,单例模式 | 动态链表,可任意增删节点 |
五、数据结构与算法——性能差异的根源
5.1 iptables:线性链表 O(N)
iptables将所有规则存储在一个平坦的线性链表中。数据包进入时,系统从链头开始逐一匹配,命中即停,否则遍历全表。

内核数据结构(定义在include/linux/netfilter/x_tables.h):
struct xt_table {
struct list_head list; // 全局表链表
unsigned int valid_hooks; // 该表挂载的钩子位图
struct xt_table_info *private; // 实际规则集
};
struct xt_table_info {
unsigned int number; // 规则数量
unsigned int *entries; // 规则入口偏移数组
struct xt_counters *counters; // 计数器
};
5.2 nftables:哈希表与表达式树 O(1)
nftables采用哈希表和表达式树等高级数据结构。数据包进入时,系统通过计算哈希值直接定位到对应的规则集。

内核数据结构(定义在include/net/netfilter/nf_tables.h):
struct nft_table {
struct list_head list; // 表链表
u16 family; // 地址族
struct list_head chains; // 属于该表的所有链
};
struct nft_chain {
struct list_head list; // 链链表
struct nft_table *table; // 所属表
struct list_head rules; // 属于该链的所有规则
};
struct nft_rule {
struct list_head list; // 规则链表
struct nft_expr *exprs; // 表达式链表(核心差异!)
};
struct nft_expr { // 单个表达式
const struct nft_expr_ops *ops; // 操作函数指针
unsigned char data[]; // 表达式私有数据
};
5.3 核心差异:规则是”对象”还是”表达式流”
- iptables:规则是一个”黑盒”对象,匹配条件和动作硬编码在一起,内核只能整体处理。每条规则在内存中是连续存储的变长结构。
- nftables:规则是表达式的有序列表,每个表达式独立实现
eval()方法。内核通过遍历表达式链表完成规则执行。这种设计使得扩展新功能时无需修改内核核心代码。
| 特性 | iptables | nftables |
|---|---|---|
| 遍历方式 | 线性链表遍历 | 表达式流式求值 |
| 匹配粒度 | 整条规则作为一个单元 | 每个表达式独立求值 |
| 短路机制 | 需匹配所有条件后才执行动作 | 任一表达式求值失败即可终止 |
| 优化潜力 | 规则顺序固定,难以优化 | 表达式可独立优化和重组 |
| 查找复杂度 | O(N) | O(1) |
5.4 性能实测对比(基于社区benchmark)
| 测试场景 | iptables(legacy) | nftables |
|---|---|---|
| 100条规则,单流吞吐量 | 约950 Mbps | 约980 Mbps |
| 1000条规则,单流吞吐量 | 约720 Mbps | 约950 Mbps |
| 5000条规则,单流吞吐量 | 约320 Mbps | 约930 Mbps |
| 规则集加载时间(5000条) | 约850 ms | 约120 ms |
| 规则集原子切换 | 不支持 | < 10 ms |
结论:nftables在小规模规则集下性能略优于iptables,在大规模规则集下具有碾压级优势。
六、规则更新机制——原子性对决
生产环境最忌讳的,是在变更过程中出现”无规则空窗期”。
6.1 iptables:非原子更新,存在风险
iptables的规则更新通过setsockopt系统调用逐条下发。当使用iptables-restore恢复大批量规则时,采用”先清空所有规则,再逐条载入新规则”的策略。在清空和载入之间的那几毫秒甚至几秒内,服务器可能处于完全无防护状态。
// net/netfilter/x_tables.c
struct xt_table_info *xt_replace_table(struct xt_table *table,
struct xt_table_info *newinfo)
{
struct xt_table_info *oldinfo;
oldinfo = table->private;
table->private = newinfo; // 先替换指针
synchronize_rcu(); // 等待所有CPU完成当前遍历
return oldinfo;
}
6.2 nftables:原子事务更新,万无一失
nftables通过netlink事务机制实现原子更新。用户提交的规则集文件中的所有变更被打包成一个事务列表,内核先对所有变更进行整体校验,确认无误后一次性提交。

关键机制:
- 任何一条不合法都会导致整个事务失败(全部回滚)。
- 所有变更在确认无误后一次性提交,中间不存在任何不完整的中间状态。
七、钩子挂载与调度机制
无论iptables还是nftables,都依赖于Linux内核netfilter框架提供的5个标准钩子点:

7.1 iptables:固定优先级挂载
iptables使用固定优先级挂载钩子。所有表在同一个钩子上按硬编码顺序执行:
static struct nf_hook_ops ipt_ops[] __read_mostly = {
{
.hook = ipt_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FILTER, // 固定优先级
},
};
7.2 nftables:动态优先级,流水线处理
nftables允许用户自定义链在钩子上的执行顺序:
struct nft_chain_hook {
u32 type; // 钩子类型
u32 priority; // 优先级(数值越小越先执行)
s32 num; // 钩子编号
struct nf_hook_ops ops;
};
用户可以通过配置指定优先级:
# 优先级 -100 的链会在标准链之前执行
nft add chain ip filter input { type filter hook input priority -100 \; }
# 优先级 100 的链会在标准链之后执行
nft add chain ip filter input { type filter hook input priority 100 \; }
这种设计实现了类似”流水线”的处理模型。
八、应用控制程序——”马甲”与”真身”
你的理解完全正确:对应的应用控制程序确实不一样了。
目前系统中有两套应用控制程序并存:
iptables(新版/兼容层):其真实身份是iptables-nft。它是一个兼容层程序,负责解析iptables风格的指令,翻译成nftables能理解的nft命令,最终由nftables内核执行。这是为了兼容旧脚本而存在的”翻译官”。nft(原生工具):这是nftables框架原生的管理工具,直接与内核的nftables子系统通信,是配置新一代防火墙的核心命令。
两者是独立的程序文件,但iptables命令默认指向了兼容层,而非原生的iptables-legacy。
九、各大发行版现状
| Linux发行版 | 默认状态 | 切换方式 |
|---|---|---|
| Arch Linux | 2026年4月起,默认使用iptables-nft(nf_tables后端) | 可选iptables-legacy包回退 |
| RHEL 8/9 | 默认使用iptables-nft | 可安装iptables-legacy切换 |
| Fedora | 默认使用iptables-nft | 可通过alternatives切换 |
| Ubuntu 20.10+ | 默认使用iptables-nft | 可使用update-alternatives切换 |
| Debian 10+ | 默认使用iptables-nft | 可安装iptables-legacy |
| OpenSUSE Leap 15.3+ | 默认使用iptables-nft | 可切换 |
十、总结——一张表看懂所有差异
| 维度 | iptables(旧时代/xtables) | nftables(新时代) |
|---|---|---|
| 内核引擎 | xtables | nftables |
| 内核模块数量 | 多模块分立(ip_tables/ip6_tables/arptables/ebtables) | 统一核心(nf_tables) |
| 表的创建 | 内核固定5张,用户不可增删 | 用户自由创建,数量不限 |
| 表与钩子 | 内核硬编码绑定 | 用户自由指定 |
| 地址族支持 | 多工具分立(iptables/ip6tables) | 统一工具 + inet地址族 |
| 数据结构 | 线性链表 O(N) | 哈希表/树 O(1) |
| 规则结构 | 匹配+动作硬编码为一个对象 | 表达式链表,灵活组合 |
| 更新方式 | 拆分操作,有”空窗期” | 原子事务,无风险 |
| 多租户隔离 | 不支持 | 支持(不同用户可建不同表) |
| 扩展方式 | 需编译内核模块 | 内核模块 + 用户态组合 |
| 调试能力 | 有限 | nft monitor实时追踪 |
给不同场景用户的建议
场景一:你是普通运维,习惯用iptables命令
→ 放心继续使用。旧脚本和命令依然有效,底层会自动翻译执行。
场景二:你在写新项目,追求性能和现代化管理
→ 建议直接学习nft命令,享受原子更新和哈希查找的性能优势。
场景三:你维护的软件(如Docker、Kubernetes)出现异常
→ 排查是否与新后端(nf_tables)的兼容性问题。可暂时切换到iptables-legacy模式作为快速回退方案。
这场从iptables到nftables的变革,不是一次简单的版本号跳跃,而是一场从内核数据结构、算法设计到命令行工具的彻底重构。理解这些底层差异,将帮助你在遇到性能或兼容性问题时做出更准确的判断——当你再次敲下iptables --version并看到(nf_tables)时,你已经不再是那个只会复制粘贴命令的运维新手了。