使用 iptables 和 nftables 进行 Linux 数据包过滤

最后更新: 2026-08-23 11:46:28 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

Linux 数据包过滤由 Netfilter 实现。Netfilter 是一个在网络栈中公开钩子的内核框架。iptables 和 nftables 并不是相互独立的防火墙,而是在这些钩子处编程数据包分类和处理动作的用户空间接口。

iptables 是历史接口。使用 nft 命令配置的原生 nftables,用一种语言和一个 Netlink API,取代了彼此分离的 iptables、ip6tables、arptables 和 ebtables 规则管理模型。兼容工具仍然可以接受 iptables 语法,同时将规则写入 nftables 内核后端,因此仅凭命令名称无法确定当前使用的实现。

Netfilter 数据包流 #

数据包在网络栈中移动时,Netfilter 会调用已注册的操作:

NetworkIningresspreroutingRoutffeoorrDwweaacrriddsiopNnoe+st-twi-ron-orp-uku-tt-iO>nugtLocalouPtrpouctess

路由决策决定数据包经过哪条路径:

  • 发往本地套接字的数据包会经过 prerouting 和 input。
  • 经由主机转发的数据包会经过 prerouting、forward 和 postrouting。
  • 本地创建的数据包会经过 output 和 postrouting。
  • 可选的、设备特定的 ingress 钩子会在 prerouting 之前运行。

ingress 钩子可以在后续网络处理发生前拒绝流量,但此时 IP 分片尚未重新组装。因此,在该位置读取 TCP 或 UDP 标头的规则无法对非首个分片进行分类。

钩子名称描述数据包处理中的位置,而不是内置规则。nftables 默认不会创建链;规则集必须显式地将基础链附加到每个所需的钩子。

三种接口与两种内核 API #

当前系统可以提供三种命令接口,它们由两种不同的内核规则 API 支持:

iixgpp_ettttaaa/bbbsllleeeetssss-olsAceyPkgnIoatpcatyxiippttaabblleess-nsfytntnNafex_ttlaibnlkesAPInnffttablessyntax
  • iptables-legacy 编程历史上的 x_tables 内核 API。
  • iptables-nft 接受 iptables 语法,但通过兼容层和 nft_compat 编程 nf_tables。
  • nft 使用原生 nftables 语言和 nf_tables Netlink API。

nftables 后端会将规则编译为内核中网络专用虚拟机的指令。它复用现有的 Netfilter 钩子、连接跟踪、NAT、日志记录、排队以及其他支持基础设施。

应确定所选的 iptables 后端,而不是根据二进制名称推断:

sh
iptables --version
readlink -f "$(command -v iptables)"

包含 (nf_tables) 的版本字符串表示使用 nftables 后端。发行版 alternatives 或符号链接可能改变选择结果。

iptables 模型 #

iptables 将规则组织到预定义的表和内置链中。主要的 IPv4 表如下:

TablePrimary Purpose
filter接受或拒绝经过 INPUT、FORWARD 和 OUTPUT 的数据包。
nat为新连接创建地址或端口转换。
mangle修改数据包字段和标记,以进行特殊处理。
raw在连接跟踪之前运行,包括执行 NOTRACK 决策。
security应用 Linux 安全模块使用的安全标记。

链中的规则按顺序评估。匹配条件可以根据接口、地址、协议、端口、连接状态、标记或扩展特定属性选择数据包。随后,目标可以接受、丢弃、拒绝、记录、转换数据包,或将控制权转移到另一条链。

IPv4 和 IPv6 使用独立的命令和规则集:

sh
iptables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW -j ACCEPT

ip6tables -A INPUT -p tcp --dport 22 \
  -m conntrack --ctstate NEW -j ACCEPT

iptables-legacy 的增量修改需要读取、修改并替换一个表。因此,并发写入者会通过 xtables 锁进行协调。与包含许多独立 iptables -A 命令的 shell 脚本相比,iptables-restore 更为合适,因为它替换整个表,而不是暴露一系列部分策略。

nftables 模型 #

nftables 使用由族、表、链、规则和有状态对象组成的层次结构:

  1. 族选择数据包域:ip、ip6、inet、arp、bridge 或 netdev。
  2. 表是链、集合、映射、计数器和流表的命名空间。
  3. 基础链附加到 Netfilter 钩子,并声明链类型、优先级以及可选的默认策略。
  4. 普通链没有钩子,通过 jump 或 goto 到达。
  5. 规则从左到右评估表达式,并在表达式匹配时执行语句。

inet 族允许一个规则集同时处理 IPv4 和 IPv6 流量。协议特定表达式仍然只选择一个协议族;例如,ip saddr 匹配 IPv4 地址,而 ip6 saddr 匹配 IPv6 地址。

双栈主机防火墙 #

以下文件定义了一个有状态、默认拒绝的输入策略,同时允许环回流量、响应流量、诊断 ICMP,以及来自 IPv4 管理网络的 SSH:

nft
#!/usr/sbin/nft -f

