nftables

nftables使用

在 nftables 中,所有地址族都遵循一个规则。与 iptables 不同,nftables 在用户空间中运行,iptables 中的每个模块都运行在内核(空间)中。它很少需要更新内核,并带有一些新功能,例如映射、地址族和字典。

地址族

地址族确定要处理的数据包的类型。在 nftables 中有六个地址族,它们是

1
ip  ipv6  inet  arp  bridge  netdev

在 nftables 中,ipv4 和 ipv6 协议可以被合并为一个称为 inet 的单一地址族。因此,我们不需要指定两个规则:一个用于 ipv4,另一个用于 ipv6。如果未指定地址族,它将默认为 ip 协议,即 ipv4。我们感兴趣的领域是 inet 地址族,因为大多数家庭用户将使用 ipv4 或 ipv6 协议。

nftables

典型的 nftables 规则包含三个部分:表、链和规则。

表是链和规则的容器。它们由其地址族和名称来标识。链包含 inet/arp/bridge/netdev 等协议所需的规则,并具有三种类型:过滤器、NAT 和路由。nftables 规则可以从脚本加载,也可以在终端键入,然后另存为规则集。

对于家庭用户,默认链为过滤器。inet 系列包含以下钩子:

1
input output forward pre-routing post-routing

nftables 使用一个名为 nft 的程序来添加、创建、列出、删除和加载规则。

nft 需要以 root 身份运行或使用 sudo 运行。使用以下命令分别列出、刷新、删除规则集和加载脚本。

命令的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
列出所有规则集 
nft list ruleset
清空规则
nft flush ruleset
删除规则集
nft delete table inet filter
加载脚本
nft -f /etc/nftables/default.nft

把当前规则加载到配置文件
nft list ruleset > /etc/nftables/defalut.nft

重启
systemctl restart nftables

输入策略

就像 iptables 一样,防火墙将包含三部分:输入(input)、转发(forward)和输出(output)。在终端中,为输入(input)策略键入以下命令。在开始之前,请确保已刷新规则集。我们的默认策略将会删除所有内容。我们将在防火墙中使用 inet 地址族。将以下规则以 root 身份添加或使用 sudo 运行:

1
2
3
4
5
6
增加一张表
nft add table inet filter
增加规则
nft add chain inet filter input { type filter hook input priority 0 \; counter \; policy drop \; }

你会注意到有一个名为 priority 0 的东西。这意味着赋予该规则更高的优先级。挂钩通常赋予负整数,这意味着更高的优先级。每个挂钩都有自己的优先级,过滤器链的优先级为 0。你可以检查 nftables Wiki 页面以查看每个挂钩的优先级。

表(Tables)

表包含#链[断开的链接:无效的部分]。与iptables中的表不同,nftables中没有内置表。表的数量和名称由用户决定。但是,每个表只有一个地址簇,并且只适用于该簇的数据包。表可以指定五个簇中的一个:

nftables簇 iptables实用程序
ip iptables
ip6 ip6tables
inet iptables和ip6tables
arp arptables
bridge ebtables

ip(即IPv4)是默认簇,如果未指定簇,则使用该簇。

要创建同时适用于IPv4和IPv6的规则,请使用inetinet允许统一ipip6簇,以便更容易地定义规则。

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

有关地址簇的完整描述,请参见nft(8)中的ADDRESS FAMILIES章节。

在以下情况中,family是可选的,如果未指定则设为ip

创建表

创建一个新的表:

1
2
3
# nft add table family table
我设置为inet类型的表 表名为 filter
nft add table inet filter

列出表

列出所有表:

1
# nft list tables

列出表中的链和规则

列出指定表中的所有链和规则:

1
# nft list table inet filter

例如,要列出inet簇中filter表中的所有规则:

1
# nft list table inet filter

删除表

删除一个表:

1
# nft delete table family table

只能删除不包含链的表。

清空表

