nftables 简明教程

Posted by Mike on 2020-05-12

什么是 nftables?

nftables 是一个新式的数据包过滤框架,旨在替代现用的 iptablesip6tablesarptablesebtables 的新的包过滤框架。nftables 诞生于 2008 年,2013 年底合并到 Linux 内核,从 Linux 内核 3.13 版本开始大多数场景下 nftables 已经可以使用,但是完整的支持(即:nftables 优先级高于 iptables)应该是在 Linux 内核 3.15 版本。

nftables 旨在解决现有 {ip/ip6}tables 工具存在的诸多限制。相对于旧的 iptablesnftables 最引人注目的功能包括:改进性能、支持查询表、事务型规则更新、所有规则自动应用等等。

nftables 主要由三个组件组成:内核实现、libnl netlink 通信和 nftables 用户空间。其中内核提供了一个 netlink 配置接口以及运行时规则集评估,libnl 包含了与内核通信的基本函数,用户空间可以通过新引入的命令行工具 nft 和用户进行交互。

nft 可以通过在寄存器中储存和加载来交换数据。也就是说,它的语法与 iptables 不同。但 nft 可以利用内核提供的表达式去模拟旧的 iptables 命令,维持兼容性的同时获得更大的灵活性。简单来说,nftiptables 及其衍生指令(ip6tablesarptables )的超集。

nftables 的特点

  1. nftables 拥有一些高级的类似编程语言的能力,例如:定义变量和包含外部文件,即拥有使用额外脚本的能力。nftables 也可以用于多种地址簇的过滤和处理。

  2. 不同于 iptables, nftables 并不包含任何的内置表,需要哪些表并在这些表中添加什么处理规则一切由管理员决定。

  3. 表包含规则链,规则链包含规则。

nftables 相较于 iptables 的优点

  1. 更新速度更快

iptables 中添加一条规则,会随着规则数量增多而变得非常慢。这种状况对 nftables 而言就不存在了,因为 nftables 使用原子的快速操作来更新规则集合。

  1. 内核更新更少。

使用 iptables 时,每一个匹配或投递都需要内核模块的支持。因此,如果你忘记一些东西或者要添加新的功能时都需要重新编译内核。而在 nftables 中就不存在这种情况了, 因为在 nftables 中,大部分工作是在用户态完成的,内核只知道一些基本指令(过滤是用伪状态机实现的)。例如,icmpv6 支持是通过 nft 工具的一个简单的补丁实现的,而在 iptables 中这种类型的更改需要内核和 iptables 都升级才可以。

nftables 基础操作

nftablesiptables 一样,由表(table)、链(chain)和规则(rule)组成,其中表包含链,链包含规则,规则是真正的动作。

nftables 中,表是链的容器。所以开始使用 nftables 时你首先需要做的是添加至少一个表。然后,你可以向你的表里添加链,然后往链里添加规则。

nftables 的表管理

iptables 中的表不同,nftables 中没有内置表。表的数量和名称由用户决定。但是,每个表只有一个地址簇,并且只适用于该簇的数据包。nftables 表可以指定为以下五个簇中的一个:

nftables 簇 对应 iptables 的命令行工具
ip iptables
ip6 ip6tables
inet iptables 和 ip6tables
arp arptables
bridge ebtables

ip(即 IPv4)是默认簇,如果未指定簇,则使用该簇。如果要创建同时适用于 IPv4IPv6 的规则,请使用 inet 簇 。inet 允许统一 ipip6 簇,以便更容易地定义规则。

注意: inet 不能用于 nat 类型的链,只能用于 filter 类型的链。

下面我们来看看 nftables 是如何进行表管理操作的,以下为 nftables 创建表的基本命令语法。

1
2
3
nft list tables [<family>]
nft list table [<family>] <name> [-n] [-a]
nft (add | delete | flush) table [<family>] <name>

这里我们以创建一个 inet 簇的表为例,演示如何创建和管理一个新的表。

  1. 创建表
1
2
# 创建一个新的表
$ nft add table inet mytable
  1. 列出表
1
2
3
4
5
6
7
8
# 列出所有表
$ nft list tables

# 列出指定族的所有表
$ nft list tables inet

