参考链接:
nftables官方教程:https://wiki.nftables.org
redhat nftables 教程:第 2 章 nftables 入门 Red Hat Enterprise Linux 9 | Red Hat Customer Portal
nftables 中文教程:nftables 简体中文 - nftables 中文教程 (gitbook.io)
米开朗基杨博客:
regit.org:
Gentoo Wiki nftables:Nftables/Examples - Gentoo Wiki — Nftables/示例- Gentoo Wiki
基本知识
使用 firewalld、nftables 或者 iptables 时
以下是您应该使用以下工具之一的概述:
firewalld :对简单的防火墙用例使用 firewalld 工具。此工具易于使用,并涵盖了这些场景的典型用例。
nftables :使用 nftables 工具来设置复杂和性能关键的防火墙,如用于整个网络。
iptables :Red Hat Enterprise Linux 上的 iptables 工具使用 nf_tables 内核 API 而不是 传统的 后端。nf_tables API 提供了向后兼容性,以便使用 iptables 命令的脚本仍可在 Red Hat Enterprise Linux 上工作。对于新的防火墙脚本,红帽建议使用 nftables。
基本路径
nft 脚本存放目录
配置文件
1
/etc/sysconfig/nftables.conf
要启用 nftables 服务来加载生成的文件,请在 /etc/sysconfig/nftables.conf 文件中添加以下内容:
1
2
include "/etc/nftables/ruleset-migrated-from-iptables.nft"
include "/etc/nftables/ruleset-migrated-from-ip6tables.nft"
systemctl disable –now iptables
1
2
如果您使用自定义脚本加载 ` iptables ` 规则,请确保脚本不再自动启动并重新引导以刷新所有表。
启用并启动 nftables 服务:
1
# systemctl enable --now nftables
验证 显示 nftables 规则集:
nft 命令不会预先创建表和链。只有当用户手动创建它们时它们才会存在。
例如:列出 firewalld 生成的规则
1
2
3
# nft list table inet firewalld
# nft list table ip firewalld
# nft list table ip6 firewalld
当安装 nftables 软件包时,Red Hat Enterprise Linux 会在 /etc/nftables/ 目录中自动创建 *.nft 脚本。这些脚本包含为不同目的创建表和空链的命令。
运行 nftables 脚本
要直接运行 nftables 脚本:
在进行这个时间时:
确保脚本以以下 shebang 序列开头:
确保脚本以以下 shebang 序列开头:
#!/usr/sbin/nft -f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
** 重要 **
如果省略 `- f ` 参数( `- f ` 为执行脚本参数), ` nft ` 实用程序不会读取脚本并显示: ` Error : syntax error , unexpected newline , expecting string ` 。
3 . 1 . 可选:将脚本的所有者设置为 ` root ` :
``` sh
chown root / etc / nftables /< example_firewall_script > . nft
2 . 使脚本可以被其所有者执行:
``` sh
chmod u + x / etc / nftables /< example_firewall_script > . nft
```
3 . 运行脚本:
``` none
# /etc/nftables/<example_firewall_script>.nft
如果没有输出结果,系统将成功执行该脚本。
使用 nftables 脚本中的变量
要在 nftables 脚本中定义一个变量,请使用 define 关键字。您可以在变量中存储单个值和匿名集合。对于更复杂的场景,请使用 set 或 verdict 映射。
只有一个值的变量
以下示例定义了一个名为 INET_DEV 的变量,其值为 enp1s0 :
1
define INET_DEV = enp1s0
您可以通过输入 $ 符号后再输入变量名称来使用脚本中的变量:
1
2
3
...
add rule inet example_table example_chain iifname $INET_DEV tcp dport ssh accept
...
包含匿名集合的变量
以下示例定义了一个包含匿名集合的变量:
1
define DNS_SERVERS = { *192.0.2.1*, *192.0.2.2* }
您可以通过在$符号后跟变量名称来在脚本中使用变量:
1
add rule inet example_table example_chain ip daddr $DNS_SERVERS accept
在 nftables 脚本中包含文件
在 nftables 脚本环境中,您可以使用 include 语句包含其他脚本。
如果您只指定文件名,而没有绝对路径或相对路径,则 nftables 包括默认搜索路径中的文件,该路径被设为 Red Hat Enterprise Linux 上的 /etc。
例 2.2. 包含默认搜索目录中的文件
从默认搜索目录中包含一个文件:
例 2.3. 包含目录中的所有 *.nft 文件
要包括所有存储在 /etc/nftables/rulesets/ 目录中、以 *.nft 结尾的文件:
1
include "/etc/nftables/rulesets/*.nft"
请注意,include 语句不匹配以点开头的文件。
系统引导时自动载入 nftables 规则
nftables systemd 服务加载包含在 /etc/sysconfig/nftables.conf 文件中的防火墙脚本。
先决条件
nftables 脚本存储在 /etc/nftables/ 目录中。
流程
编辑 /etc/sysconfig/nftables.conf 文件。
可选:启动 nftables 服务以在不重启系统的情况下加载防火墙规则:
1
# systemctl start nftables
启用 nftables 服务。
1
# systemctl enable nftables
nftables 地址簇
nftables 中的表是一个包含链、规则、集合和其他对象集合的名字空间。
每个表都必须分配一个地址系列。地址系列定义此表处理的数据包类型。在创建表时,您可以设置以下地址系列之一:
ip:仅匹配 IPv4 数据包。如果没有指定地址系列,这是默认设置。
ip6 :仅匹配 IPv6 数据包.
inet:匹配 IPv4 和 IPv6 数据包。
arp:匹配 IPv4 地址解析协议(ARP)数据包。
bridge:匹配通过网桥设备的数据包。
netdev:匹配来自 ingress 的数据包。
如果要添加表,所使用的格式取决于您的防火墙脚本:
在原生语法的脚本中,使用:
1
2
table <table_address_family> <table_name> {
}
在 shell 脚本中,使用:
1
nft add table <table_address_family> <table_name>
1. 查看命令
1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看所有规则
# -a 显示规则句柄,为了方便在指定位置插入
nft -a list ruleset
# 或者
nft --handle list ruleset
# 查看 指定的表
# nft list table <family> <table>
nft list table inet my_table
# 查看 指定的链
# 命令格式:nft list chain <family> <table> <chain>
$ nft list chain inet my_table my_chain
2. 创建表
1
2
3
# 创建表
# nft add table <family> <table>
$ nft add table inet my_table
3. 创建链
链是用来保存规则的,和表一样,链也需要被显示创建,因为 nftables 没有内置的链。链有以下两种类型:
常规链 : 不需要指定钩子类型和优先级,可以用来做跳转,从逻辑上对规则进行分类。您可以将常规链用作 jump 目标来更好地组织规则。
基本链 : 数据包的入口点,需要指定钩子类型和优先级。
以下是链类型以及您可以使用的地址系列和钩子的概述:
类型
地址系列
Hook
描述
filter
all
all
标准链类型
nat
ip,ip6,inet
prerouting、input、output、postrouting
这个类型的链根据连接跟踪条目执行原生地址转换。只有第一个数据包会遍历此链类型。
route
ip,ip6
output
如果 IP 头的相关部分已更改,则接受的遍历此链类型的数据包会导致新的路由查找。
创建常规链:
1
$ nft add chain inet my_table my_utility_chain
创建基本链:
1
$ nft add chain inet my_table my_filter_chain { type filter hook input priority 0 \; }
反斜线(\)用来转义,这样 shell 就不会将分号解释为命令的结尾。
priority 采用整数值,可以是负数,值较小的链优先处理。
链优先级
priority 参数指定数据包遍历具有相同 hook 值的链的顺序。您可以将此参数设为整数值,或使用标准优先级名称。
以下列表是标准优先级名称及其数字值的一个概述,以及您可以使用它们的哪个地址系列和钩子:
文本值
数字值
地址系列
钩子
raw
-300
ip,ip6,inet
all
mangle
-150
ip,ip6,inet
all
dstnat
-100
ip,ip6,inet
prerouting
dstnat
-300
bridge
prerouting
filter
0
ip,ip6,inet,arp,netdev
all
filter
-200
bridge
all
安全
50
ip,ip6,inet
all
srcnat
100
ip,ip6,inet
postrouting
srcnat
300
bridge
postrouting
out
100
bridge
output
链策略
如果此链中的规则没有指定任何操作,则链策略定义 nftables 是否应该接受或丢弃数据包。您可以在链中设置以下策略之一:
4. 创建规则
提前准备:Quick reference-nftables in 10 minutes - nftables wiki
如何匹配包头:Matching packet headers - nftables wiki
有了表和链之后,就可以创建规则了,规则由语句或表达式构成,包含在链中。下面添加一条规则允许 SSH 登录:
1
$ nft add rule inet my_table my_filter_chain tcp dport ssh accept
add 表示将规则添加到链的末尾,如果想将规则添加到链的开头,可以使用 insert。
1
$ nft insert rule inet my_table my_filter_chain tcp dport http accept
也可以将规则插入到链的指定位置
handle 是标识特定 rule 的内部编号。
position 是一个内部数字,用于在特定 句柄前插入 rule。
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>]
1、 使用 index 来指定规则的索引。 add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。index 的值从 0 开始增加。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ nft insert rule inet my_table my_filter_chain index 1 tcp dport nfs accept
$ nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
}
}
$ nft add rule inet my_table my_filter_chain index 0 tcp dport 1234 accept
$ nft list ruleset
table inet my_table {
chain my_filter_chain {
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 类似于 iptables 的 -I 选项,但有两点需要注意:一是 index 的值是从 0 开始的;二是 index 必须指向一个存在的规则,比如 nft insert rule … index 0 就是非法的。
3、 使用 handle 来指定规则的句柄。add 表示新规则添加在索引位置的规则后面,insert 表示新规则添加在索引位置的规则前面。handle 的值可以通过参数 --handle 获取。
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
$ nft --handle list ruleset
table inet my_table { # handle 10
chain my_filter_chain { # 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
}
}
$ nft add rule inet my_table my_filter_chain handle 4 tcp dport 1234 accept
$ nft insert rule inet my_table my_filter_chain handle 5 tcp dport nfs accept
$ nft --handle list ruleset
table inet my_table { # handle 10
chain my_filter_chain { # 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 my_table my_filter_chain udp dport 3333 accept
add rule inet my_table my_filter_chain udp dport 3333 accept # handle 10
3、使用 position 添加规则到指定位置(与上面方法基本一样)
查看规则句柄
在句柄为 3 的现有规则前面 插入一条规则。例如,要插入一个允许端口 636 上 TCP 流量的规则,请输入:
1
nft insert rule inet nftables_svc INPUT position 3 tcp dport 636 accept
在句柄为 3 的现有规则后面 附加一条规则。例如,要插入一个允许端口 80 上 TCP 流量的规则,请输入:
1
nft add rule inet nftables_svc INPUT position 3 tcp dport 80 accept
5. 删除规则
单个规则只能通过其句柄删除,首先需要找到你想删除的规则句柄:
1
2
3
4
5
6
7
8
9
10
11
12
13
$ nft --handle list ruleset
table inet my_table { # handle 10
chain my_filter_chain { # 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
udp dport 3333 accept # handle 10
}
}
然后使用句柄值来删除该规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
$ nft delete rule inet my_table my_filter_chain handle 8
$ nft --handle list ruleset
table inet my_table { # handle 10
chain my_filter_chain { # handle 2
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 4
tcp dport 1234 accept # handle 6
tcp dport 3456 accept # handle 9
tcp dport nfs accept # handle 5
tcp dport ssh accept # handle 3
udp dport 3333 accept # handle 10
}
}
6. 替换规则
1
2
3
# 格式:nft replace rule [<family>] <table> <chain> [handle <handle>] <matches> <statements>
# 替换 inet example_table example_chain 的句柄为 4 的规则
nft replace rule inet example_table example_chain handle 4 tcp dport 22 counter accept
7. 集合
nftables 的语法原生支持集合,可以用来匹配多个 IP 地址、端口号、网卡或其他任何条件。
匿名集合
集合分为匿名集合 与命名集合 ,匿名集合比较适合用于将来不需要更改的规则。
例如,下面的规则允许来自源 IP 处于 10.10.10.123 ~ 10.10.10.231 这个区间内的主机的流量。
1
2
3
4
5
6
7
8
9
10
11
$ nft add rule inet my_table my_filter_chain ip saddr { 10.10.10.123, 10.10.10.231 } accept
$ nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
ip saddr { 10.10.10.123, 10.10.10.231 } accept
}
}
匿名集合的缺点是,如果需要修改集合,就得替换规则。如果后面需要频繁修改集合,推荐使用命名集合。
之前的示例中添加的规则也可以通过集合来简化:
1
$ nft add rule inet my_table my_filter_chain 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 : 标记类型
先创建一个空的命名集合:
1
2
3
4
5
6
7
$ nft add set inet my_table my_set { type ipv4_addr \; }
$ nft list sets
table inet my_table {
set my_set {
type ipv4_addr
}
}
要想在添加规则时引用集合,可以使用 @ 符号跟上集合的名字。下面的规则表示将集合 my_set 中的 IP 地址添加到黑名单中。
1
2
3
4
5
6
7
8
9
10
11
12
$ nft insert rule inet my_table my_filter_chain ip saddr @my_set drop
$ nft list chain inet my_table my_filter_chain
table inet my_table {
chain my_filter_chain {
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
2
3
4
5
6
7
8
$ nft add element inet my_table my_set { 10.10.10.22, 10.10.10.33 }
$ nft list set inet my_table my_set
table inet my_table {
set my_set {
type ipv4_addr
elements = { 10.10.10.22, 10.10.10.33 }
}
}
如果你向集合中添加一个区间就会报错:
1
2
3
4
5
$ nft add element inet my_table my_set { 10.20.20.0-10.20.20.255 }
Error: Set member cannot be range, missing interval flag on declaration
add element inet my_table my_set { 10.20.20.0-10.20.20.255 }
^^^^^^^^^^^^^^^^^^^^^^^
要想在集合中使用区间,需要加上一个 flag interval,因为内核必须提前确认该集合存储的数据类型,以便采用适当的数据结构。
支持区间
创建一个支持区间的命名集合:
1
2
3
4
5
6
7
8
9
10
$ nft add set inet my_table my_range_set { type ipv4_addr \; flags interval
$ nft add element inet my_table my_range_set { 10.20.20.0/24 }
$ nft list set inet my_table my_range_set
table inet my_table {
set my_range_set {
type ipv4_addr
flags interval
elements = { 10.20.20.0/24 }
}
}
子网掩码表示法会被隐式转换为 IP 地址的区间,你也可以直接使用区间 10.20.20.0-10.20.20.255 来获得相同的效果。
级联不同类型
命名集合也支持对不同类型的元素进行级联,通过级联操作符 . 来分隔。例如,下面的规则可以一次性匹配 IP 地址、协议和端口号。
1
2
3
4
5
6
7
8
$ nft add set inet my_table my_concat_set { type ipv4_addr . inet_proto . inet_service \; }
$ nft list set inet my_table my_concat_set
table inet my_table {
set my_concat_set {
type ipv4_addr . inet_proto . inet_service
}
}
向集合中添加元素:
1
$ nft add element inet my_table my_concat_set { 10.30.30.30 . tcp . telnet }
在规则中引用级联类型的集合和之前一样,但需要标明集合中每个元素对应到规则中的哪个位置。
1
$ nft add rule inet my_table my_filter_chain ip saddr . meta l4proto . tcp dport @my_concat_set accept
这就表示如果数据包的源 IP、协议类型、目标端口匹配 10.30.30.30、tcp、telnet 时,nftables 就会允许该数据包通过。
匿名集合也可以使用级联元素,例如:
1
$ nft add rule inet my_table my_filter_chain ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept
现在你应该能体会到 nftables 集合的强大之处了吧。
nftables 级联类型的集合类似于 ipset 的聚合类型,例如 hash:ip,port。
8. 字典
字典是 nftables 的一个高级特性,它可以使用不同类型的数据并将匹配条件映射到某一个规则上面,并且由于是哈希映射的方式,可以完美的避免链式规则跳转的性能开销。
例如,为了从逻辑上将对 TCP 和 UDP 数据包的处理规则拆分开来,可以使用字典来实现,这样就可以通过一条规则实现上述需求。
1
2
3
4
5
6
7
8
9
10
11
$ nft add chain inet my_table my_tcp_chain
$ nft add chain inet my_table my_udp_chain
$ nft add rule inet my_table my_filter_chain meta l4proto vmap { tcp : jump my_tcp_chain, udp : jump my_udp_chain }
$ nft list chain inet my_table my_filter_chain
table inet my_table {
chain my_filter_chain {
...
meta nfproto ipv4 ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept
meta l4proto vmap { tcp : jump my_tcp_chain, udp : jump my_udp_chain }
}
}
和集合一样,除了匿名字典之外,还可以创建命名字典:
1
$ nft add map inet my_table my_vmap { type inet_proto : verdict \; }
向字典中添加元素:
1
$ nft add element inet my_table my_vmap { 192.168.0.10 : drop, 192.168.0.11 : accept }
后面就可以在规则中引用字典中的元素了:
1
$ nft add rule inet my_table my_filter_chain ip saddr vmap @my_vmap
9. 表与命名空间
在 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 my_chain
$ nft add table inet table_two
$ nft add chain inet table_two my_chain
$ nft list ruleset
...
table inet table_one {
chain my_chain {
}
}
table inet table_two {
chain my_chain {
}
}
有了这个特性,不同的应用就可以在相互不影响的情况下管理自己的表中的规则,而使用 iptables 就无法做到这一点。
当然,这个特性也有缺陷,由于每个表都被视为独立的防火墙,那么某个数据包必须被所有表中的规则放行,才算真正的放行,即使 table_one 允许该数据包通过,该数据包仍然有可能被 table_two 拒绝。为了解决这个问题,nftables 引入了优先级,priority 值越高的链优先级越低,所以 priority 值低的链比 priority 值高的链先执行。如果两条链的优先级相同,就会进入竞争状态。
10. 备份与恢复
以上所有示例中的规则都是临时的,要想永久生效,我们可以将规则备份,重启后自动加载恢复,其实 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 文件中,但默认会被注释掉。
配置示例
简单的IPv4/IPv6防火墙
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
# A simple firewall
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# established/related connections
ct state established,related accept
# invalid connections
ct state invalid drop
# loopback interface
iif lo accept
# ICMP & IGMP
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report } accept
ip protocol icmp icmp type { destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem } accept
ip protocol igmp accept
# SSH (port 22)
tcp dport ssh accept
# HTTP (ports 80 & 443)
tcp dport { http, https } accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
IPv4/IPv6防火墙限流
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
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state invalid drop
iif lo accept
# no ping floods:
ip protocol icmp icmp type echo-request limit rate over 10/second burst 4 packets drop
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 10/second burst 4 packets drop
ct state established,related accept
# ICMP & IGMP
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report } accept
ip protocol icmp icmp type { destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem } accept
ip protocol igmp accept
# avoid brute force on ssh:
tcp dport ssh ct state new limit rate 15/minute accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
配置简单可用的防火墙
nftables 简体中文 - nftables 中文教程 (gitbook.io)
清空当前规则集:
添加一个表:
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:
established 表示已经建立的TCP连接。
related 表示与已经建立的连接相关的数据包。
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
nftables 中 TCP 的 4 种状态
和 iptables 一样,一个 TCP 连接在 nftables 中总共有四种状态:NEW,ESTABLISHED,RELATED 和 INVALID。
除了本地产生的包由 OUTPUT 链处理外,所有连接跟踪都是在 PREROUTING 链里进行处理的,意思就是, iptables 会在 PREROUTING 链里从新计算所有的状态。如果我们发送一个流的初始化包,状态就会在 OUTPUT 链里被设置为 NEW,当我们收到回应的包时,状态就会在 PREROUTING 链里被设置为 ESTABLISHED。如果收到回应的第一个包不是本地产生的,那就会在 PREROUTING 链里被设置为 NEW 状态。综上,所有状态的改变和计算都是在 nat 表中的 PREROUTING 链和 OUTPUT 链里完成的。
还有其他两种状态:
RELATED : RELATED 状态有点复杂,当一个连接与另一个已经是 ESTABLISHED 的连接有关时,这个连接就被认为是 RELATED。这意味着,一个连接要想成为 RELATED,必须首先有一个已经是 ESTABLISHED 的连接存在。这个 ESTABLISHED 连接再产生一个主连接之外的新连接,这个新连接就是 RELATED 状态了。
INVAILD : 表示分组对应的连接是未知的,说明数据包不能被识别属于哪个连接或没有任何状态。有几个原因可以产生这种情况,比如,内存溢出,收到不知属于哪个连接的 ICMP 错误信息。我们需要 DROP 这个状态的任何东西,并打印日志:
参考链接:让 Linux 防火墙新秀 nftables 为你的 VPS 保驾护航 - 知乎 (zhihu.com)
新的echo请求(ping)会accept:
1
2
# 仅包含 1pv4 的 ping
nft add rule inet filter input ip protocol icmp icmp type echo-request ct state new accept
1
2
3
# 包含 ipv4 和 ipv6 的 ping,而且限制最多每秒 ping 5 次 accpet
nft add rule inet filter input ip protocol icmp icmp type echo-request limit rate 5/second accept
nft add rule inet filter input ip6 nexthdr ipv6-icmp icmpv6 type echo-request limit rate 5/second accept
速率限制详解:
ICMP & IGMP
1
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report } accept
以上命令中 icmpv6 type 的解释
destination-unreachable:目的地不可达,表示数据包无法到达目的地,有多种原因,比如没有路由、端口不可用、源地址被拒绝等。
packet-too-big:数据包超大,表示数据包超过了路径最大传输单元(MTU),需要分片或者缩小。
time-exceeded:时间超过,表示数据包在传输过程中生存时间(TTL)或者跳数限制(HL)耗尽。
parameter-problem:参数问题,表示数据包中有错误的或者不识别的字段或者选项。
mld-listener-query:多播侦听器查询,表示询问接收方是否侦听某个多播地址。
mld-listener-report:多播侦听器报告,表示通知发送方接收方正在侦听某个多播地址。
mld-listener-reduction:多播侦听器减少,表示通知发送方接收方已经停止侦听某个多播地址。
nd-router-solicit:邻居发现路由器请求,表示询问本链路上是否有路由器存在。
nd-router-advert:邻居发现路由器通告,表示宣告本链路上有路由器存在,并提供一些配置信息。
nd-neighbor-solicit:邻居发现邻居请求,表示询问本链路上某个地址对应的链路层地址。
nd-neighbor-advert:邻居发现邻居通告,表示回复本链路上某个地址对应的链路层地址,并验证可达性。
ind-neighbor-solicit:逆向邻居发现邻居请求,表示询问本链路上某个代理设备是否为目标节点服务,并获取其链路层地址。
ind-neighbor-advert:逆向邻居发现邻居通告,表示回复本链路上某个代理设备是否为目标节点服务,并提供其链路层地址和状态信息。
mld2-listener-report:多播侦听器报告第二版,是对 mld-listener-report 的扩展和改进,在一个报文中可以报告多个多播组的情况。
1
2
# 使用 nft describe 查看更多 icmpv6 类型关键字
nft describe icmpv6 type
1
nft add rule inet filter input ip protocol icmp icmp type { destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem } accept
1
nft add rule inet filter input ip protocol igmp accept
UDP流量并且连接状态为 new,跳转到UDP链:(未匹配的流量会回到原本的链中)
1
nft add rule inet filter input ip protocol udp ct state new jump UDP
TCP流量并且 tcp 标志位中有 syn(同步标志)而没有其他标志位,并且连接状态是 new,跳转到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
4
5
6
# 其他 udp reject
# 其他 tcp reject 并发送 tcp reset 给对方
# 对于其他情况,统计并拒绝数据包,并发送 icmp type prot-unreachable 消息给对方
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
2
3
nft add rule inet filter TCP tcp dport 22 accept
# 或者使用以下限流规则
nft add rule inet filter TCP tcp dport ssh ct state new limit rate 15/second accept
也可以用匿名集合一次加入多个端口
1
2
3
tcp dport { ssh, http, https } accept
# 或者
tcp dport { 22, 80, 443 } accept
允许传入DNS请求:
1
2
nft add rule inet filter TCP tcp dport 53 accept
nft add rule inet filter UDP udp dport 53 accept
确保更改是永久的。
查看规则列表 nft list ruleset:
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
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif "lo" accept
ct state invalid drop
ip protocol icmp icmp type echo-request limit rate 5/second accept
ip6 nexthdr ipv6-icmp icmpv6 type echo-request limit rate 5/second accept
ip6 nexthdr ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report } accept
ip protocol icmp icmp type { destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem }
ip protocol igmp accept
ip protocol udp ct state new jump UDP
ip protocol tcp tcp flags & ( fin | syn | rst | ack) == syn ct state new jump TCP
ip protocol udp reject
ip protocol tcp reject with tcp reset
meta nfproto ipv4 counter packets 0 bytes 0 reject with icmp type prot-unreachable
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
chain TCP {
tcp dport 80 accept
tcp dport 443 accept
tcp dport 22 accept
udp dport 53 accept
}
chain UDP {
udp dport 53 accept
}
}
Gentoo Wiki 示例
Nftables/Examples - Gentoo Wiki — Nftables/示例- Gentoo Wiki
日志处理
让 Linux 防火墙新秀 nftables 为你的 VPS 保驾护航 - 知乎 (zhihu.com)
其他知识
ip6 nexthdr 是一个 nftables 中用来匹配 IPv6 数据包的下一个头部类型的表达式。它可以用来指定数据包的传输层协议,比如 tcp 或 udp,或者 IPv6 的扩展头部,比如 hopopt 或 frag。例如,你可以使用 ip6 nexthdr icmpv6 来匹配 ICMPv6 类型的数据包。
不过,要注意的是,ip6 nexthdr 只能匹配下一个头部类型,而不是最终的头部类型。也就是说,如果数据包中有多个扩展头部,你需要使用多个 ip6 nexthdr 表达式来匹配它们。例如,如果你想匹配一个有路由扩展头部和 TCP 头部的数据包,你需要这样写:
ip6 nexthdr routing ip6 nexthdr tcp
如果你只写 ip6 nexthdr tcp ,那么这个规则就不会匹配到这个数据包。
ip protocol 是一个 nftables 中用来匹配 IPv4 数据包的协议类型的表达式。它可以用来指定数据包的传输层协议,比如 tcp 或 udp,或者 IPv4 的选项头部,比如 icmp 或 igmp。例如,你可以使用 ip protocol icmp 来匹配 ICMP 类型的数据包。
如果你想同时匹配 IPv4 和 IPv6 的协议类型,你可以使用 meta l4proto 表达式。例如,你可以使用 meta l4proto tcp 来匹配 TCP 类型的数据包,无论它们是 IPv4 还是 IPv6。