目录
- 前言
- 基本概念
- nft命令的语法
- nft命令示例(基础版)
- nft命令示例(进阶版)
- 1.NAT与端口重定向
- 2.限制网速
- 3.限制流量
- 4.连接数限制
- 5.reject返回错误
- [6.防syn flood](#6.防syn flood)
- 7.其他例子请参考后面的"其他数据结构:范围、集合、映射等"
- 匹配表达式和动作
- 其他数据结构:范围、集合、映射等
- 范围
- 集合(sets)
- 映射(maps)
- 连接
- [状态对象(stateful object)](#状态对象(stateful object))
- 流表(flowtable)
- nftables脚本
前言
- 本文先说明语法规则,再展示命令示例,最后详细解释相关知识
- 相信你认真看完本文,并按博文例子实践之后,你会完全从iptables过渡过来。
- 本博文主要参考
基本概念
-
层级关系:分表、链、规则,表可以装多条链,链可以装多条规则。
-
地址族类型 :一共6种,ip、ip6、inet、arp、bridge、netdev。这6种分别表示"ipv4地址族"、"ipv6地址族"、"ipv4和ipv6双栈地址族"、"arp地址族"、"桥接地址族"(处理经过桥接设备的报文)、"netdev网络设备地址族",处理入站和出站报文专门用于创建绑定到单个网络接口的基础链,这类基础链能够监控指定接口上的所有网络流量(不分2、3层)。创建的每个表、链、规则都要指明地址族类型。不指定地址族时,表示使用默认值,即ip(作用于ipv4)
-
链类型:链分基本链和常规链,常规链就是创建的时候花括号中不带任何内容,常规链常于跳转的目标链。
-
基本链有3种filter、nat、route
类型 支持地址族 支持的钩子 说明 filter 全部 全部 标准链类型 nat ip、ip6、inet input、output、forward、prerouting、postrouting snat、dnat、源地址伪装等 route ip、ip6 output 会影响路由结果 -
上面提到的钩子之间的关系是
本 地 处 理 ^ | .-----------. .-----------. | | | 路 由 | | |-----> input / \---> | 选 择 |----> output -\ --> prerouting ---> | 路 由 | .-----------. \ | 选 择 | --> postrouting | | / | |---------------> forward --------------------------- .-----------. 4.2内核后多了ingress钩子,ingress钩子与其他钩子的关系如下: .-----------. | |-----> input ... ---> ingress ---> prerouting --->| 路 由 | | 选 择 | | | | |-----> forward ... .-----------.
-
-
链的优先级:优先级指定数据包遍历具有相同钩子的链的顺序。您可以将此参数设为整数值,或使用标准优先级名称。
-
标准优先级见下表(优先级filter、srcnat、dstnat在使用bridge地址族是,这三个标准优先级所代表的整数值会与下表不同,可以自行参看man nftables):
名称 所代表的数值 地址族 钩子 raw -300 ip, ip6, inet 全部 mangle -150 ip, ip6, inet 全部 dstnat -100 ip, ip6, inet prerouting filter 0 ip, ip6, inet, arp, netdev 全部 security 50 ip, ip6, inet 全部 srcnat 100 ip, ip6, inet postrouting
-
-
链的预设动作:某链下所有规则都不匹配的时候,就使用该链的预设动作(accept或drop)。如果匹配了某链下的某个规则,则使用该规则的动作(比如accpet、srcnat、dstnat、drop、jump等)。
-
规则的构成 : 规则由"匹配表达式(Expressions)"和"动作(Statements)"构成。关于匹配表达式和动作说明见后面章节的匹配表达式和动作。
nft命令的语法
-
nft命令的选项
-a 显示handle(句柄号),我们可以通过规则的handle号来执行删除、在某handle号代表的规则之前或之后插入规则,替换某handle号代表的规则。 -D 名称=值,定义变量。仅可与 -f 选项配合使用。 -c 检查语法,而不是真的执行 -o 优化语句,可以和-c配合使用 -n 数字格式显示,例如:没有此选项nft list ruleset显示某条规则为ct state established,related accept。使用nft -n list ruleset,则该规则显示为ct state 0x2,0x4 accept。 -s 不显示动态状态信息(比如有counter的规则,nft -s list ruleset时,不会显示数据表个数和流量字节数) -S 显示服务名,比如22显示成ssh,80显示成http -N 反向解析(不显示ip地址,而是解析成名称) -j 以json格式输出 -u 打印用户/组的guid -y 以数字形式打印链的优先级 -p 以数字形式打印第四层协议 -T 以数字形式打印时间 -t 输出中省略集合内容。
-
shell转义注意事项
在shell中,分号(;)表示语句结束,花括弧({})表示展开(比如rm /tmp/{a,b}.txt等效于rm /tmp/a.txt /tmp/b.txt),<和>表示重定向,&表后台执行,()表示子shell命令组... 所以nft命令遇到; {} <>等时要转义或用单引号包含。
比如:
nft add chain ip tb0 ch0 '{ type filter hook input priority 0; policy accept; }'
再比如:
nft add rule ip tb0 ch0 tcp dport '{22,80,443}' accept
如果你不用shell解释器,而是直接使用nft解释器,那么就无需做shell转义。具体用法如下
```
root@raspberrypi:~#
root@raspberrypi:~# nft -i # 输入nft -i就进入nft交互模式,解释器变成nft了,花括号外就不需要单引号包含了,免去了某些符合的shell转义问题。
nft> add rule inet tb0 ch0 ip saddr 192.168.1.121 tcp dport {80,443} drop
```
-
规则集(ruleset)操作
nft list ruleset [地址族]
# 显示规则集,也就是显示所有表、链、规则等。如果需要查看特定地址族的规则集,则需要加上地址族。
nft -a list ruleset
#显示规则集及handle号(句柄号,可以通过句柄号插入、删、改规则,删表或链一般不用handle号,而是直接用名字)
nft flush ruleset
#清理所有规则级 -
表操作
nft <add|create> table [地址族] <表名> ['{ comment "备注";}' '{ flags 标记}']
#添加表,地址族可选(如果是非ipv4地址族,则必选),备注和标志,create和add类似,但是如果表名存在,则create会返回错误。
nft add table [地址族] <表名> '{ flags dormant; }'
# 临时禁用某个表(表的地址族不是ip,则必须指明地址族),使用 nft add table [地址族] <表名> 恢复表。
nft delete table [地址族] <表名>
#删除表,如果待删表的地址族不是ip,则必须指明地址族
请注意:后面的内容对于[地址族]不再强调"地址族如果不是ip,则必须指明地址族" -
链操作
nft <add|create> chain [地址族] <所属表名> <链名> '{ type <基本链类型> hook <钩子名> priority <优先级>; policy <预设动作>; [comment 备注]}'
# 添加基本链,关于基本链类型、钩子函数名、 优先级见基本概念。链的预设动作可选值为accept和drop。如果不写policy <链的预设动作>,则表示该链的预设动作是accept。 如果不加'{}' 不分,则表示该链是常规链,常用于规则跳转的目标链create和add差不多,只是create创建链的时候,如果链已经在,则返回错误。
nft <delete|list|flush> chain [地址族] <所属表名> <链名>
# 删除链、列出链、清除链规则
nft list chains [地址族]
# 列出所有链,或指定地址族的链,注意这里是chains,复数。
nft rename chain [地址族] table <所属表名> <旧链名> <新链名>
# 重命名链 -
规则操作
nft <add|insert> rule [地址族] <表名> <链名> [handle 句柄号 | index 规则序号] 语句... [comment 备注]
# 添加或插入一条规则,句柄号、规则序号、备注是可选。规则序号是一条链上的排序,从0开始排序。语句由匹配表达式和动作构成。(man 原文是 expressions和statements, man中statements有各种各样的动作,所以这里我就直接翻译成动作了)
nft replace rule [地址族] <表名> <链名> handle 句柄号 语句... [comment 备注]
# 替换规则(使用句柄号来表示旧规则)
delete rule [地址族] <表名> <链名> handle 句柄号
# 删除规则(按句柄号来删除)
nft命令示例(基础版)
- 表、链、规则基础操作 (匹配表达式、动作的解释参考后文的 匹配表达式和动作 )
nft add table inet tb0
# 添加一个表,表名是tb0, 作用于inet地址族(ipv4和ipv6双栈)
nft add chain inet tb0 ch0 '{ type filter hook input priority 0; policy accept; }'
# 在tb0表下添加一条名为ch0的基本链,链的类型是filter,钩子是input,优先级是0,链的预设动作是accept(此处可以省略"policy accept;", 因为不指明预设动作,就是使用accept)。
nft add rule inet tb0 ch0 oifname eth0 tcp dport 22 drop
# 在tb0表的ch0链下添加一条规则,从eth0出去的tcp 22端口的数据包要丢弃(禁止本机通过eth0访问外部ssh)
nft -a list ruleset
# 列出规则集,使用-a选项后,我们看到上一条规则的句柄号是2,下面的命令我们在句柄号2代表的规则前插入一条规则
nft insert rule inet tb0 ch0 handle 2 oifname eth0 tcp dport '{80,443}' counter comment '"统计通过eth0访问http/https的浏览"'
# 在句柄号为2的规则前插入一条规则,并给规则增加备注(为了防止shell转移,备注用单引号和双引号包裹,这样才可以让备注可以使用空格等字符)
nft add table inet tb0 '{flags dormant;}'
# 临时禁用tb0表,测试发现通过eth0可以访问外部ssh了。
nft add table inet tb0
# 重新启用tb0, 测试发现又不可以通过eth0访问外部ssh了。
nft replace rule inet tb0 ch0 handle 2 oifname eth0 tcp dport 22222 drop comment '"禁止通过eth0访问外部22222端口"'
# 替换原来句柄号是2的规则,并给规则备注
nft delete rule inet tb0 ch0 handle 2
# 通过句柄号删除限制本机通过eth0访问外部ssh的规则
nft flush chain inet tb0 ch0
# 清空tb0表下的ch0链下的所有规则。
nft add table ip tb1 '{ comment '\"测试表1\"'; }'
# 添加一个表tb1,并备注。注意因为花括号外已经使用了一对单引号,所以comment部分的双引号要"转义
nft add chain inet tb1 ch1 '{ type filter hook output priority filter; comment '\"通过eth0出去的\"'; }'
# 添加一条新链ch1, 并备注
nft rename chain inet tb1 ch1 ch_test
# 修改tb1表下的ch1链的名字为ch_test - 集合、映射等基础操作示例请参考后文 ,请点击:其他数据结构:范围、集合、映射等 )
nft命令示例(进阶版)
-
进阶示例的知识点不是循序渐进的,相关知识点可能出现在本段落之后。
1.NAT与端口重定向
-
NAT语法
bashsnat [[ip | ip6] to] 地址 [:端口号] [标志] dnat [[ip | ip6] to] 地址 [:端口号] [标志] masquerade [to :端口号] [标志] redirect [to :端口号] [标志] 地址 := 地址 | 地址1 - 地址2 # 单个地址或地址范围 端口号 := 端口号 | 端口号1 - 端口号2 # 单个端口号或端口号范围 标志 := persistent | fully-random # 固定端口号或随机端口号
-
SNAT源地址转换
应用场景:请求的数据要跨子网流出
为什么要SNAT: 举例,假设电脑有两个接口,eth0(10.0.0.10,能连上互联网)和wlan0(192.168.1.11)。手机通过wifi连接到电脑的wlan0,手机的默认网关指向192.168.1.11(此时wlan0是热点模式),手机的请求数据包通过电脑的wlan0进入电脑,电脑的启用ipv4 forward转发之后,wlan0进来的数据包会被转发后经eth0出去,经eth0出去的ip数据包的源地址是192.168.1.11,远端服务器响应后返回的数据包没法回到电脑。所以就需要在ip数据包从eth0出去前把源地址替换成eth0的ip地址10.0.0.10。所以说SNAT和出去的数据包相关, 也就是路由选择之后,所以SNAT关联的钩子是postrouting钩子。
nft add table ip tb0
# 添加表
nft add chain ip tb0 ch_snat '{type nat hook postrouting priority srcnat; }'
# 添加链,使用postrouting钩子(路由选择之后)
nft add rule ip tb0 ch_snat oifname eth0 ip saddr 192.168.1.11 snat to 10.0.0.10
# 对流出接口是eth0,源地址是192.168.1.11做SNAT,把源地址替换成eth0的ip地址10.0.0.10(SNAT用于知道源IP地址的场景)。 -
masquerade 源地址伪装,相当于动态SNAT
nft add table ip tb0
# 添加表
nft add chain ip tb0 ch_snat '{type nat hook postrouting priority srcnat; }'
# 添加链,使用postrouting钩子(路由选择之后)
nft add rule ip tb0 ch_snat oifname eth0 ip saddr 192.168.1.0/24 masquerade
# 源地址伪装(masquerade用于不知道源ip地址场景),假设电脑有两个网口eth0(10.0.0.10)和wlan0(192.168.1.11),eth0连接了互联网,现在在电脑开启ipv4 forward(命令sysctl -w net.ipv4.ip_forward=1)。wlan0是开启了热点功能,并启用了DHCP服务(手机连上该热点后,获取了一个192.168.1.X的网址,默认网关是192.168.1.11)。然后手机就可以通过电脑的网络上网了。 -
DNAT目标地址转换
应用场景:请求的数据要跨子网流入
为什么要DNAT:举例,假设主机A有两个接口eth0(10.0.0.10)和eth1(192.168.1.11)。 主机B上有web服务,监听在192.168.1.200,主机B的网线连接了主机A的eth1,,现在想要通过主机A的eth0跨子网请求主机B的web服务,那么当数据包从主机A的eth0进来的时候,目标地址eth0的IP地址,此时数据包还在路由选择之前,为了访问主机B的web服务,就需要把数据包的目标地址改成主机B的IP地址192.168.1.200。DNAT和进入的数据相关,还处于路由选择之前,所以要用prerouting钩子。
nft add table ip tb0
# 添加表
nft add chain ip tb0 ch_dnat '{type nat hook prerouting priority dstnat; }'
# 添加链,使用postrouting钩子(路由选择之后)
nft add rule ip tb0 ch_dnat iifname eth0 tcp dport 80 dnat to 192.168.1.200
# 把从eth0进来的,且目标端口是80的数据包做目标地址转换,进来的数据包目标ip地址从eth0的10.0.0.10转换另一个主机的ip地址192.168.1.200。如果192.168.1.200的web服务监听在8000端口,则最后一段应该写成dstnat 192.168.1.200:8000
-
端口重定向
nft create table ip tb0; nft add chain ip tb0 ch0 '{type filter hook prerouting priority filter; }'
# 添加表和链,链的钩子是prerouting
nft add rule ip tb0 ch0 tcp dport 80 redirect to :8000
# 把访问80的端口重定向到8000, 测试http服务监听于8000端口,外部浏览器仍然可以通过80端口访问该http服务
2.限制网速
nft create table inet tb0; nft add chain inet tb0 ch0 '{ type filter hook input priority filter; }'
# 添加表和链,地址族是inet(作用于ipv4和ipv6)
nft add rule inet tb0 ch0 iif wlan0 ip saddr != 192.168.1.0/24 limit rate over 50 kbytes/second drop
# 对于从wlan0进入的且源ip地址不是192.168.1.0/24的数据包,超过50KB/秒就drop。可以用wget测试速度。
nft add rule inet tb0 ch0 iif eth0 meta l4proto tcp limit rate over 200 kbytes/second burst 100 mbytes drop
# 对tcp流量限速,我们观察到突发流量100MB用完后,速率就下降到200KB/秒
nft add rule inet tb0 ch0 iif eth1 ip saddr != 192.168.3.0/24 limit rate over 100 mbytes/minute counter
# 超过100MB/分钟就DROP,流量可以按字节数算(单位可以是bytes、kbytes、mbytes),也可按包个数算(100/s
表示100个包每秒)。周期可以是second、minute、hour、day、week。
*
3.限制流量
nft add table ip tb0; nft add chain ip tb0 ch1 '{ type filter hook output priority 0 ; }'
# 添加表和链,地址族是ip
nft add set ip tb0 s0 '{type ipv4_addr; timeout 1d; flags dynamic;}'
# 添加一个存放ipv4地址的集合,集合元素过期时间是1天,元素是动态加入的
nft add rule ip tb0 ch1 oif wlan0 tcp sport '{80,443}' add @s0 '{ip saddr}'
# 把访问本机的http和https的源ipv4地址动态添加到集合s0。这里add @s0是动作,不用update,因为update会更新过期时间(动作的语法参考后面动作部分)
nft add rule ip tb0 ch1 oif wlan0 ip saddr @s0 tcp sport '{80,443}' quota over 4096 mbytes counter drop
# 添加规则,动作是:限制集合s0中的源ipv4地址的访问本机的下行流量为4GB,统计流量,超过就drop。我的实测是s0使用2分钟超时,下载流量10MB,使用nft -a list ruleset查看流量后发现超过10MB后,客户端无法访问本机web服务,2分钟超时后,又能恢复访问。
还有一种简洁的写法,上面的第3、4句添加的两条规则可以用一条规则解决: nft add rule ip tb0 ch1 oif wlan0 tcp sport '{80,443}' add @s0 '{ip saddr quota over 4096 mbytes }' counter drop
*
4.连接数限制
nft add table inet tb0; nft add chain inet tb0 ch0 '{ type filter hook input priority 0 ; }'
# 添加表和链
nft add set inet tb0 set-ssh '{type ipv4_addr;flags dynamic; }'
# 添加集合,集合元素是ipv4地址, 标志是动态(动态加入元素)
nft add rule inet tb0 ch0 tcp dport 22 ct state new add @set-ssh '{ ip saddr ct count over 2 }' counter reject with tcp reset
# 限制同一ip连进来的ssh连接数是2,超过就拒绝链接。测试发现第三次连ssh服务失败。这条语句有三个动作"add集合元素"、counter、reject
*
5.reject返回错误
-
tcp
nft create table inet tb0; nft add chain inet tb0 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是inet
nft add rule inet tb0 ch0 iif wlan0 ip protocol tcp reject with tcp reset
# 使用 tcp reset拒绝来从wlan0进来tcp流量 -
icmp禁ping
nft create table inet tb0; nft add chain inet tb0 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是ip
nft add rule ip tb0 ch0 iif eth0 icmp type echo-request reject with icmp host-unreachable
# 对来自eth0的ping请求做"主机不可达"错误icmp包返回。icmp或icmpv6的更多信息请参考匹配表达式和动作
6.防syn flood
nft create table inet tb0; nft add chain inet tb0 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是inet
nft add rule inet tb0 ch0 tcp flags '& (fin | syn | rst | ack) == syn' ct state new limit rate over 20/second burst 50 packets counter drop
#限制新连接的tcp syn包20个每秒,突发流量50个包。还要结合syn cookie、源ip验证等来防止syn flood
*
7.其他例子请参考后面的"其他数据结构:范围、集合、映射等"
匹配表达式和动作
匹配表达式
-
提示:这里给出的是常用的,不常用的匹配条件以及例子可以参考:https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Matches
-
ip:
<saddr|daddr> <ip地址>
:源/目标ipv4地址,例如:ip saddr 192.168.1.22
或ip saddr 192.168.1.0/24
或ip saddr 192.168.1.2-192.168.1.254
protocol <协议>
: ip协议,取值为tcp udp icmp等, 如果你是inet地址族的,要使用meta l4proto
length <数值>
: 连接所有包的长度 -
ip6:
<saddr|daddr> <ipv6地址>
:源/目标ipv6地址,例如ip6 saddr ::/64
length <数值>
: ipv6连接的payload长度。例如:ip6 length != 233
-
tcp/udp:
<tcp/udp> <sport|dport> <端口号>
:tcp/udp的源/目标端口,例如tcp sport {22,80,443}
ucp dport gt 40000
。
tcp flags <标志>
:tcp标志,有8个标志 fin, syn, rst, psh, ack, urg, ecn, cwr。例如tcp flags '& (fin|syn|ack|rst) == syn'
-
icmp/icmpv6:
type <icmp/icmpv6类型>
: 类型。例如icmp type echo-request
表示ping请求。icmpv6 type echo-reply
表示icmpv6 ping回复 -
ether:
saddr <mac地址>
: 以太网源mac地址, 例如ether saddr 00:0f:11:0c:31:0d
type <类型>
: 以太网帧类型,例如ether type vlan
-
vlan:
id <vlan号>
: vlan id, 例如:vlan id 134
-
ct: 注意ct是关于连接的,和meta不一样,meta是关于包的。
state <连接状态>
:连接的状态,可选值有:new, established, related, untracked 例如:ct state new
direction <original|reply>
: 连接的方向, 可选值有:original(初始的或请求的),reply(回复的) 例如:ct direction reply
[original|reply] <bytes|packets> <数值>
:连接的流量(可以限制方向),例如ct original bytes gt 1048576
表示请求方向大于1MB
<original|reply> ip <saddr|daddr> <ip地址>
:某个连接方向的源/目标IP
count [over] <连接数>
:连接数, 例如nft add rule ip tb_pi ch_in tcp dport 22 ip saddr 192.168.1.98 ct state new ct count gt 2 counter reject
表示从192.168.1.98发起的ssh连接数超过2就拒绝,over表示大于(测试发现不能用gt,估计是ct内部不支持nft常用比较运算符,省略over表示等于某值)
[original | reply] l3proto <协议>
# netfilter协议(ipv4/ipv6). 例如ct original l3proto ipv4
[original | reply] protocol <协议>
:协议(icmp/udp/tcp), 例如ct original protocol 6
或ct original protocol tcp
,ct original protocol icmp
mark
: 连接标记 -
meta:
<iifname|oifname> <网口名>
: 数据进来、出去的网络接口名,例如meta iifname eth0
、meta oifname '{eth0,wlan0}'
、meta 'eth*'
(支持通配符匹配,这里有单引号防止shell展开)
<iif|oif> <网口序号>
: 数据进来或出去的网络接口序号,例如meta oif != eth0
。注意和iifname、oifname区分,iif/oif是索引,规则添加完后,如果接口重命名了,规则仍有效,iifname/oifname则不然。
<iiftype|oiftype> <网口类型>
: 网口类型,例如meta iiftype ppp
、meta oiftype ether
length <数值>
:包长度,注意和ct的ip/ip6的length区分,ct的是关于连接的,meta是关于包的。例子:meta length gt 1000
protocol <协议>
:协议(ip、ip6、arp、vlan)
nfproto <协议>
:协议(ipv4,ipv6)
l4proto <协议>
:协议(tcp、udp、icmp等) 。例如meta l4proto '{tcp, udp}'
# 同时匹配udp/tcp协议
mark [set]
: 数据包标记,注意和ct的mark区分
pkttype <广播类型>
:包的广播类型,broadcast、 unicast、 multicast -
原始载荷表达式(RAW PAYLOAD)
-
语法:
@基址,偏移量,长度
-
原始载荷表达式用于从指定偏移量加载指定长度的比特数据。位0对应数据包的第一个比特
-
支持的载荷基址如下:
基址 描述 ll 链路层(如以太网头) nh 网络层(如IPv4/IPv6头) th 传输层(如TCP头) ih 内部载荷(传输层头之后的载荷) -
示例:
nft add rule inet tb1 ch0 meta l4proto {tcp, udp} @th,16,16 { 53, 80 } counter
# 表示匹配tcp、udp协议的数据包,数据包参考th基址偏移16位,读取16位长度的数据(从tcp/udp数据包结构可知,偏移16位读取16位长度的数据是指tcp/udp的目标端口号)。 所以示例的含义是,匹配tcp和udp数据包,且目标端口号是53或80的数据包,执行流量统计。
-
-
其他表达式:比如扩展头表达式、数值生成器(结合映射做负载均衡)、哈希表达式(结合映射做负载均衡)等表达式这里不详述。如果需要,自己查看man手册。
-
比较运算符
eq或== :等于 ne或!= :不等于 lt或< : 小于 gt或> : 大于 le或<= :小于等于 ge或>= : 大于等于 例子: `nft add rule inet tb0 ch0 iif wlan0 tcp dport ge 1024 drop` # tcp端口大于等于1024的流入数据丢弃。`ge 1024`也可以写成 `'>=' 1024`,因为>在shell中是重定向的意思,所以要用单引号包含
动作
-
动作分终止性动作(立即终止规则处理)和非终止性动作(继续处理后续规则)。终止性动作有accept、drop、queue、continue、return、jump、goto等, 非终止性动作有log、counter等。
动作名 描述 accept 接受数据包并停止后续规则评估 drop 丢弃数据包并停止后续规则评估 queue 将数据包排队至用户空间并停止后续规则评估 continue 继续使用下一条规则评估规则集 return 从当前链返回,并在最后一个链的下一条规则继续执行(在基础链中等同于accept) jump <链> 跳转至<链>的第一条规则继续执行,在收到返回语句后将执行下一条规则 goto <链> 与jump类似,但评估完新链后会继续在最后一个链执行,而非包含goto语句的链 log 写日志, 比如动作 log prefix ExtSSH
, 则可以用journalctl -k -g 'ExtSSH'查看日志reject 类似drop但是返回拒绝数据包,reject后面可以不带任何类型,如果带的话,可以是: reject with tcp reset
。或`reject with <icmpcounter 统计流量 snat、dnat、redirect、tproxy 源地址转换、目标地址转换、端口重定向、透明代理 其他动作 比如数据包头修改(payload表达式 set value)、包元信息(meta ... set)修改、连接元信息修改(ct {mark | label | zone} set)、数据包复制到其他网口或ip地址(dup to动作 镜像数据包到远程主机方便分析)、转发数据包到其他网口或ip地址(fwd to动作,类似dup,但是数据没有副本,直接转发)、负载均衡、add或update集合或映射... -
关于上面表格中的其他动作中的add或update集合或映射
语法:{add | update} @集合名或映射名 { 匹配表达式[timeout timeout] [comment string] }
# 匹配表达是类似于ip saddr(集合)或 ip saddr:tcp dport(映射)
其他数据结构:范围、集合、映射等
范围
例如 ip daddr 192.168.0.1-192.168.0.250
tcp dport 1-1024
*
集合(sets)
- 匿名集合:用逗号分开的值,例如
tcp dport '{ 22、80、443 }'
. 匿名集合的缺陷是,如果要更改集合,则需要替换规则。对于动态解决方案,使用命名集合。 - 命名集合是一个列表或一组元素,您可以在表中的多个规则中引用。命名集合的另外一个好处在于,您可以更新命名的集合而不必替换使用集合的规则。
- 命名集合的元素类型可以是:ipv4地址(ipv4_addr)、ipv6地址(ipv6_addr)、mac地址(ether_addr)、传输层协议(inet_proto)比如tcp/udp/icmp,互联网服务(inet_service)比如ssh/http。数据包标记(mark)
- 命名集合的语法规则:
add set [地址族] <所属表名> set { type 元素类型 | typeof 表达式 ; [flags 标志 ;] [timeout 超时;] [gc-interval gc间隔;] [elements = { 元素[, ...] } ;] [size 大小 ;] [comment 备注 ;] [policy 策略;] [auto-merge ;] }
# 添加集合。
· type: 集合元素类型,可选值ipv4_addr/ipv6_addr/ether_addr/inet_proto/inet_service/mark 也可以用typeof 表达式推导得出(不知道是什么类型的时候,用typeof很好用 ),比如typeof iifname、type of ct direction
· flags: 集合标志,可选值:constant、dynamic、interval、timeout等。constant表示如果该集合被规则引用了,那么就无法增删集合的元素,dynamic与constant相反,我测试发现,不指定dynamic也能动态的增删集合元素。
· timeout: 超时,单位是d、h、m、s(天、时、分、秒),timeout是元素在集合中停留的时间,比如timeout=30s,则元素被添加进集合30秒后自动删除,当元素是通过规则动态添加进集合时,该选项必选。
· gc-interval: gc间隔,垃圾回收间隔,单位是d、h、m、s, 当 timeout或flags timeout指定是,该选项必选。
· elements: 新创建的集合的初始元素
· size:集合的容量,当元素是动态从规则集中添加的时候,该选项必选。
· policy :可选值performance(预设值)、memory。
· counter :针对每个元素做流量统计,比如 nft add set inet tb1 set0 '{type ipv4_addr; counter}' 则nft list ruleset的时候,能看到每个ipv4地址的流量
· auto-merge:自动合并区间,比如192.168.1.1-192.168.1.30与192.168.1.25-192.168.1.50,会自动合并成192.168.1.1-192.168.1.50
{delete | list | flush} set [family] <表名> <集合名>
# 删除集合、列出集合元素、清除集合元素
list sets [地址族]
# 列出所有集合
delete set [地址族] <所属表名> handle <句柄号>
# 通过句柄号删除集合
{add | delete} element [地址族] <所属表名> <集合名> { 元素[, ...] }
# 给集合添加或删除元素 - 命名集合示例
nft add table inet tb1
# 添加表,地址族是inet
nft add set inet tb1 set-v4-addr '{type ipv4_addr; }'
# 在表下面添加一个元素类型是ipv4地址的命名集合,初始元素
nft add element inet tb1 set-v4-addr '{ 192.168.1.10, 192.168.1.17, 192.168.1.26 }'
# 向刚创建的命名集合里添加元素
nft add chain inet tb1 ch0 '{type filter hook input priority filter; }'
# 在表下添加一条链,以便此链可以引用上面的命名集合
nft add rule inet tb1 ch0 meta l4proto tcp ip saddr @set-v4-addr counter
# 在链里面引用命名集合,源ip地址是集合中的3个ipv4地址时,做tcp流量统计
nft add rule inet tb1 ch0 ip daddr 192.168.1.240 tcp dport 80 add set ip saddr @set-v4-addr
# 凡是访问了192.168.1.240的http服务的主机,都动态的加入到set-v4-addr命名集合中(语句格式:add set <元素> @集合名)
nft delete element inet tb1 set-v4-addr '{192.168.1.10, 192.168.1.17}'
# 从命名集合中删除两个元素,执行nft list sets
发现元素少了两个
nft delete set inet tb1 set-v4-addr
# 删除命名集合,但是提示Error: Could not process rule: Device or resource busy,因为链引用了该命名集合
nft delete rule inet tb1 ch0 handle 13
# 删除引用set-v4-addr集合的链后,再执行上面的语句就可以成功删除命名集合了。
nft add set inet tb1 set-addr4-interval '{ type ipv4_addr; flags interval; }'
# 添加一个可以装ipv4地址范围的集合,注意这里的flags interval;
nft add element inet tb1 set-addr4-interval '{192.168.2.1-192.168.2.254,192.168.1.0/24}'
# 向该命名集合添加两个元素,两个元素都表示ipv4地址范围。
映射(maps)
-
匿名映射
匿名映射的元素是 { 键:值 } 语句。这里映射元素"键:值",可以有多个,用逗号分开,键和值的类型可以是:ipv4_addr/ipv6_addr/ether_addr/inet_proto/inet_service/mark/等(counter、quota不可以做键类型,但是可以作为值类型)。匿名映射的缺点是,如果要修改映射,则必须替换该匿名映射所属的规则(具体例子请参考后面的示例)。 -
命名映射
- 命名映射的语法规则
- 映射操作语法规则
add map [地址族] <所属表名> <映射名> { type 元素类型 | typeof 表达式 [flags 标记 ;] [elements = { 元素[, ...] } ;] [size 映射容量;] [comment 备注;] [policy 策略;] }
# 添加集合。
· type : 映射的元素数据类型(键类型),可选值:ipv4_addr/ipv6_addr/ether_addr/inet_proto/inet_service/mark/counter/quota等,也可以用typeof 表达式推导。
· flags : 映射标记, 可选值:constant/interval。
· elements: 映射初始元素(键:值对)
· size : 映射最大容量
· policy :映射策略,可选值performance(默认)/memory
{delete | list | flush} map [地址族] <所属表名> <映射名>
# 删除、列出、清除映射
list maps [地址族]
# 列出所有映射,可以限定地址族
- 映射操作语法规则
- 命名映射的示例
- 命名映射的语法规则
-
判决映射(vmaps/verdict maps)**
- 概念: 判决映射我个人的理解是一种{ 匹配表达式 : 动作 } 的特殊映射,也就是说这种映射的键和常规映射的键的类型是一样的,值的类型是verdict。(verdict表示各种动作,比如accept、drop、queue、continue、return、jump、goto)。判决映射也分匿名判决映射和命名判决映射。
-
映射的示例(判决映射、映射)
nft create table inet tb1; nft create chain inet tb1 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是inet
nft add chain inet tb1 ch-counter
# 添加一条名为ch-counter的常规链,作为跳转目标链,注意常规链是不带'{ type xxx hook xxxx priority xxx; policy xxx }'的。
nft add rule inet tb1 ch-counter tcp dport 443 counter
# 给ch-counter的常规链添加一条规则,用于统计请求443端口的流量
nft add rule inet tb1 ch0 ip saddr vmap '{ 192.168.1.12:drop, 192.168.2.0/24:accept, 192.168.3.0/24: jump ch-counter}'
# 添加一条规则,使用匿名判决映射,注意vmap
关键字,注意ip saddr vmap
表示ip saddr对应到匿名映射中的三个成员, 如果ip saddr是192.168.1.12则drop,如果ip saddr是192.168.2.0/24则accept, 如果ip saddr是192.168.3.0/24,则跳转到ch-counter链。
nft add map inet tb1 vmap0 '{type ipv4_addr:verdict;}'
# 在tb1表下新增一个命名判决映射vmap0,映射的类型是。ipv4地址映射到verdict。
nft add element inet tb1 vmap0 '{192.168.33.11:accept, 192.168.33.22:drop, 192.168.33.33:jump ch-counter}'
# 给命名判决映射添加元素
nft add rule inet tb1 ch0 ip saddr @vmap0
# 在规则中引用命名判决映射vmap0
nft add chain inet tb1 ch-snat '{type nat hook postrouting priority srcnat; }'
# 添加一条SNAT链,用于下面映射举例
nft add map inet tb1 map-snat '{type ipv4_addr:ipv4_addr; flags interval; }'
# 添加一个名为map-snat的映射,映射元素的键和值类型都是ipv4地址,flags是范围,亦即:ipv4地址范围映射到ipv4地址
nft add element inet tb1 map-snat '{ 192.168.1.0/24:192.168.3.55, 192.168.2.0/24:192.168.3.55 }'
# 给映射添加两个元素
nft add rule inet tb1 ch-snat snat ip to ip saddr map @map-snat
# 添加一条snat的规则引用映射, 注意,因为tb1是inet地址族,所以snat to之间要指明是ip还是ip6地址族。这里是数据包的源ip地址映射到地址转换后的源ip地址。 -
集合或映射的元素操作语法规则
{add | create | delete | get } element [地址族] <所属表名> <集合名> { 元素[,...] } # 元素相关命令允许修改命名集合(sets)和映射(maps)的内容。 元素 := 键表达式 选项 [: 值表达式 ] 选项 := [timeout 时间定义 ] [expires 时间定义 ] [comment 字符串] 时间定义 := [Xd][Xh][Xm][X[s]] # X表示数字,d、h、m、s表示天、时、分、秒 键表达式(key_expression)通常是与集合类型匹配的值。值表达式(value_expression)在集合中不允许使用,但在向映射添加元素时是必需的(需匹配其类型定 义中的数据部分)。当从映射中删除元素时,值表达式可以指定但非必需(因为键表达式能唯一标识元素)。create命令与add命令类似,区别在于列出的所有元素必须 都不存在(即不能重复添加已存在的元素)。get命令可用于检查元素是否包含在集合中,对于非常大或区间类型的集合(interval sets),这种检查可能并不简单。 对于区间集合,get命令会返回包含该元素的区间,而不仅仅是元素本身。 timeout:带有超时标志(timeout)的集合/映射的超时值 expires: 给定元素的过期时间,仅用于规则集复制(ruleset replication) comment: 每个元素的注释字段
连接
语法格式:匹配表达式.匹配表达式
, 可以连接多个,连接可以作为集合的元素、映射元素的键或值等,直接上例子解释:
nft create table inet tb1; nft add chain inet tb1 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是inet
nft add set inet tb1 set-a2p '{ type ipv4_addr . inet_service; }'
# 新建一个集合,集合的元素类型是"ipv4地址 . 端口号"的连接,务必注意点号前后都有空格。
nft add element inet tb1 set-a2p '{192.168.22.10 . 80, 192.168.22.11 . 443}'
# 给集合添加两个元素。
nft add rule inet tb1 ch0 ip saddr . tcp dport @set-a2p counter
# 新建规则,引用集合。目的是统计源ip是192.168.22.10目标端口号是80端口、源ip是192.168.22.11目标端口号是443端口的网络流量
nft add map inet tb1 map-a2p '{type ipv4_addr . inet_service : verdict;}'
# 增加一个映射,"ipv4地址 . 端口号"作为映射的键,动作作为值
nft add element inet tb1 map-a2p '{192.168.22.66 . 80:accept, 192.168.22.77 . 22:drop}'
# 添加映射元素
nft add rule inet tb1 ch0 ip saddr . tcp dport @map-a2p
#引用上面的映射(源ip地址是192.168.22.66,tcp目标端口是80就执行accept;源ip地址是192.168.22.77,tcp目标端口是22就执行drop)
*
状态对象(stateful object)
-
状态对象是表下面的,他们能聚合规则的状态信息,通过
类型 name 名字引用
,例如counter name "cnt0"。 状态对象主要有:counter(流量计数器)、quota(配额)、limit(限速)等 -
语法规则如下:
{add | delete | list | reset} 状态对象 [地址族] 所属表名 对象名 [{ 状态对象的选项; [comment 备注;]}] # 新建/删除/列出/重置 状态对象。这里状态对象的选项随状态对象的种类不同而不同,比如counter的选项是 "packets 数值 bytes 数值 ;" quota的选项是" [over|until] 数值 流量单位 [ used 数值 流量单位 ] "(流量单位是bytes、kbytes、mbytes) 。 delete 状态对象 [地址族] 所属表名 handle handle # 用句柄号删除状态对象 list 状态对象 [地址族] #列出状态对象 reset 状态对象s [地址族] # 重置所有状态对象,注意复数s,比如重置所有inet地址族的counter,则使用nft reset counters inet reset 状态对象s [地址族] table 所属表名 #重置某表下的所有状态对象,注意复数s
-
示例
nft create table inet tb1; nft create chain inet tb1 ch0 '{type filter hook input priority filter; }'
# 添加表和链,地址族是inet
nft add counter inet tb1 cnt-web-down '{comment '\"统计http、https下行流量\"';}'
# 新建一个counter,统计流量
nft add rule inet tb1 ch0 iif wlan0 tcp sport '{80,443}' counter name "cnt-web-down"
# 统计从wlan0进入且tcp源端口是80或443的流量。
nft add counter inet tb1 cnt-80-down; nft add counter inet tb1 cnt-443-down
# 建立两个名为"cnt-80-down"和"cnt-443-down"流量计数器
nft add rule inet tb1 ch0 iif wlan0 counter name tcp sport map'{80:"cnt-80-down",443:"cnt-443-down"}'
# 创建一条规则,结合匿名映射,分别统计http和https的流量
nft add limit inet tb1 lmt-web-down rate over 100 kbytes/second
# 添加一个限速100KBytes/秒的limit对象
nft add rule inet tb1 ch0 iif wlan0 tcp sport '{80,443}' limit name "lmt-web-down" drop
# 引用该限速器,因为规则所属的链的预设动作是accept,所以这里超过100kB/s的时候,要drop。 wget测试网速生效
nft add quota inet tb1 quo-web-down over 100 mbytes
# 添加一个配额100MB
nft add rule inet tb1 ch0 iif wlan0 tcp sport '{80,443}' quota name "quo-web-down" drop
# 添加一条规则应用配额,下载超过100MB就drop,wget测试发现,下载到100MB的时候,下载停止了。执行nft reset quotas,刚才的配额又归零了。
流表(flowtable)
- 流表用于加速数据包转发,通过元组(输入接口、源/目的地址、源/目的端口、协议)缓存转发信息。
- 语法:
{add | create} flowtable [地址族] 所属表名 流表名 { hook 钩子 priority 优先级 ; devices = { 设备,... } ; }
#添加流表
list flowtables [地址族]
# 列出所有流表
{delete | list} flowtable [地址族] 所属表名 流表名
# 删除或列出某表下的流表
delete flowtable [地址族] 所属表名 handle 句柄号
# 通过句柄号删除流表- 流表参数:
优先级支持整数或标准名称(如filter表示0),支持算术运算(如filter+5表示5)
地址族支持ip/ip6/inet(inet为IPv4/IPv6混合表)
- 流表参数:
- 示例:
nft add flowtable inet tb1 ftb0 '{hook ingress priority filter; devices = {wlan0,eth0};}'
# 添加一个inet地址族的流表
nftables脚本
有三种形式:
-
shell形式
bash#!/bin/bash nft flush ruleset nft add table ip tb0 '{ comment '\"测试表\"'; }' nft add chain ip tb0 ch0 '{ type filter hook input priority filter; comment '\"测试链\"';}' nft add rule ip tb0 ch0 iifname eth0 tcp dport 22 drop comment '"测试规则"'
上面是/tmp/nft.sh的内容,准确说这是shell脚本,不是nft脚本,执行方法有两种:1、直接执行
bash /tmp/nft.sh
。2、增加可执行权限chmod a+x /tmp/nft.sh
再执行:/tmp/nft.sh
-
与nft命令基本相同的形式
bash#!/usr/sbin/nft -f flush ruleset add table ip tb0 { comment "测试表"; } add chain ip tb0 ch0 { type filter hook input priority filter; comment "测试链";} add rule ip tb0 ch0 iifname eth0 tcp dport 22 drop comment "测试规则"
上面是/tmp/a.nft的内容,注意和shell版本比较:首行的解释器、引号和转义、每行首少了nft命令。执行方式有两种:1、赋x权限
chmod a+x /tmp/a.nft
,执行/tmp/a.nft
2、直接用解释器导入nft -f /tmp/a.nft
-
与nft list ruleset显示结果基本相同的形式
bash#!/usr/sbin/nft -f flush ruleset table ip tb0 { comment "测试表" chain ch0 { comment "测试链" type filter hook input priority filter; policy accept; iifname "eth0" tcp dport 22 drop comment "测试规则" } }
执行方法有两种:1、赋x权限
chmod a+x /tmp/a.nft
,再执行/tmp/a.nft
2、直接用解释器导入nft -f /tmp/a.nft
-
其他知识点:比如脚本定义变量、脚本中导入外部脚本(include "test.nft")等知识点,这里不详述,可以参看man nftables
-
开机启动nftables服务
systemctl enable nftables
#这样每次启动的时候,nftables服务会载入/etc/nftables.conf配置文件,配置文件的格式建议使用上面第3种形式。