# 列出 inet 簇中 mytable 表中的所有规则
$ nft list table inet mytable
  1. 删除表
1
2
# 删除一个表
$ nft delete table inet mytable

注意:只能删除不包含链的表。

  1. 清空表
1
2
# 清空一个表中的所有规则
$ nft flush table inet mytable

nftables 的链管理

链是用来保存规则的,与 iptables 中的链不同,nftables 没有内置链。这意味着和表一样,链也需要被显示创建。链有以下两种类型:

  • 常规链 : 主要用来做跳转,不需要指定钩子类型和优先级。从逻辑上对规则进行分类,支持所有的 nftables 簇。

  • 基本链 : 来自网络栈数据包的入口点,需要指定钩子类型和优先级,支持 ipip6 簇。

nftables 链支持钩子的类型

nftablesiptables 类似,依然使用 netfiler 中的 5 个 钩子。

不同的是 nftablesLinux Kernel 4.2 中新增了 ingress 钩子。

nftables 链支持钩子的作用

  • prerouting:刚到达并未被 nftables 的其他部分所路由或处理的数据包。

  • input:已经被接收并且已经经过 prerouting 钩子的传入数据包。

  • forward:如果数据报将被发送到另一个设备,它将会通过 forward 钩子。

  • output:从本地传出的数据包。

  • postrouting:仅仅在离开系统之前,可以对数据包进行进一步处理。

nftables 链支持钩子的适用范围

  • ipip6inet 簇支持的钩子有: preroutinginputforwardoutputpostrouting

  • arp 簇支持的钩子有: inputoutput

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
2
3
nft (add | create) chain [<family>] <table> <name> [ { type <type> hook <hook> [device <device>] priority <priority> \; [policy <policy> \;] } ]
nft (delete | list | flush) chain [<family>] <table> <name>
nft rename chain [<family>] <table> <name> <newname>

nftables 创建链的基本操作

  1. 创建链
  • 创建一个常规链
1
2
# 将名为 tcpchain 的常规链添加到 inet 簇中名为 mytable 的表中
$ nft add chain inet mytable tcpchain
  • 创建一个基本链

添加一个基本链,你必需指定钩子和优先级。基本链的类型可以是 filterroute 或者 nat

1
2
# 添加一个筛选输入数据包的基本链
$ nft add chain inet mytable input { type filter hook input priority 0\; }

注意:命令中的反斜线 (\) 用来转义,这样 Shell 就不会将分号解释为命令的结尾。

  1. 列出规则

列出一个链中的所有规则。

1
2
# 列出 inet 筛中 filter 表的 input 链中的所有的规则
$ nft list chain inet filter input
  1. 编辑链

要编辑一个链,只需按名称调用并重新定义要更改的规则即可。

1
2
# 将默认表中的 input 链策略从 accept 更改为 drop
$ nft chain inet mytable input { policy drop \; }
  1. 清空链中的规则
1
2
# 清空指定链中的规则,这里为 input
$ nft flush chain inet mytable input
  1. 删除链
1
2
# 删除指定的链,这里为 input
$ nft delete chain inet mytable input

注意:要删除的链中不能包含任何规则或者跳转目标。

nftables 的规则管理

nftables 规则由语句或表达式构成,包含在链中。以下为创建 nftables 规则的基本命令语法:

1
2
3
4
nft add rule [<family>] <table> <chain> <matches> <statements>
nft insert rule [<family>] <table> <chain> [position <position>] <matches> <statements>
nft replace rule [<family>] <table> <chain> [handle <handle>] <matches> <statements>
nft delete rule [<family>] <table> <chain> [handle <handle>]

其中 matches 是报文需要满足的条件。matches 的内容非常多,可以识别以下多种类型的报文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ip          :  ipv4 协议字段
ip6 : ipv6 协议字段
tcp : tcp 协议字段
udp : udp 协议字段
udplite : udp-lite 协议
sctp : sctp 协议
dccp
ah
esp
comp
icmp
icmpv6
ether : 以太头
dst
frag :
hbh
mh
rt
vlan : vlan
arp : arp协议
ct : 连接状态
meta : 报文的基本信息

