Linux 数据包过滤由 Netfilter 实现。Netfilter 是一个在网络栈中公开钩子的内核框架。iptables 和 nftables 并不是相互独立的防火墙,而是在这些钩子处编程数据包分类和处理动作的用户空间接口。
iptables 是历史接口。使用 nft 命令配置的原生 nftables,用一种语言和一个 Netlink API,取代了彼此分离的 iptables、ip6tables、arptables 和 ebtables 规则管理模型。兼容工具仍然可以接受 iptables 语法,同时将规则写入 nftables 内核后端,因此仅凭命令名称无法确定当前使用的实现。
Netfilter 数据包流 #
数据包在网络栈中移动时,Netfilter 会调用已注册的操作:
路由决策决定数据包经过哪条路径:
- 发往本地套接字的数据包会经过
prerouting和input。 - 经由主机转发的数据包会经过
prerouting、forward和postrouting。 - 本地创建的数据包会经过
output和postrouting。 - 可选的、设备特定的
ingress钩子会在prerouting之前运行。
ingress 钩子可以在后续网络处理发生前拒绝流量,但此时 IP 分片尚未重新组装。因此,在该位置读取 TCP 或 UDP 标头的规则无法对非首个分片进行分类。
钩子名称描述数据包处理中的位置,而不是内置规则。nftables 默认不会创建链;规则集必须显式地将基础链附加到每个所需的钩子。
三种接口与两种内核 API #
当前系统可以提供三种命令接口,它们由两种不同的内核规则 API 支持:
iptables-legacy编程历史上的x_tables内核 API。iptables-nft接受 iptables 语法,但通过兼容层和nft_compat编程nf_tables。nft使用原生 nftables 语言和nf_tablesNetlink API。
nftables 后端会将规则编译为内核中网络专用虚拟机的指令。它复用现有的 Netfilter 钩子、连接跟踪、NAT、日志记录、排队以及其他支持基础设施。
应确定所选的 iptables 后端,而不是根据二进制名称推断:
iptables --version
readlink -f "$(command -v iptables)"包含 (nf_tables) 的版本字符串表示使用 nftables 后端。发行版 alternatives 或符号链接可能改变选择结果。
iptables 模型 #
iptables 将规则组织到预定义的表和内置链中。主要的 IPv4 表如下:
| Table | Primary Purpose |
|---|---|
filter | 接受或拒绝经过 INPUT、FORWARD 和 OUTPUT 的数据包。 |
nat | 为新连接创建地址或端口转换。 |
mangle | 修改数据包字段和标记,以进行特殊处理。 |
raw | 在连接跟踪之前运行,包括执行 NOTRACK 决策。 |
security | 应用 Linux 安全模块使用的安全标记。 |
链中的规则按顺序评估。匹配条件可以根据接口、地址、协议、端口、连接状态、标记或扩展特定属性选择数据包。随后,目标可以接受、丢弃、拒绝、记录、转换数据包,或将控制权转移到另一条链。
IPv4 和 IPv6 使用独立的命令和规则集:
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 ACCEPTiptables-legacy 的增量修改需要读取、修改并替换一个表。因此,并发写入者会通过 xtables 锁进行协调。与包含许多独立 iptables -A 命令的 shell 脚本相比,iptables-restore 更为合适,因为它替换整个表,而不是暴露一系列部分策略。
nftables 模型 #
nftables 使用由族、表、链、规则和有状态对象组成的层次结构:
- 族选择数据包域:
ip、ip6、inet、arp、bridge或netdev。 - 表是链、集合、映射、计数器和流表的命名空间。
- 基础链附加到 Netfilter 钩子,并声明链类型、优先级以及可选的默认策略。
- 普通链没有钩子,通过
jump或goto到达。 - 规则从左到右评估表达式,并在表达式匹配时执行语句。
inet 族允许一个规则集同时处理 IPv4 和 IPv6 流量。协议特定表达式仍然只选择一个协议族;例如,ip saddr 匹配 IPv4 地址,而 ip6 saddr 匹配 IPv6 地址。
双栈主机防火墙 #
以下文件定义了一个有状态、默认拒绝的输入策略,同时允许环回流量、响应流量、诊断 ICMP,以及来自 IPv4 管理网络的 SSH:
#!/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
}
}将文件作为单个事务进行验证和加载:
nft --check --file /etc/nftables.conf
nft --file /etc/nftables.conf
nft list rulesetnft --file 会解析完整批次,并将其作为一个内核事务提交。如果任何命令失败,该事务都不会被部分提交。在同一个文件中放置 flush ruleset 可以原子地替换受管理的规则集。重复加载追加型文件时省略 flush,可能会产生重复规则。
当其他服务拥有不相关的 nftables 表时,不要使用 flush ruleset。此时只替换分配给当前组件的表。
钩子、优先级和判定 #
基础链声明控制其运行时机:
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。常见状态如下:
| State | Meaning |
|---|---|
new | 有效数据包启动一个尚未完成双向流量交换的流,或属于这样的流。 |
established | Conntrack 已观察到两个方向上的有效流量。 |
related | 新流与现有的被跟踪连接相关联。 |
invalid | 数据包无法关联到有效的被跟踪流。 |
untracked | 早期策略明确将数据包排除在跟踪之外。 |
有状态规则可以在不开放所有临时目标端口的情况下允许响应流量:
ct state established,related accept
ct state invalid dropConntrack 会消耗有限的内核内存。当其表达到 nf_conntrack_max 时,内核无法跟踪更多流,并可能丢弃新的流量。应同时监控这两个值:
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max不计算内存使用量就提高最大值,只会转移资源限制。高流量系统还需要检查超时、验证流量并监控容量。
网络地址转换 #
有状态 NAT 会根据被跟踪流的第一个数据包创建转换绑定。后续数据包使用 conntrack 状态,而不是重新评估每一条 NAT 规则。因此,过滤应放在 filter 链中,而不是 NAT 链中。
路由器可以如下定义目标 NAT 和源 NAT:
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 集成了带类型的数据结构:
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等判定。 - 连接将源地址和目标端口等字段组合成一个复合键。
命名集合可以在不重建每条引用规则的情况下更新。区间集合能够紧凑地表示地址和端口范围。映射和连接减少了几乎相同规则的长串,因为查找可以直接选择数据或控制流。
计数器、日志和跟踪 #
规则只有在包含计数器语句或引用命名计数器时才会统计流量:
tcp dport 22 counter accept日志是非终止语句,因此一条规则可以同时记录日志并作出判定:
ct state invalid log prefix "invalid: " limit rate 10/second drop由于数据包触发的日志记录可能消耗 CPU、存储和日志服务容量,因此必须进行速率限制。
nftables 跟踪会记录选定数据包的规则遍历过程。通过范围受限的规则启用跟踪:
meta l4proto tcp tcp dport 443 meta nftrace set 1然后观察事件:
nft monitor trace诊断完成后删除跟踪规则,因为广泛跟踪可能生成大量事件流。
从 iptables 迁移 #
迁移有两个不同目标:
- 兼容性迁移在保留 iptables 语法的同时改变内核后端。
iptables-nft-restore将 iptables-save 文件加载到nf_tables。 - 原生迁移先进行转换,然后使用 nftables 语法重新设计策略。
在更改任一后端前,盘点所有活动规则的所有者:
iptables --version
iptables-legacy-save
ip6tables-legacy-save
nft list ruleset转换工具不会修改实时规则集:
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 的版本仍然依赖发行版;应验证已部署的后端和所需功能版本,而不要依赖发行版发布名称。
运维检查清单 #
- 修改远程主机的 input 策略前,确保有可用的带外控制台。
- 应用文件前,使用
nft --check --file进行验证。 - 使用
nft --file加载完整变更,而不是按顺序执行 shell 命令。 - 强制执行默认拒绝策略前,建立环回和管理访问。
- 检查每个钩子上的所有基础链及其优先级。
- 监控计数器、日志、conntrack 容量和规则集变化。
- 将 NAT、数据包过滤、路由和流量控制视为彼此独立的策略,因为其中任何一个都不能替代其他策略。
- 为每个表保留唯一的权威所有者,并记录动态管理的表。
对于新的 Linux 防火墙设计,原生 nftables 提供了更清晰的模型、双栈 inet 规则、集成的集合和映射,以及事务性更新。iptables 兼容性仍然适用于现有应用,但应将其视为迁移机制,而不是新规则集的架构。
参考资料 #
- Netfilter: nftables Project
- nftables Wiki: Netfilter Hooks
- nftables Wiki: Configuring Tables
- nftables Wiki: Configuring Chains
- nftables Wiki: nftables Families
- nftables Wiki: Connection Tracking
- nftables Wiki: Network Address Translation
- nftables Wiki: Sets
- nftables Wiki: Maps
- nftables Wiki: Verdict Maps
- nftables Wiki: Concatenations
- nftables Wiki: Atomic Rule Replacement
- nftables Wiki: Ruleset Tracing
- nftables Wiki: Moving from iptables to nftables
xtables-nft(8)xtables-translate(8)
评论
在 GitHub 上阅读或评论(发表评论需要登录)