什么是 nftables?
nftables 是一个新式的数据包过滤框架,旨在替代现用的 iptables、ip6tables、arptables 和 ebtables 的新的包过滤框架。nftables 诞生于 2008 年,2013 年底合并到 Linux 内核,从 Linux 内核 3.13 版本开始大多数场景下 nftables 已经可以使用,但是完整的支持(即:nftables 优先级高于 iptables)应该是在 Linux 内核 3.15 版本。
nftables 旨在解决现有 {ip/ip6}tables 工具存在的诸多限制。相对于旧的 iptables,nftables 最引人注目的功能包括:改进性能、支持查询表、事务型规则更新、所有规则自动应用等等。
nftables 主要由三个组件组成:内核实现、libnl netlink 通信和 nftables 用户空间。其中内核提供了一个 netlink 配置接口以及运行时规则集评估,libnl 包含了与内核通信的基本函数,用户空间可以通过新引入的命令行工具 nft 和用户进行交互。
nft 可以通过在寄存器中储存和加载来交换数据。也就是说,它的语法与 iptables 不同。但 nft 可以利用内核提供的表达式去模拟旧的 iptables 命令,维持兼容性的同时获得更大的灵活性。简单来说,nft 是 iptables 及其衍生指令(ip6tables 和 arptables )的超集。
nftables 的特点
- 
nftables拥有一些高级的类似编程语言的能力,例如:定义变量和包含外部文件,即拥有使用额外脚本的能力。nftables也可以用于多种地址簇的过滤和处理。
- 
不同于 iptables,nftables并不包含任何的内置表,需要哪些表并在这些表中添加什么处理规则一切由管理员决定。
- 
表包含规则链,规则链包含规则。 
nftables 相较于 iptables 的优点
- 更新速度更快
在 iptables 中添加一条规则,会随着规则数量增多而变得非常慢。这种状况对 nftables 而言就不存在了,因为 nftables 使用原子的快速操作来更新规则集合。
- 内核更新更少。
使用 iptables 时,每一个匹配或投递都需要内核模块的支持。因此,如果你忘记一些东西或者要添加新的功能时都需要重新编译内核。而在 nftables 中就不存在这种情况了, 因为在 nftables 中,大部分工作是在用户态完成的,内核只知道一些基本指令(过滤是用伪状态机实现的)。例如,icmpv6 支持是通过 nft 工具的一个简单的补丁实现的,而在 iptables 中这种类型的更改需要内核和 iptables 都升级才可以。
nftables 基础操作
nftables 和 iptables 一样,由表(table)、链(chain)和规则(rule)组成,其中表包含链,链包含规则,规则是真正的动作。
在 nftables 中,表是链的容器。所以开始使用 nftables 时你首先需要做的是添加至少一个表。然后,你可以向你的表里添加链,然后往链里添加规则。
nftables 的表管理
与 iptables 中的表不同,nftables 中没有内置表。表的数量和名称由用户决定。但是,每个表只有一个地址簇,并且只适用于该簇的数据包。nftables 表可以指定为以下五个簇中的一个:
| nftables 簇 | 对应 iptables 的命令行工具 | 
|---|---|
| ip | iptables | 
| ip6 | ip6tables | 
| inet | iptables 和 ip6tables | 
| arp | arptables | 
| bridge | ebtables | 
ip(即 IPv4)是默认簇,如果未指定簇,则使用该簇。如果要创建同时适用于 IPv4 和 IPv6 的规则,请使用 inet 簇 。inet 允许统一 ip 和 ip6 簇,以便更容易地定义规则。
注意:
inet不能用于nat类型的链,只能用于filter类型的链。
下面我们来看看 nftables 是如何进行表管理操作的,以下为 nftables 创建表的基本命令语法。
| 1 | nft list tables [<family>] | 
这里我们以创建一个 inet 簇的表为例,演示如何创建和管理一个新的表。
- 创建表
| 1 | # 创建一个新的表 | 
- 列出表
| 1 | # 列出所有表 | 
- 删除表
| 1 | # 删除一个表 | 
注意:只能删除不包含链的表。
- 清空表
| 1 | # 清空一个表中的所有规则 | 
nftables 的链管理
链是用来保存规则的,与 iptables 中的链不同,nftables 没有内置链。这意味着和表一样,链也需要被显示创建。链有以下两种类型:
- 
常规链 : 主要用来做跳转,不需要指定钩子类型和优先级。从逻辑上对规则进行分类,支持所有的 nftables簇。
- 
基本链 : 来自网络栈数据包的入口点,需要指定钩子类型和优先级,支持 ip和ip6簇。
nftables 链支持钩子的类型
nftables 和 iptables 类似,依然使用 netfiler 中的 5 个 钩子。

