nftables精讲(有NAT、限速、限流量、禁ping等例子)

目录

前言

基本概念

  • 层级关系:分表、链、规则,表可以装多条链,链可以装多条规则。

  • 地址族类型 :一共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语法

    bash 复制代码
    snat [[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.22ip saddr 192.168.1.0/24ip 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 6ct original protocol tcp, ct original protocol icmp
    mark : 连接标记

  • meta:
    <iifname|oifname> <网口名> : 数据进来、出去的网络接口名,例如meta iifname eth0meta oifname '{eth0,wlan0}'meta 'eth*' (支持通配符匹配,这里有单引号防止shell展开)
    <iif|oif> <网口序号>: 数据进来或出去的网络接口序号,例如meta oif != eth0 。注意和iifname、oifname区分,iif/oif是索引,规则添加完后,如果接口重命名了,规则仍有效,iifname/oifname则不然。
    <iiftype|oiftype> <网口类型> : 网口类型,例如meta iiftype pppmeta 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 <icmp
    counter 统计流量
    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种形式。

相关推荐
牛奶咖啡131 个月前
Linux的软件防火墙iptables
iptables·linux防火墙·linux云计算·防火墙的规则和执行流程·防火墙的设置规则与常用过滤规则·防火墙针对数据状态的过滤设置·iptables常用命令清单
恩爸编程9 个月前
Linux 防火墙:守护系统安全的坚固防线
linux·运维·系统安全·linux防火墙·linux防火墙是什么·linux关闭防火墙·linux查看防火墙状态
NFT_Research10 个月前
NFTScan | 11.18~11.24 NFT 市场热点汇总
web3·区块链·nft
花花花11 年前
如何使用智能合约铸造 NFT —— 以 NftMarket 合约为例
去中心化·区块链·智能合约·以太坊·nft·sepolia
花花花11 年前
使用Pinata在IPFS上存储NFT图片的实践
web3·去中心化·区块链·nft·分布式存储·ipfs
NFT_Research1 年前
NFTScan | 07.22~07.28 NFT 市场热点汇总
web3·区块链·nft
cui_win1 年前
Nginx-http_limit_req_module模块
运维·nginx·http·限速·优化·nginx配置
Dancen1 年前
Win10/11系统针对ftp的限速问题
win10·限速·ftp·带宽·qos·网速
李小白杂货铺1 年前
百度网盘限速解决办法
限速·p2p·百度网盘·解决办法·百度网盘青春版