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 | 列出所有规则集 |
输入策略
就像 iptables 一样,防火墙将包含三部分:输入(input)、转发(forward)和输出(output)。在终端中,为输入(input)策略键入以下命令。在开始之前,请确保已刷新规则集。我们的默认策略将会删除所有内容。我们将在防火墙中使用 inet 地址族。将以下规则以 root 身份添加或使用 sudo 运行:
1 | 增加一张表 |
表(Tables)
表包含#链[断开的链接:无效的部分]。与iptables中的表不同,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
类型的链。(source)
有关地址簇的完整描述,请参见nft(8)中的ADDRESS FAMILIES
章节。
在以下情况中,family
是可选的,如果未指定则设为ip
。
创建表
创建一个新的表:
1 | # nft add table family table |
列出表
列出所有表:
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 | # nft add chain inet filter input |
基本链
添加基本链,需要指定钩子和优先级值:
1 | # nft add chain family table chain { type type hook hook priority priority \; } |
*type*
可以是filter
、route
或者nat
。
IPv4/IPv6/Inet地址簇中,*hook*
可以是prerouting
、input
、forward
、output
或者postrouting
。其他地址簇中的钩子列表请参见nft(8)。
*priority*
采用整数值。数字较小的链优先处理,并且可以是负数。[3]
例如,添加筛选输入数据包的基本链:
1 | # nft add chain inet filter input { type filter hook input priority 0\; } |
将上面命令中的add
替换为create
则可以添加一个新的链,但如果链已经存在,则返回错误。
列出规则
列出一个链中的所有规则:
1 | # nft list chain family table chain |
例如,要列出inet
中filter
表的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 | nft add rule inet filter input tcp dport 22 accept |
表达式
通常情况下,*statement*
包含一些要匹配的表达式,然后是判断语句。结论语句包括accept
、drop
、queue
、continue
、return
、jump *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 | meta: |
注意: 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 | # nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; } |
添加两个与TCP和UDP关联的常规链:
1 | # nft add chain inet filter TCP |
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 | # nft add rule inet filter input ip protocol udp reject |
此时,应决定对传入连接打开哪些端口,这些由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 | # nft add rule inet filter TCP tcp 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 | nft list ruleset |
现在表中还没有任何规则,需要创建一个链来保存规则。
创建链
链是用来保存规则的,和表一样,链也需要被显示创建,因为 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 | nft list chain inet nftables_svc my_utility_chain |
创建规则
有了表和链之后,就可以创建规则了,规则由语句或表达式构成,包含在链中。下面添加一条规则允许 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 | nft insert rule inet nftables_svc INPUT index 1 tcp dport nfs accept |
index 类似于 iptables 的 -I 选项,但有两点需要注意:一是 index 的值是从 0 开始的;二是 index 必须指向一个存在的规则,比如 nft insert rule … index 0 就是非法的。
2、 使用 handle 来指定规则的句柄。add 表示新规则添加在索引位置的规则后面,inser 表示新规则添加在索引位置的规则前面。handle 的值可以通过参数 –handle 获取。
1 | nft --handle list ruleset |
使用add
1 | nft add rule inet nftables_svc INPUT handle 4 tcp dport 666 accept |
在 nftables 中,句柄值是固定不变的,除非规则被删除,这就为规则提供了稳定的索引。而 index 的值是可变的,只要有新规则插入,就有可能发生变化。一般建议使用 handle 来插入新规则。
也可以在创建规则时就获取到规则的句柄值,只需要在创建规则时同时加上参数 –echo 和 –handle。
1 | nft --echo --handle add rule inet nftables_svc INPUT udp dport 333 accept |
删除规则
单个规则只能通过其句柄删除,首先需要找到你想删除的规则句柄:
1 | nft --handle list ruleset |
列出规则
前面的示例都是列出了所有规则,我们还可以根据自己的需求列出规则的一部分。例如:
列出某个表中的所有规则:
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 | nft add set inet nftables_svc allowed_ssh_ipv4_ips { |
要想在添加规则时引用集合,可以使用 @ 符号跟上集合的名字。下面的规则表示将集合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 | nft add element inet nftables_svc allowed_ssh_ipv4_ips { 192.168.224.1-192.168.224.231} |
要想在集合中使用区间,需要加上一个 flag interval,因为内核必须提前确认该集合存储的数据类型,以便采用适当的数据结构。
支持区间
创建一个支持区间的命名集合:
1 | nft add set inet nftables_svc my_range_set { type ipv4_addr \; flags interval \; } |
子网掩码表示法会被隐式转换为 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 | nft add set inet nftables_svc my_concat_set { type ipv4_addr . inet_proto . inet_service \; } |
向集合中添加元素:
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 | nft add chain inet nftables_svc TCP |
和集合一样,除了匿名字典之外,还可以创建命名字典:
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 | nft add table inet table_one |
有了这个特性,不同的应用就可以在相互不影响的情况下管理自己的表中的规则,而使用 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 | # drop any existing nftables ruleset |
后期新增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 |
- 本文标题:nftables
- 本文作者:yichen
- 本文链接:https://yc6.cool/2020/09/30/nftables/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!