不同的是 nftables 在 Linux Kernel 4.2 中新增了 ingress 钩子。

nftables 链支持钩子的作用
- 
prerouting:刚到达并未被nftables的其他部分所路由或处理的数据包。
- 
input:已经被接收并且已经经过prerouting钩子的传入数据包。
- 
forward:如果数据报将被发送到另一个设备,它将会通过forward钩子。
- 
output:从本地传出的数据包。
- 
postrouting:仅仅在离开系统之前,可以对数据包进行进一步处理。
nftables 链支持钩子的适用范围
- 
ip、ip6和inet簇支持的钩子有:prerouting、input、forward、output、postrouting。
- 
arp簇支持的钩子有:input、output。
nftables 链支持的优先级
优先级采用整数值表示,数字较小的链优先处理,并且可以是负数。可以使用的值有:
- 
NF_IP_PRI_CONNTRACK_DEFRAG (-400) 
- 
NF_IP_PRI_RAW (-300) 
- 
NF_IP_PRI_SELINUX_FIRST (-225) 
- 
NF_IP_PRI_CONNTRACK (-200) 
- 
NF_IP_PRI_MANGLE (-150) 
- 
NF_IP_PRI_NAT_DST (-100) 
- 
NF_IP_PRI_FILTER (0) 
- 
NF_IP_PRI_SECURITY (50) 
- 
NF_IP_PRI_NAT_SRC (100) 
- 
NF_IP_PRI_SELINUX_LAST (225) 
- 
NF_IP_PRI_CONNTRACK_HELPER (300) 
nftables 链对报文数据支持采取的动作
- 
accept 
- 
drop 
- 
queue 
- 
continue 
- 
return 
nftables 创建链的基本命令语法
| 1 | nft (add | create) chain [<family>] <table> <name> [ { type <type> hook <hook> [device <device>] priority <priority> \; [policy <policy> \;] } ] | 
nftables 创建链的基本操作
- 创建链
- 创建一个常规链
| 1 | # 将名为 tcpchain 的常规链添加到 inet 簇中名为 mytable 的表中 | 
- 创建一个基本链
添加一个基本链,你必需指定钩子和优先级。基本链的类型可以是 filter、route 或者 nat。
| 1 | # 添加一个筛选输入数据包的基本链 | 
注意:命令中的反斜线
(\)用来转义,这样Shell就不会将分号解释为命令的结尾。
- 列出规则
列出一个链中的所有规则。
| 1 | # 列出 inet 筛中 filter 表的 input 链中的所有的规则 | 
- 编辑链
要编辑一个链,只需按名称调用并重新定义要更改的规则即可。
| 1 | # 将默认表中的 input 链策略从 accept 更改为 drop | 
- 清空链中的规则
| 1 | # 清空指定链中的规则,这里为 input | 
- 删除链
| 1 | # 删除指定的链,这里为 input | 
注意:要删除的链中不能包含任何规则或者跳转目标。
nftables 的规则管理
nftables 规则由语句或表达式构成,包含在链中。以下为创建 nftables 规则的基本命令语法:
| 1 | nft add rule [<family>] <table> <chain> <matches> <statements> | 
其中 matches 是报文需要满足的条件。matches 的内容非常多,可以识别以下多种类型的报文。
| 1 | ip : ipv4 协议字段 | 
对每一种类型的报文,你又可以同时检查多个字段,例如:
| 1 | ip dscp cs1 | 
而 statement 是报文匹配规则时触发的操作,大致有以下几种:
| 1 | Verdict statements : 动作 | 
其中 Verdict Statements 是一组动作,大致有以下几种:
- 
accept:接受数据包并停止剩余规则评估。 
- 
drop:丢弃数据包并停止剩余规则评估。 
- 
queue:将数据包排队到用户空间并停止剩余规则评估。 
- 
continue:使用下一条规则继续进行规则评估。 
- 
return:从当前链返回并继续执行最后一条链的下一条规则。 
- 
jump :跳转到指定的规则链,当执行完成或者返回时,返回到调用的规则链。 
- 
goto :类似于跳转,发送到指定规则链但不返回。 
下面将以添加一条允许 SSH 登录的规则为例,给大家介绍下如何增加或插入一条新的规则。
- 增加规则
| 1 | $ nft add rule inet mytable input tcp dport ssh accept | 
默认情况下,add 表示将规则添加到链的末尾。如果你想从链的开头增加规则,可以使用 insert 来实现。
| 1 | $ nft insert rule inet mytable input tcp dport http accept | 
- 列出规则
- 列出目前链中所有的规则
| 1 | $ nft list ruleset | 
- 列出某个表中的所有规则
| 1 | $ nft list table inet mytable | 
- 列出某条链中的所有规则
| 1 | $ nft list chain inet mytable input | 
- 按指定位置增加规则
无论你是使用 add 或者 insert 来增加规则,你都可以通过 index 或者 handle 来指定添加的位置。
- 使用 index 来指定规则的索引
index 类似于 iptables 的 -I 选项, add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。
| 1 | # 在 input 链中已有规则中的第二条规则前插入一条新的规则 | 
注意:
index的值是从0开始的,index必须指向一个已存在的规则的索引。
- 使用 handle 来指定规则的句柄
通过 handle 的值来指定规则添加的位置,必须先知道现有规则的句柄位置。你可以通过参数 --handle 来获取当前规则的句柄位置。
| 1 | $ nft --handle list ruleset | 
获取到当前规则的句柄位置后,我们就可以在指定句柄位置添加规则。下面我们以在句柄位置 4 后面和句柄位置 5 前面分别增加一条新的规则为例:
| 1 | $ nft add rule inet mytable input handle 4 tcp dport 2345 accept | 
在 nftables 中,句柄值是固定不变的,除非规则被删除。而 index 的值是可变的,只要有新规则插入,就有可能发生变化。一般建议使用 handle 来插入新规则。
你也可以在创建规则时就获取到规则的句柄值,只需要在创建规则时同时加上参数 --echo 和 --handle。
| 1 | $ nft --echo --handle add rule inet mytable input udp dport 3333 accept | 
- 删除规则
单个规则只能通过句柄值删除,每个规则的句柄值可通过 nft --handle list ruleset 命令查看。
| 1 | # 删除指定句柄值对应的规则 | 
小技巧:你可以使用
iptables-translate实用程序将iptables规则转换成nftables格式
nftables 高级功能进阶
nftables 除了上面的基础功能外,还给我们额外提供了一些非常实用且功能强大的高级功能。
集合
nftables 的语法原生支持集合,可以用来匹配多个 IP 地址、端口号、网卡或其他任何条件。nftables 的集合可分为匿名集合与命名集合,相对 iptables 来说,nftables 是原生支持集合,并不需要借助 ipset 来实现。
- 匿名集合
匿名集合比较适合用于将来不需要更改的规则。
| 1 | # 允许来自源 IP 处于 10.10.10.123 ~ 10.10.10.231 这个区间内的主机的流量。 | 
匿名集合的缺点是需要修改集合规则时,就得替换原规则。如果需要频繁修改的集合,推荐使用命名集合。
- 命名集合
nftables 的命名集合是可以修改的。创建命名集合时需要指定其元素的类型,当前支持的数据类型有:
- 
ipv4_addr:IPv4地址
- 
ipv6_addr:IPv6地址
- 
ether_addr: 以太网(Ethernet)地址
- 
inet_proto: 网络协议
- 
inet_service: 网络服务
- 
mark: 标记类型
这里,我们来看一个实例。首先,创建一个空的命名集合。
| 1 | # 创建一个空的命名集合 | 
接着,我们向集合中添加一些元素。
| 1 | $ nft add element inet mytable myset { 10.10.10.22, 10.10.10.33 } | 
然后,在添加规则时引用集合,你可以使用 @ 符号跟上集合的名字来引用命名集合。
| 1 | # 将来源为集合 myset 中的 IP 地址的请求阻止掉 | 
- 支持区间
从上面的例子中,我们可以看到填加元素时是使用的两个独立的 IP 地址,并没有直接使用 10.10.10.0-10.10.10.255 这样的区间段来表示。这是因为直接使用区间段会报以下类似错误:
| 1 | $ nft add element inet mytable myset { 10.10.10.0-10.10.10.255 } | 
如果你想在集合中使用区间,需要加上一个 flag interval,因为内核必须提前确认该集合存储的数据类型,以便采用适当的数据结构。我们来看一个实例吧:
| 1 | # 创建一个支持区间的命名集合 | 
上面的例子中直接使用了子网掩码来表示 IP 地址段,它会被隐式转换为 IP 地址的区间,你也可以直接使用区间 10.10.10.0-10.10.10.255 来获得相同的效果。
- 级联不同类型
命名集合不仅支持同一类型元素,也可以支持对不同类型的元素进行级联。例如,下面的规则可以一次性匹配 IP 地址、协议和端口号。
首先,我们创建一个级联类型的集合。
| 1 | $ nft add set inet mytable my_concatset { type ipv4_addr . inet_proto . inet_service \; } | 
接着,向集合中添加元素。
| 1 | $ nft add element inet mytable my_concatset { 10.30.30.30 . tcp . telnet } | 
最后,我们在规则中对级联类型的集合进行引用。
| 1 | # 如果数据包的源 IP、协议类型、目标端口匹配 10.30.30.30、tcp、telnet 时,就会允许该数据包通过 | 
除了命名集合,匿名集合也是可以使用级联元素,例如:
| 1 | $ nft add rule inet mytable input ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept | 
在规则中引用级联类型的集合和一般类型集合的主要不同之处:主要在于需要标明集合中每个元素对应到规则中的哪个位置,这类似于 ipset 的聚合类型,例如 hash:ip,port 。
字典
字典是 nftables 的又一个高级特性,它同样可以支持在一条规则上面使用不同类型的数据。
首先,我们创建一个命名字典。
| 1 | $ nft add map inet mytable my_vmap { type inet_proto : verdict \; } | 
接着,我们向字典中添加一些元素。
| 1 | $ nft add element inet mytable my_vmap { 192.168.0.10 : drop, 192.168.0.11 : accept } | 
最后,我们就可以在规则中引用字典中的元素。
| 1 | $ nft add rule inet mytable input ip saddr vmap @my_vmap | 
和集合一样,除了命名字典,你也可以创建匿名字典。例如,为了从逻辑上对 TCP 和 UDP 的数据包拆分开来用两条不同链来处理,你就可以通过使用字典来实现。
| 1 | $ nft add chain inet mytable my_tcpchain | 
表与命名空间
在 nftables 中,每个表都是一个独立的命名空间,这就意味着不同的表中的链、集合、字典等名字可以相同。例如:
| 1 | $ nft add table inet table_one | 
有了这个特性后,不同的应用就可以在相互不影响的情况下管理自己的表中的规则。不过使用这个特性前,你需要注意的一点是:由于 nftables 将每个表都被视为独立的防火墙,一个数据包必须被所有表中的规则放行才能真正通过。如果,出现两条链的优先级相同,就会进入竞争状态。
当然,你可以使用 nftables 优先级特性来解决这个问题。优先级值越高的链优先级越低,所以优先级值低的链会比优先级值高的链先执行。
备份与恢复
默认情况下,通过 nftables 用户态工具 nft 直接在终端中加入的规则都是临时的。如果要想永久生效,我们可以将规则备份后并在开机自动加载时进行恢复。
- 备份规则
| 1 | $ nft list ruleset > /root/nftables.conf | 
- 恢复规则
| 1 | $ nft -f /root/nftables.conf | 
在 CentOS 8 中,nftables 是以 Systemd 服务形式进行工作的。nftables.service 的规则被存储在 /etc/nftables.conf 中,其中包含了一些其他的示例规则,一般会位于 /etc/sysconfig/nftables.conf 文件中。如果你想开机自加载 nftables 规则,只需将备份规则放到 /etc/sysconfig/nftables.conf 文件即可。
总结
至此,本文对 nftables 的基本功能和用法就讲解完了,更高级的用法可以在以下文档中做进一步探索。
Archlinux Wiki:https://wiki.archlinux.org/index.php/Nftables
nftables HOWTO:https://farkasity.gitbooks.io/nftables-howto-zh/
nftables Wiki:https://wiki.nftables.org/wiki-nftables/index.php/Main_Page