flush ruleset

table inet firewall {
    set admin_ipv4 {
        type ipv4_addr
        flags interval
        elements = { 192.0.2.0/24 }
    }

    chain input {
        type filter hook input priority filter
        policy drop

        ct state invalid drop
        ct state established,related accept

        iifname "lo" accept
        meta l4proto { icmp, ipv6-icmp } accept
        ip saddr @admin_ipv4 tcp dport 22 accept
    }

    chain forward {
        type filter hook forward priority filter
        policy drop
    }

    chain output {
        type filter hook output priority filter
        policy accept
    }
}

将文件作为单个事务进行验证和加载:

sh
nft --check --file /etc/nftables.conf
nft --file /etc/nftables.conf
nft list ruleset

nft --file 会解析完整批次,并将其作为一个内核事务提交。如果任何命令失败,该事务都不会被部分提交。在同一个文件中放置 flush ruleset 可以原子地替换受管理的规则集。重复加载追加型文件时省略 flush,可能会产生重复规则。

当其他服务拥有不相关的 nftables 表时,不要使用 flush ruleset。此时只替换分配给当前组件的表。

钩子、优先级和判定 #

基础链声明控制其运行时机:

nft
type filter hook input priority filter; policy drop;
  • type filter 选择普通数据包过滤。
  • hook input 将链附加到本地投递的流量。
  • priority filter 将其置于标准过滤优先级,数值为零。
  • policy drop 在评估到达末尾且没有其他判定时生效。

多个基础链可以附加到同一个钩子。Netfilter 按数值优先级从低到高进行评估,其中包括分片重组、连接跟踪、目标 NAT 和源 NAT 等内部操作。

accept 判定会结束当前链,但同一钩子中优先级更靠后的基础链仍然可以丢弃该数据包。drop 判定会立即丢弃数据包。因此,防火墙分析必须包含钩子上的每一个基础链,而不仅仅是第一个返回 accept 的链。

普通链的控制流也有所不同:

  • jump service_checks 进入另一条链,并在该链到达末尾时返回。
  • goto service_checks 转移控制权,但不会返回调用链。
  • return 恢复调用链;从基础链返回时,则应用基础链策略。

连接跟踪 #

nf_conntrack 子系统将数据包关联到流。iptables 和 nftables 都使用它实现有状态过滤和 NAT。常见状态如下:

StateMeaning
new有效数据包启动一个尚未完成双向流量交换的流,或属于这样的流。
establishedConntrack 已观察到两个方向上的有效流量。
related新流与现有的被跟踪连接相关联。
invalid数据包无法关联到有效的被跟踪流。
untracked早期策略明确将数据包排除在跟踪之外。

有状态规则可以在不开放所有临时目标端口的情况下允许响应流量:

nft
ct state established,related accept
ct state invalid drop

Conntrack 会消耗有限的内核内存。当其表达到 nf_conntrack_max 时,内核无法跟踪更多流,并可能丢弃新的流量。应同时监控这两个值:

sh
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

不计算内存使用量就提高最大值,只会转移资源限制。高流量系统还需要检查超时、验证流量并监控容量。

网络地址转换 #

有状态 NAT 会根据被跟踪流的第一个数据包创建转换绑定。后续数据包使用 conntrack 状态,而不是重新评估每一条 NAT 规则。因此,过滤应放在 filter 链中,而不是 NAT 链中。

路由器可以如下定义目标 NAT 和源 NAT:

nft
table inet nat {
    chain prerouting {
        type nat hook prerouting priority dstnat
        tcp dport 443 dnat ip to 192.0.2.20:8443
    }

    chain postrouting {
        type nat hook postrouting priority srcnat
        oifname "wan0" masquerade
    }
}

inet 族中的 NAT 要求 Linux 5.2 或更高版本。较旧的内核需要使用独立的 ip 和 ip6 NAT 表。

  • DNAT 通常在 prerouting 中运行,此时转发路由尚未选定。
  • SNAT 和 masquerade 通常在 postrouting 中运行,此时路由已经选定。
  • redirect 将流量发送到本机。
  • masquerade 从输出接口派生源地址,适用于该地址可能发生变化的场景。

NAT 不是安全策略。经过转换的数据包仍然必须通过相关的 input 或 forward 过滤策略。

集合、映射和连接 #

iptables 规则集通常将重复值表示为线性规则,或依赖独立的 ipset 功能。nftables 集成了带类型的数据结构:

nft
set web_ports {
    type inet_service
    elements = { 80, 443 }
}

map service_targets {
    type inet_service : ipv4_addr
    elements = {
        80 : 192.0.2.10,
        443 : 192.0.2.20
    }
}
  • 集合判断一个键是否存在。
  • 映射返回键对应的值。
  • 判定映射返回 accept、drop 或 jump chain 等判定。
  • 连接将源地址和目标端口等字段组合成一个复合键。