对每一种类型的报文,你又可以同时检查多个字段,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ip dscp cs1
ip dscp != cs1
ip dscp 0x38
ip dscp != 0x20
ip dscp {cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7, af11, af12, af13, af21,
af22, af23, af31, af32, af33, af41, af42, af43, ef}

ip length 232
ip length != 233
ip length 333-435
ip length != 333-453
ip length { 333, 553, 673, 838}

ip6 flowlabel 22
ip6 flowlabel != 233
ip6 flowlabel { 33, 55, 67, 88 }
ip6 flowlabel { 33-55 }

statement 是报文匹配规则时触发的操作,大致有以下几种:

1
2
3
4
5
6
7
Verdict statements :   动作
Log : 记录日志并继续处理请求
Reject : 停止处理并拒绝请求
Counter : 计数
Limit : 如果达到了接收数据包的匹配限制,则根据规则处理数据包
Nat : NAT
Queuea : 停止处理并发送数据包到用户空间程序

其中 Verdict Statements 是一组动作,大致有以下几种:

  • accept:接受数据包并停止剩余规则评估。

  • drop:丢弃数据包并停止剩余规则评估。

  • queue:将数据包排队到用户空间并停止剩余规则评估。

  • continue:使用下一条规则继续进行规则评估。

  • return:从当前链返回并继续执行最后一条链的下一条规则。

  • jump :跳转到指定的规则链,当执行完成或者返回时,返回到调用的规则链。

  • goto :类似于跳转,发送到指定规则链但不返回。

下面将以添加一条允许 SSH 登录的规则为例,给大家介绍下如何增加或插入一条新的规则。

  1. 增加规则
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. 列出规则
  • 列出目前链中所有的规则
1
2
3
4
5
6
7
8
$ nft list ruleset
table inet mytable {
chain input {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport ssh accept
}
}
  • 列出某个表中的所有规则
1
$ nft list table inet mytable
  • 列出某条链中的所有规则
1
$ nft list chain inet mytable input
  1. 按指定位置增加规则

无论你是使用 add 或者 insert 来增加规则,你都可以通过 index 或者 handle 来指定添加的位置。

  • 使用 index 来指定规则的索引

index 类似于 iptables-I 选项, add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 在 input 链中已有规则中的第二条规则前插入一条新的规则
$ nft insert rule inet mytable input index 1 tcp dport nfs accept
$ nft list ruleset
table inet mytable {
chain input {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
}
}

# 在 input 链中已有规则中的第一条规则后插入一条新的规则
$ nft add rule inet mytable input index 0 tcp dport 1234 accept
$ nft list ruleset
table inet mytable {
chain input {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport 1234 accept
tcp dport nfs accept
tcp dport ssh accept
}
}

注意:index 的值是从 0 开始的,index 必须指向一个已存在的规则的索引。

  • 使用 handle 来指定规则的句柄

通过 handle 的值来指定规则添加的位置,必须先知道现有规则的句柄位置。你可以通过参数 --handle 来获取当前规则的句柄位置。

1
2
3
4
5
6
7
8
9
10
$ nft --handle list ruleset
table inet mytable { # handle 10
chain input { # handle 2
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 4
tcp dport 1234 accept # handle 6
tcp dport nfs accept # handle 5
tcp dport ssh accept # handle 3
}
}

获取到当前规则的句柄位置后,我们就可以在指定句柄位置添加规则。下面我们以在句柄位置 4 后面和句柄位置 5 前面分别增加一条新的规则为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nft add rule inet mytable input handle 4 tcp dport 2345 accept
$ nft insert rule inet mytable input handle 5 tcp dport 3456 accept
$ nft --handle list ruleset
table inet mytable { # handle 10
chain input { # handle 2
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 4
tcp dport 2345 accept # handle 8
tcp dport 1234 accept # handle 6
tcp dport 3456 accept # handle 9
tcp dport nfs accept # handle 5
tcp dport ssh accept # handle 3
}
}

nftables 中,句柄值是固定不变的,除非规则被删除。而 index 的值是可变的,只要有新规则插入,就有可能发生变化。一般建议使用 handle 来插入新规则。

你也可以在创建规则时就获取到规则的句柄值,只需要在创建规则时同时加上参数 --echo--handle