要清空一个表中的所有规则:

1
# nft flush table family table

链(Chains)

链的目的是保存#规则[断开的链接:无效的部分]。与iptables中的链不同,nftables没有内置链。这意味着与iptables不同,如果链没有使用netfilter框架中的任何类型或钩子,则流经这些链的数据包不会被nftables触及。

链有两种类型。基本链是来自网络栈的数据包的入口点,其中指定了钩子值。常规链可以作为更好地处理的跳转目标。

在以下情况中,*family*是可选的,如果未指定则设为ip

创建链

常规链

将名为*chain*的常规链添加到名为*filter*的表中:

1
# nft add chain family filter chain

例如,将名为input的常规链添加到inet簇中名为filter的表中:

1
2
3
# nft add chain inet filter input

nft delete chain inet filter input 删除

基本链

添加基本链,需要指定钩子和优先级值:

1
# nft add chain family table chain { type type hook hook priority priority \; }

*type*可以是filterroute或者nat

IPv4/IPv6/Inet地址簇中,*hook*可以是preroutinginputforwardoutput或者postrouting。其他地址簇中的钩子列表请参见nft(8)

*priority*采用整数值。数字较小的链优先处理,并且可以是负数。[3]

例如,添加筛选输入数据包的基本链:

1
2
3
# nft add chain inet filter input { type filter hook input priority 0\; }
增加一个output钩子
nft add chain inet filter output { type filter hook output priority 0\; }

将上面命令中的add替换为create则可以添加一个新的链,但如果链已经存在,则返回错误。

列出规则

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

1
# nft list chain family table chain

例如,要列出inetfilter表的output链中的所有规则:

1
# nft list chain inet filter output

编辑链

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

1
# nft chain family table chain { [ type type hook hook device device priority priority \; policy <policy> \; ] }

例如,将默认表中的input链策略从accept更改为drop

1
# nft chain inet filter input { policy drop \; }

删除链

删除一个链:

1
# nft delete chain inet filter output

清空链中的规则

清空一个链的规则:

1
# nft flush chain inet  filter output

规则(Rules)

规则由语句或表达式构成,包含在链中。

添加规则

提示: iptables-translate实用程序何以将iptables规则转换成nftables格式。

将一条规则添加到链中:

1
# nft add rule family table chain handle handle statement

规则添加到*handle*处,这是可选的。如果不指定,则规则添加到链的末尾。

将规则插入到指定位置:

1
# nft insert rule family table chain handle handle statement

如果未指定*handle*,则规则插入到链的开头。

例如:增加规则允许所有访问22端口

1
2
3
4
5
nft add rule inet filter input tcp dport 22 accept
允许来源ip 192.168.224.0网段的访问
nft add rule inet filter input ip saddr 192.168.224.0 tcp dport 22 accept
允许 80-99端口放行
nft add rule inet filter input tcp dport 80-99 accept

表达式

通常情况下,*statement*包含一些要匹配的表达式,然后是判断语句。结论语句包括acceptdropqueuecontinuereturnjump *chain*goto *chain*。也可能是其他陈述。有关信息信息,请参阅nft(8)

nftables中有多种可用的表达式,并且在大多数情况下,与iptables的对应项一致。最明显的区别是没有一般或隐式匹配。一般匹配是始终可用的匹配,如--protocol--source。隐式匹配是用于特定协议的匹配,如TCP数据包的--sport

以下是可用匹配的部分列表:

  • meta (元属性,如接口)
  • icmp (ICMP协议)
  • icmpv6 (ICMPv6协议)
  • ip (IP协议)
  • ip6 (IPv6协议)
  • tcp (TCP协议)
  • udp (UDP协议)
  • sctp (SCTP协议)
  • ct (链接跟踪)