命名集合可以在不重建每条引用规则的情况下更新。区间集合能够紧凑地表示地址和端口范围。映射和连接减少了几乎相同规则的长串,因为查找可以直接选择数据或控制流。

计数器、日志和跟踪 #

规则只有在包含计数器语句或引用命名计数器时才会统计流量:

nft
tcp dport 22 counter accept

日志是非终止语句,因此一条规则可以同时记录日志并作出判定:

nft
ct state invalid log prefix "invalid: " limit rate 10/second drop

由于数据包触发的日志记录可能消耗 CPU、存储和日志服务容量,因此必须进行速率限制。

nftables 跟踪会记录选定数据包的规则遍历过程。通过范围受限的规则启用跟踪:

nft
meta l4proto tcp tcp dport 443 meta nftrace set 1

然后观察事件:

sh
nft monitor trace

诊断完成后删除跟踪规则,因为广泛跟踪可能生成大量事件流。

从 iptables 迁移 #

迁移有两个不同目标:

  1. 兼容性迁移在保留 iptables 语法的同时改变内核后端。iptables-nft-restore 将 iptables-save 文件加载到 nf_tables。
  2. 原生迁移先进行转换,然后使用 nftables 语法重新设计策略。

在更改任一后端前,盘点所有活动规则的所有者:

sh
iptables --version
iptables-legacy-save
ip6tables-legacy-save
nft list ruleset

转换工具不会修改实时规则集:

sh
iptables-legacy-save > iptables-v4.save
ip6tables-legacy-save > iptables-v6.save

iptables-restore-translate -f iptables-v4.save > rules-v4.nft
ip6tables-restore-translate -f iptables-v6.save > rules-v6.nft

转换只是起点,因为它会保留旧结构。检查输出时应关注:

  • 不受支持或仅部分受支持的 xtables 扩展;
  • 可以合并为一条 inet 规则的重复 IPv4 和 IPv6 规则;
  • 应转换为集合、映射或判定映射的重复规则;
  • 链策略、计数器、日志速率和 NAT 行为;
  • 对标记、路由策略、接口、命名空间或连接跟踪辅助程序的依赖;
  • 容器引擎、虚拟机管理器、入侵防御工具或防火墙前端动态插入的规则。

iptables-nft 适合作为过渡接口,因为其修改是原子的,并且可以通过 nft list ruleset 查看。它仍然表示 xtables 语义,一些扩展可能继续通过 nft_compat 进行封装,而不会变为原生 nftables 表达式。

共存与规则所有权 #

不要将 iptables-legacy 和 nftables 作为同一流量的独立管理者运行。传统的 x_tables 规则和 nf_tables 规则是独立的内核规则集,任何一个命令视图都无法提供完整策略。数据包可能经过两者,从而产生看似规则缺失或相互矛盾的故障。

iptables-nft 和 nft 共享 nf_tables 后端,但共享存储不代表共享所有权。使用原生命令编辑由兼容工具生成的链,可能违反创建该链的工具所依赖的假设。

明确分配所有权:

  • 使用一个服务加载主机防火墙。
  • 在编排系统支持该模型时,为其分配独立的表。
  • 确定 firewalld、容器引擎、Kubernetes 组件、虚拟机管理器、VPN 软件和封禁工具是否会添加规则。
  • 不要刷新其他组件拥有的表。
  • 通过发行版的 nftables 服务或选定的防火墙管理器持久化规则,而不是使用相互竞争的启动脚本。

后端支持取决于软件版本和发行版打包方式。应检查已部署的系统,而不是假设工具名称表示传统或原生 nftables 行为。

项目状态 #

自 Linux 上游内核 3.13 版本起,nftables 已可用。Netfilter 项目自 2018 年 6 月起将原始 xtables/setsockopt 工具视为传统工具,同时继续在 nftables 后端之上提供 iptables 兼容工具。

因此,当前的 iptables 软件包承担两个角色:维护现有的传统部署,并提供通往 nf_tables 的兼容路径。内核、nftables 和 iptables 的版本仍然依赖发行版;应验证已部署的后端和所需功能版本,而不要依赖发行版发布名称。

运维检查清单 #

  1. 修改远程主机的 input 策略前,确保有可用的带外控制台。
  2. 应用文件前,使用 nft --check --file 进行验证。
  3. 使用 nft --file 加载完整变更,而不是按顺序执行 shell 命令。
  4. 强制执行默认拒绝策略前,建立环回和管理访问。
  5. 检查每个钩子上的所有基础链及其优先级。
  6. 监控计数器、日志、conntrack 容量和规则集变化。
  7. 将 NAT、数据包过滤、路由和流量控制视为彼此独立的策略,因为其中任何一个都不能替代其他策略。
  8. 为每个表保留唯一的权威所有者,并记录动态管理的表。

对于新的 Linux 防火墙设计,原生 nftables 提供了更清晰的模型、双栈 inet 规则、集成的集合和映射,以及事务性更新。iptables 兼容性仍然适用于现有应用,但应将其视为迁移机制,而不是新规则集的架构。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)