1
2
$ nft --echo --handle add rule inet mytable input udp dport 3333 accept
add rule inet mytable input udp dport 3333 accept # handle 10
  1. 删除规则

单个规则只能通过句柄值删除,每个规则的句柄值可通过 nft --handle list ruleset 命令查看。

1
2
# 删除指定句柄值对应的规则
$ nft delete rule inet mytable input handle 8

小技巧:你可以使用 iptables-translate 实用程序将 iptables 规则转换成 nftables 格式

nftables 高级功能进阶

nftables 除了上面的基础功能外,还给我们额外提供了一些非常实用且功能强大的高级功能。

集合

nftables 的语法原生支持集合,可以用来匹配多个 IP 地址、端口号、网卡或其他任何条件。nftables 的集合可分为匿名集合与命名集合,相对 iptables 来说,nftables 是原生支持集合,并不需要借助 ipset 来实现。

  1. 匿名集合

匿名集合比较适合用于将来不需要更改的规则。

1
2
3
4
5
# 允许来自源 IP 处于 10.10.10.123 ~ 10.10.10.231 这个区间内的主机的流量。
$ nft add rule inet mytable input ip saddr { 10.10.10.123, 10.10.10.231 } accept

# 允许指定协义的流量通过
$ nft add rule inet mytable input tcp dport { http, nfs, ssh } accept

匿名集合的缺点是需要修改集合规则时,就得替换原规则。如果需要频繁修改的集合,推荐使用命名集合。

  1. 命名集合

nftables 的命名集合是可以修改的。创建命名集合时需要指定其元素的类型,当前支持的数据类型有:

  • ipv4_addr : IPv4 地址

  • ipv6_addr : IPv6 地址

  • ether_addr : 以太网(Ethernet)地址

  • inet_proto : 网络协议

  • inet_service : 网络服务

  • mark : 标记类型

这里,我们来看一个实例。首先,创建一个空的命名集合。

1
2
3
4
5
6
7
8
# 创建一个空的命名集合
$ nft add set inet mytable myset { type ipv4_addr \; }
$ nft list sets
table inet mytable {
set myset {
type ipv4_addr
}
}

接着,我们向集合中添加一些元素。

1
2
3
4
5
6
7
8
$ nft add element inet mytable myset { 10.10.10.22, 10.10.10.33 }
$ nft list set inet mytable myset
table inet mytable {
set myset {
type ipv4_addr
elements = { 10.10.10.22, 10.10.10.33 }
}
}

然后,在添加规则时引用集合,你可以使用 @ 符号跟上集合的名字来引用命名集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 将来源为集合 myset 中的 IP 地址的请求阻止掉
$ nft insert rule inet mytable input ip saddr @myset drop
$ nft list chain inet mytable input
table inet mytable {
chain input {
type filter hook input priority 0; policy accept;
ip saddr @my_set drop
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
ip saddr { 10.10.10.123, 10.10.10.231 } accept
}
}
  1. 支持区间

从上面的例子中,我们可以看到填加元素时是使用的两个独立的 IP 地址,并没有直接使用 10.10.10.0-10.10.10.255 这样的区间段来表示。这是因为直接使用区间段会报以下类似错误:

1
2
3
4
5
$ nft add element inet mytable myset { 10.10.10.0-10.10.10.255 }

Error: Set member cannot be range, missing interval flag on declaration
add element inet mytable my_set { 10.10.10.0-10.10.10.255 }
^^^^^^^^^^^^^^^^^^^^^^^

如果你想在集合中使用区间,需要加上一个 flag interval,因为内核必须提前确认该集合存储的数据类型,以便采用适当的数据结构。我们来看一个实例吧:

1
2
3
4
5
6
7
8
9
10
11
# 创建一个支持区间的命名集合
$ nft add set inet mytable my_rangeset { type ipv4_addr \; flags interval
$ nft add element inet mytable my_rangeset { 10.10.10.0/24 }
$ nft list set inet mytable my_rangeset
table inet mytable {
set my_rangeset {
type ipv4_addr
flags interval
elements = { 10.10.10.0/24 }
}
}

上面的例子中直接使用了子网掩码来表示 IP 地址段,它会被隐式转换为 IP 地址的区间,你也可以直接使用区间 10.10.10.0-10.10.10.255 来获得相同的效果。

  1. 级联不同类型

命名集合不仅支持同一类型元素,也可以支持对不同类型的元素进行级联。例如,下面的规则可以一次性匹配 IP 地址、协议和端口号。

首先,我们创建一个级联类型的集合。

1
2
3
4
5
6
7
8
9
$ nft add set inet mytable my_concatset  { type ipv4_addr . inet_proto . inet_service \; }

# 不同类型的元素可以通过级联操作符 . 来分隔。
$ nft list set inet mytable my_concatset
table inet mytable {
set my_concatset {
type ipv4_addr . inet_proto . inet_service
}
}

接着,向集合中添加元素。

1
$ nft add element inet mytable my_concatset { 10.30.30.30 . tcp . telnet }

最后,我们在规则中对级联类型的集合进行引用。

1
2
# 如果数据包的源 IP、协议类型、目标端口匹配 10.30.30.30、tcp、telnet 时,就会允许该数据包通过
$ nft add rule inet mytable input ip saddr . meta l4proto . tcp dport @my_concatset accept

除了命名集合,匿名集合也是可以使用级联元素,例如:

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

和集合一样,除了命名字典,你也可以创建匿名字典。例如,为了从逻辑上对 TCPUDP 的数据包拆分开来用两条不同链来处理,你就可以通过使用字典来实现。

1
2
3
4
5
6
7
8
9
10
11
$ nft add chain inet mytable my_tcpchain
$ nft add chain inet mytable my_udpchain
$ nft add rule inet mytable input meta l4proto vmap { tcp : jump my_tcpchain, udp : jump my_udpchain }
$ nft list chain inet mytable input
table inet mytable {
chain input {
...
meta nfproto ipv4 ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept
meta l4proto vmap { tcp : jump my_tcpchain, udp : jump my_udpchain }
}
}

表与命名空间

nftables 中,每个表都是一个独立的命名空间,这就意味着不同的表中的链、集合、字典等名字可以相同。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nft add table inet table_one
$ nft add chain inet table_one mychain
$ nft add table inet table_two
$ nft add chain inet table_two mychain
$ nft list ruleset
...
table inet table_one {
chain mychain {
}
}
table inet table_two {
chain mychain {
}
}

有了这个特性后,不同的应用就可以在相互不影响的情况下管理自己的表中的规则。不过使用这个特性前,你需要注意的一点是:由于 nftables 将每个表都被视为独立的防火墙,一个数据包必须被所有表中的规则放行才能真正通过。如果,出现两条链的优先级相同,就会进入竞争状态。

当然,你可以使用 nftables 优先级特性来解决这个问题。优先级值越高的链优先级越低,所以优先级值低的链会比优先级值高的链先执行。

备份与恢复

默认情况下,通过 nftables 用户态工具 nft 直接在终端中加入的规则都是临时的。如果要想永久生效,我们可以将规则备份后并在开机自动加载时进行恢复。

  1. 备份规则
1
$ nft list ruleset > /root/nftables.conf
  1. 恢复规则
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 的基本功能和用法就讲解完了,更高级的用法可以在以下文档中做进一步探索。

  1. Archlinux Wiki:https://wiki.archlinux.org/index.php/Nftables

  2. nftables HOWTO:https://farkasity.gitbooks.io/nftables-howto-zh/

  3. nftables Wiki:https://wiki.nftables.org/wiki-nftables/index.php/Main_Page

参考文档

  1. https://www.google.com

  2. https://url.cn/56tJkD8

  3. http://www.freecls.com/a/2712/fc

  4. https://blog.51cto.com/babyshen/2065749

  5. https://adoyle.me/Today-I-Learned/linux/iptables.html

  6. https://blog.csdn.net/dog250/article/details/54170683

  7. https://www.yangcs.net/posts/using-nftables/

  8. https://wiki.archlinux.org/index.php/Nftables

  9. https://farkasity.gitbooks.io/nftables-howto-zh/content/

  10. https://blog.omicron3069.com/post/nftablesfornode/