以下是匹配参数的部分列表(完整列表请参见nft(8)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
meta:
oif <output interface INDEX>
iif <input interface INDEX>
oifname <output interface NAME>
iifname <input interface NAME>

(oif 和 iif 接受字符串参数并转换为接口索引)
(oifname 和 iifname 更具动态性,但因字符串匹配速度更慢)

icmp:
type <icmp type>

icmpv6:
type <icmpv6 type>

ip:
protocol <protocol>
daddr <destination address>
saddr <source address>

ip6:
daddr <destination address>
saddr <source address>

tcp:
dport <destination port>
sport <source port>

udp:
dport <destination port>
sport <source port>

sctp:
dport <destination port>
sport <source port>

ct:
state <new | established | related | invalid>

注意: nft不使用/etc/services文件匹配端口号和名称,而是使用内置列表[失效链接 2020-08-04 ⓘ]。要在命令行显示端口映射,请使用 nft describe tcp dport

提示和技巧

简单可用的防火墙

详细信息参见 Simple stateful firewall

单一计算机

清空当前规则集:

1
# nft flush ruleset

添加一个表:

1
# nft add table inet filter

添加input、forward和output三个基本链。input和forward的默认策略是drop。output的默认策略是accept。

1
2
3
# nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
# nft add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; }
# nft add chain inet filter output { type filter hook output priority 0 \; policy accept \; }

添加两个与TCP和UDP关联的常规链:

1
2
# nft add chain inet filter TCP
# nft add chain inet filter UDP

related和established的流量会accept:

1
# nft add rule inet filter input ct state related,established accept

loopback接口的流量会accept:

1
# nft add rule inet filter input iif lo accept

无效的流量会drop:

1
# nft add rule inet filter input ct state invalid drop

新的echo请求(ping)会accept:

1
# nft add rule inet filter input ip protocol icmp icmp type echo-request ct state new accept

新的UDP流量跳转到UDP链:

1
# nft add rule inet filter input ip protocol udp ct state new jump UDP

新的TCP流量跳转到TCP链:

1
# nft add rule inet filter input ip protocol tcp tcp flags \& \(fin\|syn\|rst\|ack\) == syn ct state new jump TCP

未由其他规则处理的所有通信会reject:

1
2
3
# nft add rule inet filter input ip protocol udp reject
# nft add rule inet filter input ip protocol tcp reject with tcp reset
# nft add rule inet filter input counter reject with icmp type prot-unreachable

此时,应决定对传入连接打开哪些端口,这些由TCP和UDP链处理。例如,要打开web服务器的连接端口,添加:

1
# nft add rule inet filter TCP tcp dport 80 accept

要打开web服务器HTTPS连接端口443:

1
# nft add rule inet filter TCP tcp dport 443 accept

允许SSH连接端口22:

1
# nft add rule inet filter TCP tcp dport 22 accept

允许传入DNS请求:

1
2
# nft add rule inet filter TCP tcp dport 53 accept
# nft add rule inet filter UDP udp dport 53 accept

确保更改是永久的。

nftables详细语法

创建表

nftables 的每个表只有一个地址簇,并且只适用于该簇的数据包。表可以指定五个簇中的一个:

\nftables\*簇*** \iptables\*命令行工具***
ip iptables
ip6 ip6tables
inet iptables和ip6tables
arp arptables
bridge ebtables

inet 同时适用于 IPv4 和 IPv6 的数据包,即统一了 ip 和 ip6 簇,可以更容易地定义规则,下文的示例都将采用 inet 簇。

先创建一个新的表:

1
nft add table inet nftables_svc

列出所有规则:

1
2
3
nft list ruleset
table inet nftables_svc {
}

现在表中还没有任何规则,需要创建一个链来保存规则。

创建链

链是用来保存规则的,和表一样,链也需要被显示创建,因为 nftables 没有内置的链。链有以下两种类型:

  • 常规链 : 不需要指定钩子类型和优先级,可以用来做跳转,从逻辑上对规则进行分类。
  • 基本链 : 数据包的入口点,需要指定钩子类型和优先级。

创建常规链:

1
nft add chain inet nftables_svc my_utility_chain

创建基本链:

1
nft add chain inet nftables_svc INPUT { type filter hook input priority 0 \;}
  • 反斜线(\)用来转义,这样 shell 就不会将分号解释为命令的结尾。
  • priority 采用整数值,可以是负数,值较小的链优先处理。

列出链中的所有规则:

1
2
3
4
5
6
7
8
9
10
11
12
nft list chain inet nftables_svc my_utility_chain
table inet nftables_svc {
chain my_utility_chain {
}
}

nft list chain inet nftables_svc INPUT
table inet nftables_svc {
chain INPUT {
type filter hook input priority filter; policy accept;
}
}

创建规则

有了表和链之后,就可以创建规则了,规则由语句或表达式构成,包含在链中。下面添加一条规则允许 SSH 登录:

1
nft add rule inet nftables_svc INPUT tcp dport ssh accept

add 表示将规则添加到链的末尾,如果想将规则添加到链的开头,可以使用 insert。

1
nft insert rule inet nftables_svc INPUT tcp dport http accept

也可以将规则插入到链的指定位置,有两种方法:

1、 使用 index 来指定规则的索引。add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。index 的值从 0 开始增加。

1
2
nft insert rule inet nftables_svc INPUT index 1 tcp dport nfs accept
nft add rule inet nftables_svc INPUT index 1 tcp dport 888 accept

index 类似于 iptables 的 -I 选项,但有两点需要注意:一是 index 的值是从 0 开始的;二是 index 必须指向一个存在的规则,比如 nft insert rule … index 0 就是非法的。

2、 使用 handle 来指定规则的句柄。add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。handle 的值可以通过参数 –handle 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nft --handle list ruleset

table inet nftables_svc { # handle 41
chain my_utility_chain { # handle 1
}

chain INPUT { # handle 2
type filter hook input priority filter; policy accept;
tcp dport 80 accept # handle 4
tcp dport 2049 accept # handle 5
tcp dport 888 accept # handle 6
tcp dport 22 accept # handle 3
}
}

使用add

1
2
nft add rule inet nftables_svc INPUT handle 4 tcp dport 666 accept
nft insert rule inet nftables_svc INPUT handle 5 tcp dport 777 accept

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

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

1
2
3
nft --echo --handle add  rule inet nftables_svc INPUT  udp dport 333 accept

add rule inet nftables_svc INPUT udp dport 333 accept # handle 9

删除规则

单个规则只能通过其句柄删除,首先需要找到你想删除的规则句柄:

1
2
3
nft --handle list ruleset
删除句柄9的规则
nft delete rule inet nftables_svc INPUT handle 9

列出规则

前面的示例都是列出了所有规则,我们还可以根据自己的需求列出规则的一部分。例如:

列出某个表中的所有规则:

1
nft list table inet nftables_svc

列出某条链中的所有规则:

1
nft list chain inet nftables_svc INPUT

集合

nftables 的语法原生支持集合,可以用来匹配多个 IP 地址、端口号、网卡或其他任何条件。

匿名集合

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

例如,下面的规则允许来自源 IP 处于 192.168.224.1 ~ 192.168.224.231 这个区间内的主机的流量。

1
nft add rule inet nftables_svc INPUT ip saddr { 192.168.224.1, 192.168.224.231 } accept

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

之前的示例中添加的规则也可以通过集合来简化

1
nft add rule inet nftables_svc INPUT tcp dport  { http,nfs,ssh } accept

iptables 可以借助 ipset 来使用集合,而 nftables 原生支持集合,所以不需要借助 ipset。

命名集合

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

  • ipv4_addr : IPv4 地址
  • ipv6_addr : IPv6 地址
  • ether_addr : 以太网(Ethernet)地址
  • inet_proto : 网络协议
  • inet_service : 网络服务
  • mark : 标记类型

先创建一个空的命名集合:名字为allowed_ssh_ipv4_ips 类型为 ipv4_addr

1
2
3
4
5
6
7
8
9
10
nft add set inet nftables_svc allowed_ssh_ipv4_ips {
type ipv4_addr \;}

nft list sets

table inet nftables_svc {
set allowed_ssh_ipv4_ips {
type ipv4_addr
}
}

要想在添加规则时引用集合,可以使用 @ 符号跟上集合的名字。下面的规则表示将集合allowed_ssh_ipv4_ips 中的 IP 地址添加到白名单中。

1
nft insert rule inet nftables_svc INPUT ip saddr @allowed_ssh_ipv4_ips accept

向集合中添加元素:

1
nft add element inet nftables_svc allowed_ssh_ipv4_ips { 162.247.4.225, 34.96.147.71, 47.244.41.255, 154.194.255.73, 154.194.255.73, 47.244.62.17, 192.168.224.0,}

如果你向集合中添加一个区间就会报错:

1
2
3
4
5
nft add element inet nftables_svc allowed_ssh_ipv4_ips { 192.168.224.1-192.168.224.231}

Error: Set member cannot be range, missing interval flag on declaration
add element inet nftables_svc allowed_ssh_ipv4_ips { 192.168.224.1-192.168.224.231}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

支持区间

创建一个支持区间的命名集合:

1
2
3
4
nft add set inet nftables_svc my_range_set { type ipv4_addr \;  flags interval \; }
添加元素
nft add element inet nftables_svc my_range_set { 192.168.224.1-192.168.224.231}
nft add element inet nftables_svc my_range_set { 192.168.223.0/24}

子网掩码表示法会被隐式转换为 IP 地址的区间,你也可以直接使用区间 10.20.20.0-10.20.20.255 来获得相同的效果。

创建一个集合放行端口

1
nft add set inet nftables_svc allowed_ports { type inet_service \; flags interval \; }

添加规则引用集合

1
nft add rule inet nftables_svc INPUT tcp dport @allowed_ports accept

向集合里添加端口

1
nft add element inet nftables_svc allowed_ports { telnet, http, https, 25, 88, 90-95, 666, 888, 873, 2018-2022, 2097-2098, 3656, 999, 8000, 8080, 8989, 9000 }

级联不同类型

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

1
2
3
nft add set inet nftables_svc my_concat_set { type ipv4_addr . inet_proto . inet_service \; }

nft list set inet nftables_svc my_concat_set

向集合中添加元素:

1
nft add element inet nftables_svc my_concat_set { 192.168.224.12  . tcp . telnet }

在规则中引用级联类型的集合和之前一样,但需要标明集合中每个元素对应到规则中的哪个位置。

1
nft add rule inet nftables_svc INPUT ip saddr . meta l4proto . tcp dport @my_concat_set accept

这就表示如果数据包的源 IP、协议类型、目标端口匹配 192.168.224.12、tcp、telnet 时,nftables 就会允许该数据包通过。

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

1
nft add rule inet nftables_svc INPUT ip saddr . meta l4proto . udp dport { 192.168.224.12 . udp . bootps } accept

nftables 级联类型的集合类似于 ipset 的聚合类型,例如 hash:ip,port。

字典

字典是 nftables 的一个高级特性,它可以使用不同类型的数据并将匹配条件映射到某一个规则上面,并且由于是哈希映射的方式,可以完美的避免链式规则跳转的性能开销。

例如,为了从逻辑上将对 TCP 和 UDP 数据包的处理规则拆分开来,可以使用字典来实现,这样就可以通过一条规则实现上述需求。

1
2
3
4
5
6
nft add chain inet nftables_svc TCP
nft add chain inet nftables_svc UDP

nft add rule inet nftables_svc INPUT meta l4proto vmap { tcp : jump TCP, udp : jump UDP }

nft list chain inet nftables_svc INPUT

和集合一样,除了匿名字典之外,还可以创建命名字典:

1
nft add map inet nftables_svc my_vmap { type inet_proto : verdict \; }

向字典中添加元素:

1
nft add element inet nftables_svc my_vmap { 192.168.224.10 : drop, 192.168.224.11 : accept }

后面就可以在规则中引用字典中的元素了:

1
nft add rule inet nftables_svc INPUT ip saddr vmap @my_vmap

表与命名空间

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

1
2
3
4
5
6
7
nft add table inet table_one

nft add chain inet table_one INPUT

nft add table inet table_two

nft add chain inet table_two INPUT

有了这个特性,不同的应用就可以在相互不影响的情况下管理自己的表中的规则,而使用 iptables 就无法做到这一点。

当然,这个特性也有缺陷,由于每个表都被视为独立的防火墙,那么某个数据包必须被所有表中的规则放行,才算真正的放行,即使 table_one 允许该数据包通过,该数据包仍然有可能被 table_two 拒绝。为了解决这个问题,nftables 引入了优先级,priority 值越高的链优先级越低,所以 priority 值低的链比 priority 值高的链先执行。如果两条链的优先级相同,就会进入竞争状态。

备份与恢复

以上所有示例中的规则都是临时的,要想永久生效,我们可以将规则备份,重启后自动加载恢复,其实 nftables 的 systemd 服务就是这么工作的。

备份规则:

1
nft list ruleset > /root/nftables.conf

加载恢复:

1
nft -f /root/nftables.conf

在 CentOS 8 中,nftables.service 的规则被存储在 /etc/nftables.conf 中,其中 include 一些其他的示例规则,一般位于 /etc/sysconfig/nftables.conf 文件中,但默认会被注释掉。

工作环境nftables配置实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# drop any existing nftables ruleset
flush ruleset

# a common table for both IPv4 and IPv6
table inet nftables_svc {
# 允许ping
set allowed_protocols {
type inet_proto
elements = { icmp, icmpv6 }
}

# 放行本地回环及docker0网卡
set allowed_interfaces {
type ifname
elements = { "lo", "docker0" }
}

# ssh白名单
set allowed_ssh_ipv4_ips {
type ipv4_addr
elements = { 162.247.4.225, 34.96.147.71, 47.244.41.255,
114.199.68.31, 46.19.166.241, 49.213.26.92,
58.82.202.253, 58.82.246.106, 58.82.247.186,
154.194.255.73, 154.194.255.73, 47.244.62.176,
202.60.234.56, 123.59.194.60, 154.194.254.176,
192.168.224.0,
}
}

# 端口放行
set allowed_ports {
type inet_service
flags interval
elements = { telnet, http, https, 25, 88, 90-95, 666, 888,
873, 2018-2022, 2097-2098, 3656, 999, 8000, 8080,
8989, 9000
}
}

chain INPUT {
type filter hook input priority 0
policy drop

ct state { established, related } accept

meta l4proto @allowed_protocols accept
iifname @allowed_interfaces accept
ip saddr @allowed_ssh_ipv4_ips tcp dport 22 accept
tcp dport @allowed_ports accept

#zabbix
ip saddr 47.244.62.176 tcp dport 10050 accept
}

chain OUTPUT {
type filter hook output priority 0
policy drop

ct state { new, established, related, untracked } accept
}

chain FORWARD {
type filter hook forward priority 0
policy accept
}
}

后期新增ssh的ip白名单

1
nft add element inet nftables_svc allowed_ssh_ipv4_ips { 192.168.224.1}

后期新增端口访问

1
nft add element inet nftables_svc allowed_ports {3306-3308, 6397}

保存规则

1
nft list ruleset > /etc/nftables/default.nft

评论


:D 一言句子获取中...

加载中,最新评论有1分钟缓存...