nftables用法介绍

2024-01-31 08:29:17 浏览数 (2)

Kubernetes 1.29版本中已经将nftables作为一个featureGates,本文简单整理了nftables的用法,便于后续理解kubernetes的nftables规则。文末给出了使用kubeadm部署启用nftables featureGates的配置文件。

如下内容来源nftables的man文档以及wiki。

nftables和iptables的不同之处

  1. nftables使用了新的语法:nftables使用了类似tcpdump的紧凑语法
  2. 可以完全配置tables和chains:iptables中有一些预定义的tables和chains(即使不需要),会对性能造成一定的影响。而nftables则没有预定义的tables和chains,因此需要明确定义各个table,以及其包含的对象(chains、sets、maps、flowtables和state object)。你可以定义table和chain的名称以及netfilter hook优先级。
  3. 单个nftables可以执行多个动作:iptables中通过匹配只能执行单个动作,但在nftables 规则中可以包含0或多个expressions(用于匹配报文),以及1或多个statements,每个expression会测试一个报文是否匹配特定的payload字段或报文/流的元数据。多个expressions会从左到右作线性评估,如果第一个expression匹配成功,则继续评估下一个expression。如果匹配了所有的expressions,则会执行statement。 每个statement会执行一个动作,如设置netfilter mark、计算报文数、记录报文日志或做出诸如接收或丢弃报文或跳到另一个chain的决定。多个statements也是从左到右线性执行的,这样一条规则可以通过多个statements执行多个动作(需要注意的是verdict statement 会终结规则)。
  4. chain和rule中没有内置的计数器:nftables中的计数器是可选的,可以在需要时启用
  5. 可以更好地支持动态规则更新:iptables的规则是一体式的,一条规则的变更会影响到整体规则。而nftables的ruleset在内部以链表表示。现在,添加或删除一条规则不会影响道其他规则,从而简化了内部状态信息的维护。
  6. 简化了IPv4/IPv6双栈管理: nftables的inet family 可以同时支持IPv4和IPv6的chain。不再需要脚本来复制规则
  7. 新的通用型基础结构:该基础结构与nftables的核心紧密集成,并支持高级配置,如maps, verdict maps 和 intervals ,以实现面向性能的报文分类。
  8. 支持串联(Concatenations):从Linux kernel 4.1开始,可以串联多个keys,并将它们与maps 和 verdict maps相结合。其思想是构建一个元组,并该元组的值进行哈希以获得接近O(1)的查找效率。
  9. 无需升级内核即可支持新协议:内核升级一项耗时且艰巨的任务,尤其是当你必须在网络中维护多个防火墙时。分发的内核版本通常滞后于最新版本。当虚拟机使用nftables时,支持新协议通常不需要更新内核,只需要更新用户空间的nft即可。

nftables的结构

跟iptables一样,nftables也使用了table->chain->rule的概念。并使用family的概念区分了报文类型。

ADDRESS FAMILIES

Address families决定了处理的报文类型,默认使用ip

ip

IPv4 address family.

ip6

IPv6 address family.

inet

Internet (IPv4/IPv6) address family.

arp

ARP address family, handling IPv4 ARP packets.

bridge

Bridge address family, handling packets which traverse a bridge device.

netdev

Netdev address family, handling packets on ingress and egress.

内核在报文处理路径的不同阶段设置了hooks,不同的address family对应各自的hooks。如 **IPv4/IPv6/ **的hooks如下:

Hook

Description

prerouting

所有报文进入系统前都会经过prerouting hook的处理。在执行路由前调用,早期过滤或通过变更报文属性来影响路由结果。

input

在报文进入本地系统时会经过input hook 的处理

forward

在将报文转发到不同的主机时会经过forward hook的处理

output

本地进程发送的报文会经过outpu hook的处理

postrouting

所有离开系统的报文都会经过 postrouting hook的处理

ingress

所有进入系统的报文都会经过该hook的处理。它会在3层协议处理前被调用,因此它更早于prerouting hook,,可以用于过滤和策略处理。Ingress只针对Inet family (Linux kernel 5.10以上)生效。

特殊语法

连接符

nft中使用.表示连接符,如下面表示1.1.1.1 and 2.2.2.2 and TCP1.1.1.1 and 3.3.3.3 and UDP

代码语言:javascript复制
nft add rule ip filter input ip saddr . ip daddr . ip protocol { 1.1.1.1 . 2.2.2.2 . tcp, 1.1.1.1 . 3.3.3.3 . udp} counter accept
Intervals

可以使用value-value的方式表示Intervals,一个Intervals可以视作一个参数

代码语言:javascript复制
% nft add rule filter input ip daddr 192.168.0.1-192.168.0.250 drop

通用参数

位置参数handle

nftables中使用handle来表示位置ID,用于添加或删除表项,对应命令中的handle参数。可以通过-a参数输出handle信息。

代码语言:javascript复制
$ nft -a list ruleset
注释comment

注释为一个单词或双引号中的多个单词。在hash中,需要使用斜杠来转移引号,如"enable ssh for servers"

DATA TYPES

DATA TYPES为Expression提供了数据类型定义。

RULESET
  • {list | flush} ruleset [family]

注意黑体字是关键字段,斜体为自定义字段,带[]的表示有默认值,为可选字段

ruleset表示所有的tables、chains等。

Example
  • nft list ruleset: 显示所有的nftables规则,对应iptables的iptables-save命令.
    • 还可以指定family类型,如nft list ruleset inet
  • nft flush ruleset: 清空所有的nftables规则
备份和恢复

备份

代码语言:javascript复制
 % echo "flush ruleset" > backup.nft
 % nft list ruleset >> backup.nft

恢复

代码语言:javascript复制
 % nft -f backup.nft
TABLES

{add | create} table [family] table [ {comment comment ;} { flags 'flags ; }] {delete | destroy | list | flush} table [family] table list tables [family] delete table [family] handle handle destroy table [family] handle handle

tables包含chains、sets和stateful objects,并使用address family和名称进行区分,address family必须是ip, ip6, inet, arp, bridge, netdevinet用于创建 IPv4/IPv6混合tables,如果不指定,默认为ipaddcreate的区别是,前者不会在table存在的情况下返回错误,而后者会。deletedestroy的区别也是如此,delete会在table不存在的情况下返货错误,而destroy则不会。

Example
代码语言:javascript复制
$ nft add table inet my_table
CHAINS
  • {add | create} chain [family] table chain [{ type type hook hook [device device] priority priority ; [policy policy ;] [comment comment ;] }]
  • {delete | destroy | list | flush} chain ['family] table chain
  • list chains [family]
  • delete chain [family] table handle handle
  • destroy chain [family] table handle handle
  • rename chain [family] table chain newname

chains中包含了规则(rules)。chains有两种:base chainsregular chainsbase chains为来自网络栈的报文的入口,regular chain用于跳转和组织规则。

对于base chain需要指定 type, hookpriority参数:

Type

Families

Hooks

Description

filter

all

all

Standard chain type to use in doubt.

nat

ip, ip6, inet

prerouting, input, output, postrouting

该类型的Chains可以根据conntrack表项执行NAT地址转换。一条连接中,只有第一个报文会经过该chain,其包含的rules定义了创建conntrack表项的详细信息。

route

ip, ip6

output

如果报文已遍历了该类型的chain并将被接受,则如果IP首部的相关字段发送了更改,则将执行新的路由查找。这可以在Nftables中实现策略路由选择器。

priority

priority参数接收一个有符号整数或一个标准优先级名称,其指定了具有相同hook下的chain的执行顺序。数值越小优先级越高。

对于nat类型的chain,其优先级的下限为-200。

标准优先级名称、family和hook兼容性矩阵图:

Name

Value

Families

Hooks

raw

-300

ip, ip6, inet

all

mangle

-150

ip, ip6, inet

all

dstnat

-100

ip, ip6, inet

prerouting

filter

0

ip, ip6, inet, arp, netdev

all

security

50

ip, ip6, inet

all

srcnat

100

ip, ip6, inet

postrouting

可以使用基本的算术表达式(加法或减法)结合标准优先级名称来实现相对优先级,如 mangle - 5 表示-155。在使用list等命令显示时也使用这种表达方式。

bridge family的标准优先级名称和hook兼容性:

Name

Value

Hooks

dstnat

-300

prerouting

filter

-200

all

out

100

output

srcnat

300

postrouting

policy

定义接收还是拒绝匹配到chain规则的报文,可选值为accept(默认值)或drop

Example
代码语言:javascript复制
$ nft add chain inet mytable myin { type filter hook input priority 1; policy accept;}

下面展示了优先级和策略的用法:

代码语言:javascript复制
table inet filter {
    # This chain is evaluated first due to priority
    chain services {
        type filter hook input priority 0; policy accept;

        # If matched, this rule will prevent any further evaluation
        tcp dport http drop

        # If matched, and despite the accept verdict, the packet proceeds to enter the chain below
        tcp dport ssh accept

        # Likewise for any packets that get this far and hit the default policy
    }

    # This chain is evaluated last due to priority
    chain input {
        type filter hook input priority 1; policy drop;
        # All ingress packets end up being dropped here!
    }
}
RULES
  • {add | insert} rule [family] table chain [handle handle | index index] statement ... [comment comment]
  • replace rule [family] table chain handle handle statement ... [comment comment]
  • {delete | reset} rule [family] table chain handle handle
  • destroy rule [family] table chain handle handle
  • reset rules [family] [table [chain]]

规则中,如果没有指定family,则默认使用ip。规则包含两部分:expression和statement

addinset都用于添加规则,前者用于将规则追加到给定的chain中(或给定的位置handle之后),后者用于将规则插入到给定的chain的开头(给给定的位置handle的前面)

Example
代码语言:javascript复制
$ nft add rule ip filter output ip daddr 192.168.0.0/24 accept
代码语言:javascript复制
# nft -a list ruleset
table inet filter {
        chain input {
                type filter hook input priority filter; policy accept;
                ct state established,related accept # handle 4
                ip saddr 10.1.1.1 tcp dport ssh accept # handle 5
          ...
# delete the rule with handle 5
nft delete rule inet filter input handle 5
SETS
  • add set [family] table set { type type | typeof expression ; [flags flags ;] [timeout timeout ;] [gc-interval gc-interval ;] [elements = { element[, ...] } ;] [size size ;] [comment comment ;] [policy 'policy ;] [auto-merge ;] }
  • {delete | destroy | list | flush} set [family] table set
  • list sets [family]
  • delete set [family] table handle handle
  • {add | delete | destroy } element [family] table set { element[, ...] }

Keyword

Description

Type

type

定义集合元素的数据类型

支持字符串: ipv4_addr, ipv6_addr, ether_addr, inet_proto, inet_service, mark

typeof

定义集合元素的数据类型

从Expression派生出的数据类型

flags

集合标识

字符串: constant, dynamic, interval, timeout. 用于描述集合属性

timeout

元素停留在集合中的时间,如果集合来自报文路径(ruleset),则为强制字段

字符串, 十进制加单位表示. 单位为: d, h, m, s

gc-interval

GC间隔,在指定timeout或flag timeout时生效

字符串, 十进制加单位表示. 单位为: d, h, m, s

elements

定义集合中的元素

集合的数据类型

size

集合的最大元素数目,如果集合来自报文路径(ruleset),则为强制字段

unsigned integer (64 bit)

policy

集合策略

字符串: performance [默认], memory

auto-merge

自动连接或合并集合元素(仅适用于interval集合)

nftables提供了两种集合的概念:匿名集合和命名集合。

Example

下面给出了set的基本用法:

代码语言:javascript复制
$ nft add set ip filter flags_set {type ipv4_addr; flags constant, interval;}

$ nft add set ip filter daddrs {type ipv4_addr ; flags timeout ; elements={192.168.1.1 timeout 10s, 192.168.1.2 timeout 30s} ;}
匿名集合

匿名集合使用大括号表示,内部使用逗号划分元素,一旦规则被移除,该集合也会被移除,且集合中的内容不可变(除非删除再添加)。匿名集合不需要定义元素类型。下面几个使用匿名集合的例子。

代码语言:javascript复制
$ nft add rule filter input ip saddr { 10.0.0.0/8, 192.168.0.0/16 } tcp dport { 22, 443 } accept
$ nft add rule ip6 filter input tcp dport {telnet, http, https} accept
$ nft add rule ip6 filter input icmpv6 type { nd-neighbor-solicit, echo-request, nd-router-advert, nd-neighbor-advert } accept
命名集合

命名集合可以在任意时间添加或移除元素,通过在名称前面使用@前缀来引用命名集合。命名集合需要定义元素的类型,然后再给出元素的值。

代码语言:javascript复制
$ nft add set ip filter blackhole { type ipv4_addr; comment "drop all packets from these hosts" ; }
$ nft add element ip filter blackhole { 192.168.3.4 } #添加元素
$ nft add element ip filter blackhole { 192.168.1.4, 192.168.1.5 } #添加元素
代码语言:javascript复制
$ nft add rule ip filter input ip saddr @blackhole drop #引用命名集合
$ nft add rule ip filter output ip daddr != @blackhole accept
MAPS
  • add map [family] table map { type type | typeof expression [flags flags ;] [elements = { element[, ...] } ;] [size size ;] [comment comment ;] [policy 'policy ;] }
  • {delete | destroy | list | flush} map [family] table map
  • list maps [family]

Keyword

Description

Type

type

定义Map元素的数据类型

字符串: ipv4_addr, ipv6_addr, ether_addr, inet_proto, inet_service, mark, counter, quota. 不能使用Counter 和 quota 作为keys

typeof

定义Map元素的数据类型

从表示式派生出的数据类型

flags

map 标识

string, same as set flags

elements

定义map中的元素

map数据类型

size

Map的最大元素数目

unsigned integer (64 bit)

policy

map 策略

字符串: performance [默认], memory

匿名map

如果端口是80,则DNAT到192.168.1.100,如果端口是8888,则DNAT到192.168.1.101

代码语言:javascript复制
$ nft add rule ip nat prerouting dnat to tcp dport map { 80 : 192.168.1.100, 8888 : 192.168.1.101 }
命名map
代码语言:javascript复制
$ nft add map nat porttoip  { type inet_service: ipv4_addr; }
$ nft add element nat porttoip { 80 : 192.168.1.100, 8888 : 192.168.1.101 }

TCP/80且源地址为192.168.1.100的出站报文会被SNAT为TCP/8888,地址为192.168.1.101

代码语言:javascript复制
$ nft add rule ip nat postrouting snat to tcp dport map @porttoip
ELEMENTS
  • {add | create | delete | destroy | get } element [family] table set { ELEMENT[, ...] } ELEMENT := key_expression OPTIONS [: value_expression] OPTIONS := [timeout TIMESPEC] [expires TIMESPEC] [comment string] TIMESPEC := [numd][numh][numm][num[s]]

Option

Description

timeout

sets/maps flag 中的timeout 值

expires

timeout开始的倒计时计数器,当数值为0时,会删除掉element

comment

单个元素的注释字段

element命令用于修改命名集合和命名maps的内容。key_expression用于集合类型的值,value_expression用于map类型,表示map的数据部分。

Example
代码语言:javascript复制
$ nft add table inet myfilter
$ nft add set inet myfilter myset {type ipv4_addr; flags timeout; }
$ nft add element inet myfilter myset {10.0.0.1 timeout 10s }
FLOWTABLES

Flowtables用于加速报文的转发,它可以绕过传统的转发路径。

代码语言:javascript复制
                                         userspace process
                                          ^              |
                                          |              |
                                     _____|____     ____/___
                                    /             /         
                                    |   input  |   |  output |
                                    __________/   _________/
                                         ^               |
                                         |               |
      _________      __________      ---------     _____/_____
     /             /               |Routing |   /            
  -->  ingress  ---> prerouting ---> |decision|   | postrouting|--> neigh_xmit
     _________/    __________/     ----------   ____________/          ^
       |      ^                          |               ^                |
   flowtable  |                     ____/___            |                |
       |      |                    /                    |                |
    __/___   |                    | forward |------------                |
    |-----|   |                    _________/                            |
    |-----|   |                 'flow offload' rule                       |
    |-----|   |                   adds entry to                           |
    |_____|   |                     flowtable                             |
       |      |                                                           |
      /      |                                                           |
     /hit_no_|                                                           |
      ? /                                                                |
       /                                                                 |
       |__yes_________________fastpath bypass ____________________________|

               Fig.1 Netfilter hooks and flowtable interactions
STATEFUL OBJECTS

可以使用"type name"来引用Statefulset object,如"counter name"、"quota name"、"limit name"

COUNTER
  • add counter [family] table counter_name [{ [ packets packets bytes bytes ; ] [ comment comment ; }]
  • delete counter [family] table counter_name
  • list counters

计数器,统计报文总数和自上次重置以来所接收到的总字节数。需要明确指定统计的每个规则的计数器。

匿名counter

匿名counter只属于其所在的rule。下面匿名counter用于统计所有路由到本地的TCP流量:

代码语言:javascript复制
table ip counter_demo {
    chain IN {
        type filter hook input priority filter; policy drop;

        ip protocol tcp counter
    }
}
命名counter
代码语言:javascript复制
$ nft add counter filter http
$ nft add rule filter input tcp dport 80 counter name "http"

在map中使用counter:

代码语言:javascript复制
$ nft add counter filter http
$ nft add counter filter https
$ nft add rule filter input counter name tcp dport map { 80 : "http", 443 : "https" }

重置counter:

代码语言:javascript复制
$ nft reset counter filter http
Quotas
  • add quota [family] table name { [over|until] bytes BYTE_UNIT [ used bytes BYTE_UNIT ] ; [ comment comment ; ] }
  • BYTE_UNIT := bytes | kbytes | mbytes
  • delete quota [family] table name
  • list quotas

Keyword

Description

Type

quota

配额限制,作为配额名称

两个参数:unsigned integer (64 bit) 和 字符串: bytes, kbytes, mbytes。 在这些参数前面加上"over" 和 "until"

used

一开始使用的配额值

两个参数:unsigned integer (64 bit) 和 字符串: bytes, kbytes, mbytes

comment

每个quota的注释

字符串

配额,用于定义:

  1. 字节数上限
  2. 设置初始的字节数(默认0字节)
  3. 计算从初始字节数开始接收到的总字节数,直到字节数达到或超过上限。
匿名Quota

下面配置要求到端口udp/5060的数据量不超过100 mbytes。

代码语言:javascript复制
table inet anon_quota_demo {
    chain IN {
        type filter hook input priority filter; policy drop;

        udp dport 5060 quota until 100 mbytes accept
    }
}
命名Quota

下面定义了一对quota,并在chain IN中引用了这两个quotas。

  • 到端口udp/5060的数据不超过100 mbytes,并丢弃其余到该端口的报文;
  • 到端口tcp/80的数据不超过500 mbytes,,并丢弃其余到该端口的报文;
  • tcp/443 (https)的报文不设限;
  • 丢弃其余报文(policy drop)
代码语言:javascript复制
table inet quota_demo {
   quota q_until_sip { until 100 mbytes used 0 bytes }
   quota q_over_http { over  500 mbytes ; comment "cap http (but not https)" ; }

   chain IN { 
      type filter hook input priority filter; policy drop;

      udp dport 5060 quota name "q_until_sip" accept
      tcp dport 80 quota name "q_over_http" drop
      tcp dport { 80, 443 } accept
   }
}

下面是在map中引用quota的例子

代码语言:javascript复制
$ nft add quota filter user123 { over 20 mbytes }
$ nft add quota filter user124 { over 20 mbytes }
$ nft add rule filter input quota name ip saddr map { 192.168.10.123 : "user123", 192.168.10.124 : "user124" }
Limits

使用token bucket进行限流。

匿名limits

下面限制每秒最多10个ICMP echo-request报文

代码语言:javascript复制
$ nft add rule filter input icmp type echo-request limit rate 10/second accept
命名limits

下面定义了两个limits,分别为:

  • 接收所有类型的icmp报文,限制最大速率为400 packets / minute.
  • 接收到端口 tcp/25 (smtp)的流量,最大速率为 1024 bytes / second,可接收的最大突发流量为512 bytes
  • 丢弃其他流量(policy drop)
代码语言:javascript复制
table inet limit_demo {

   limit lim_400ppm { rate 400/minute ; comment "use to limit incoming icmp" ; }
   limit lim_1kbps  { rate over 1024 bytes/second burst 512 bytes ; comment "use to limit incoming smtp" ; }

   chain IN { 
      type filter hook input priority filter; policy drop;

      meta l4proto icmp limit name "lim_400ppm" accept
      tcp dport 25 limit name "lim_1kbps" accept
   }
}
Others
  • Conntrack helpers (ct helper, Layer 7 ALG)
  • Conntrack timeout policies (ct timeout)
  • Conntrack expectations (ct expectation)

数据类型

EXPRESSIONS

PRIMARY EXPRESSIONS

expression用于匹配报文。

primary expression是最低阶的表达式,表示来自报文的payload、元数据或有状态模块的常量单个数据

下面仅列出了部分expressions。

META EXPRESSIONS
  • meta {length | nfproto | l4proto | protocol | priority}
  • [meta] {mark | iif | iifname | iiftype | oif | oifname | oiftype | skuid | skgid | nftrace | rtclassid | ibrname | obrname | pkttype | cpu | iifgroup | oifgroup | cgroup | random | ipsec | iifkind | oifkind | time | hour | day }

从上面看出,元数据有很多种,每种元数据都对应特定的类型。这里根据元数据的类型进行了划分。

meta expression指与报文有关的元数据。

meta expressions有两种:unqualified 和 qualified meta expressions,区别就是有没有meta关键字:

代码语言:javascript复制
# qualified meta expression
filter output meta oif eth0
filter forward meta iifkind { "tun", "veth" }

# unqualified meta expression
filter output oif eth0
ROUTING EXPRESSIONS
  • rt [ip | ip6] {classid | nexthop | mtu | ipsec}

Keyword

Description

Type

classid

Routing realm

realm

nexthop

Routing nexthop

ipv4_addr/ipv6_addr

mtu

TCP maximum segment size of route

integer (16 bit)

ipsec

route via ipsec tunnel or transport

boolean

routeing expression指与报文有关的路由数据:

代码语言:javascript复制
# IP family independent rt expression
filter output rt classid 10

# IP family dependent rt expressions
ip filter output rt nexthop 192.168.0.1
ip6 filter output rt nexthop fd00::1
inet filter output rt ip nexthop 192.168.0.1
inet filter output rt ip6 nexthop fd00::1

# outgoing packet will be encapsulated/encrypted by ipsec
filter output rt ipsec exists
NUMGEN EXPRESSION
  • numgen {inc | random} mod NUM [ offset NUM ]

生成一个数字,incrandom 用于控制其操作模式,inc模式下,最后的返回值会简单递增,而random模式下,会返回一个随机数。mod用于执行取模操作,可选的offset可以让返回值加上一个固定的偏移量。

numgen通常用于负载均衡:

代码语言:javascript复制
# round-robin between 192.168.10.100 and 192.168.20.200:
add rule nat prerouting dnat to numgen inc mod 2 map 
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }

# probability-based with odd bias using intervals:
add rule nat prerouting dnat to numgen random mod 10 map 
        { 0-2 : 192.168.10.100, 3-9 : 192.168.20.200 }
HASH EXPRESSIONS
  • jhash {ip saddr | ip6 daddr | tcp dport | udp sport | ether saddr} [. ...] mod NUM [ seed NUM ] [ offset NUM ]
  • symhash mod NUM [ offset NUM ]

使用哈希函数来生成一个数字,可用的哈希函数为jhash(Jenkins hash),symhash(Symmetric Hash)。jhash需要一个expression来确定哈希参数(报文首部),可以串联多个参数。mod用于执行取模操作,可选的seed执行了哈希函数的种子,可选的offset可以让返回值加上一个固定的偏移量。

jhashsymhash的通常用于负载均衡:

代码语言:javascript复制
# load balance based on source ip between 2 ip addresses:
add rule nat prerouting dnat to jhash ip saddr mod 2 map 
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }

# symmetric load balancing between 2 ip addresses:
add rule nat prerouting dnat to symhash mod 2 map 
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }
PAYLOAD EXPRESSIONS

指来自报文payload的数据。下面给出常用报文的expressions。

ETHERNET HEADER EXPRESSION
  • ether {daddr | saddr | type}

Keyword

Description

Type

daddr

Destination MAC address

ether_addr

saddr

Source MAC address

ether_addr

type

EtherType

ether_type

VLAN HEADER EXPRESSION

除非接口配置了reorder_hdr off ,否则无法在 ip, ip6inet family上生效。

  • vlan {id | dei | pcp | type}

Keyword

Description

Type

id

VLAN ID (VID)

integer (12 bit)

dei

Drop Eligible Indicator

integer (1 bit)

pcp

Priority code point

integer (3 bit)

type

EtherType

ether_type

IPV4 HEADER EXPRESSION
  • ip {version | hdrlength | dscp | ecn | length | id | frag-off | ttl | protocol | checksum | saddr | daddr }

Keyword

Description

Type

version

IP header version (4)

integer (4 bit)

hdrlength

IP header length including options

integer (4 bit) FIXME scaling

dscp

Differentiated Services Code Point

dscp

ecn

Explicit Congestion Notification

ecn

length

Total packet length

integer (16 bit)

id

IP ID

integer (16 bit)

frag-off

Fragment offset

integer (16 bit)

ttl

Time to live

integer (8 bit)

protocol

Upper layer protocol

inet_proto

checksum

IP header checksum

integer (16 bit)

saddr

Source address

ipv4_addr

daddr

Destination address

ipv4_addr

ICMP HEADER EXPRESSION
  • icmp {type | code | checksum | id | sequence | gateway | mtu}

当在inetbridgenetdev family中使用时,将导致对IPv4的隐式依赖。

Keyword

Description

Type

type

ICMP type field

icmp_type

code

ICMP code field

integer (8 bit)

checksum

ICMP checksum field

integer (16 bit)

id

ID of echo request/response

integer (16 bit)

sequence

sequence number of echo request/response

integer (16 bit)

gateway

gateway of redirects

integer (32 bit)

mtu

MTU of path MTU discovery

integer (16 bit)

TCP HEADER EXPRESSION
  • tcp {sport | dport | sequence | ackseq | doff | reserved | flags | window | checksum | urgptr}

Keyword

Description

Type

sport

Source port

inet_service

dport

Destination port

inet_service

sequence

Sequence number

integer (32 bit)

ackseq

Acknowledgement number

integer (32 bit)

doff

Data offset

integer (4 bit) FIXME scaling

reserved

Reserved area

integer (4 bit)

flags

TCP flags

tcp_flag

window

Window

integer (16 bit)

checksum

Checksum

integer (16 bit)

urgptr

Urgent pointer

integer (16 bit)

UDP HEADER EXPRESSION
  • udp {sport | dport | length | checksum}

Keyword

Description

Type

sport

Source port

inet_service

dport

Destination port

inet_service

length

Total packet length

integer (16 bit)

checksum

Checksum

integer (16 bit)

EXTENSION HEADER EXPRESSIONS

指与协议首部有关的扩展字段,如IPv6扩展首部,TCP选项和IPv4选项。

CONNTRACK EXPRESSIONS
  • ct {state | direction | status | mark | expiration | helper | label | count | id}
  • ct [original | reply] {l3proto | protocol | bytes | packets | avgpkt | zone}
  • ct {original | reply} {proto-src | proto-dst}
  • ct {original | reply} {ip | ip6} {saddr | daddr}

用于匹配报文的连接元数据

STATEMENTS

Statements表示需要执行的动作,可以是变更控制流(return、jump到另一个chain、accept或drop报文)或执行动作,如logging、拒绝报文等等。

下面给出了部分主要的statements。

VERDICT STATEMENT
  • {accept | drop | queue | continue | return}
  • {jump | goto} chain

verdict statement 用于修改ruleset中的控制流,并确定报文策略。

acceptdrop都属于 绝对verdicts

accept

结束ruleset评估并接收报文。

drop

结束ruleset评估,并丢弃报文。drop是立即执行的,不再评估后续的chains或hooks,因此无法在该后续的chain中accept报文

queue

结束ruleset评估,并将报文传给用户空间队列中。用户空间必须提供一个drop或accept的verdict statement 。在accept的情况下,会在下一个base chain hook中继续处理报文,而不是跟在queue verdict中的规则中处理。

continue

使用下一个规则继续评估ruleset,如果一条规则没有指定verdict statement ,则默认使用该statement。

return

从当前chain中返回,并继续使用下一个chain中的规则进行评估,如果执行该statement的是一个base chain, 则它相当于一个base chain policy.

jump chain

继续在 chain的第一条规则中进行评估,ruleset的当前位置会被push到一个调用栈中,并在新的chain评估结束或执行了return 之后返回到之前的位置。如果chain中的某个规则执行了绝对VERDICT,则将立即终止对ruleset的评估,并采取响应的操作。

goto chain

类似 jump,但不会将当前位置push到调用栈,意味着在新chain评估结束之后会继续执行下一个chain,而不会返回之前的位置。

Example
代码语言:javascript复制
# process packets from eth0 and the internal network in from_lan
# chain, drop all packets from eth0 with different source addresses.

filter input iif eth0 ip saddr 192.168.0.0/24 jump from_lan
filter input iif eth0 drop
REJECT STATEMENT
  • reject [ with REJECT_WITH ] REJECT_WITH := icmp icmp_code | icmpv6 icmpv6_code | icmpx icmpx_code | tcp reset

拒绝匹配到的报文,并返回错误。默认的错误为port-unreachable。只能用于 input, forwardoutput hooks

LOG STATEMENT
  • log [prefix quoted_string] [level syslog-level] [flags log-flags]
  • log group nflog_group [prefix quoted_string] [queue-threshold value] [snaplen size]
  • log level audit

用于将匹配到的报文记录到内核日志中,可以使用dmesg(1) 读取日志或从 syslog中读取日志。当然也可以通过监听nflog_group将日志转发到用户空间。

COUNTER STATEMENT

设置匹配报文的字节数。

  • counter packets number bytes number
  • counter { packets number | bytes number }
MAP STATEMENT
  • expression map { MAP_ELEMENTS } MAP_ELEMENTS := MAP_ELEMENT [, MAP_ELEMENTS] MAP_ELEMENT := key : value

map statement用于根据特定的key查找数据,key通常是一个expression的返回值。

代码语言:javascript复制
# select DNAT target based on TCP dport,key为dport
# connections to port 80 are redirected to 192.168.1.100,
# connections to port 8888 are redirected to 192.168.1.101
nft add rule ip nat prerouting dnat tcp dport map { 80 : 192.168.1.100, 8888 : 192.168.1.101 }

# source address based SNAT,key为saddr
# packets from net 192.168.1.0/24 will appear as originating from 10.0.0.1,
# packets from net 192.168.2.0/24 will appear as originating from 10.0.0.2
nft add rule ip nat postrouting snat to ip saddr map { 192.168.1.0/24 : 10.0.0.1, 192.168.2.0/24 : 10.0.0.2 }
VMAP STATEMENT
  • expression vmap { VMAP_ELEMENTS } VMAP_ELEMENTS := VMAP_ELEMENT [, VMAP_ELEMENTS] VMAP_ELEMENT := key : verdict

vmap statement和map statement类似,但包含一个verdicts字段

代码语言:javascript复制
# jump to different chains depending on layer 4 protocol type:
$ nft add rule ip filter input ip protocol vmap { tcp : jump tcp-chain, udp : jump udp-chain , icmp : jump icmp-chain }
NAT STATEMENTS
  • snat [[ip | ip6] [ prefix ] to] ADDR_SPEC [:PORT_SPEC] [FLAGS]
  • dnat [[ip | ip6] [ prefix ] to] ADDR_SPEC [:PORT_SPEC] [FLAGS]
  • masquerade [to :PORT_SPEC] [FLAGS]
  • redirect [to :PORT_SPEC] [FLAGS] ADDR_SPEC := address | address - address PORT_SPEC := port | port - port FLAGS := FLAG [, FLAGS] FLAG := persistent | random | fully-random

nat statement仅适用于nat 类型的chain。

snatmasquerade statements都会修改报文的源地址。snat只能用在postrouting和input chain,masquerade只能用在postrouting chain。dnatredirect statements只能用在prerouting和output chain,用于修改报文的目的地址。

masquerade statement是一种特殊的snat,用于将报文的源地址转换为出接口的IP地址。特别适用于使用动态IP地址的网关。

redirect statement是一种特殊的dnat,将目的地址转换为本地主机地址。如果只想更改不同接口上传入流量的目的端口,就可以使用该statement。

在内核4.18之前,nat statement要求同时有prerouting和postrouting base chain,否则netfilter将看不到返回路径上的数据包,因此不会反向转换地址。

Expression

Description

Type

address

指定要修改的报文的源/目的地址,可以指定一个mapping,包含由任意expression key与地址值组合的列表。

ipv4_addr, ipv6_addr, 如 abcd::1234, 或使用mapping,如 meta mark map

port

指定要修改的报文的源/目的端口

端口号 (16 bit)

基本用法
代码语言:javascript复制
# create a suitable table/chain setup for all further examples
add table nat
add chain nat prerouting { type nat hook prerouting priority dstnat; }
add chain nat postrouting { type nat hook postrouting priority srcnat; }

# translate source addresses of all packets leaving via eth0 to address 1.2.3.4
add rule nat postrouting oif eth0 snat to 1.2.3.4

# redirect all traffic entering via eth0 to destination address 192.168.1.120
add rule nat prerouting iif eth0 dnat to 192.168.1.120

# translate source addresses of all packets leaving via eth0 to whatever
# locally generated packets would use as source to reach the same destination
add rule nat postrouting oif eth0 masquerade

# redirect incoming TCP traffic for port 22 to port 2222
add rule nat prerouting tcp dport 22 redirect to :2222

# inet family:
# handle ip dnat:
add rule inet nat prerouting dnat ip to 10.0.2.99
# handle ip6 dnat:
add rule inet nat prerouting dnat ip6 to fe80::dead
# this masquerades both ipv4 and ipv6:
add rule inet nat postrouting meta oif ppp0 masquerade
高级用法
代码语言:javascript复制
# map prefixes in one network to that of another, e.g. 10.141.11.4 is mangled to 192.168.2.4,
# 10.141.11.5 is mangled to 192.168.2.5 and so on.
add rule nat postrouting snat ip prefix to ip saddr map { 10.141.11.0/24 : 192.168.2.0/24 }

# map a source address, source port combination to a pool of destination addresses and ports:
add rule nat postrouting dnat to ip saddr . tcp dport map { 192.168.1.2 . 80 : 10.141.10.2-10.141.10.5 . 8888-8999 }

# The above example generates the following NAT expression:
#
# [ nat dnat ip addr_min reg 1 addr_max reg 10 proto_min reg 9 proto_max reg 11 ]
#
# which expects to obtain the following tuple:
# IP address (min), source port (min), IP address (max), source port (max)
# to be obtained from the map. The given addresses and ports are inclusive.

# This also works with named maps and in combination with both concatenations and ranges:
table ip nat {
        map ipportmap {
                typeof ip saddr : interval ip daddr . tcp dport
                flags interval
                elements = { 192.168.1.2 : 10.141.10.1-10.141.10.3 . 8888-8999, 192.168.2.0/24 : 10.141.11.5-10.141.11.20 . 8888-8999 }
        }

        chain prerouting {
                type nat hook prerouting priority dstnat; policy accept;
                ip protocol tcp dnat ip to ip saddr map @ipportmap
        }
}

# @ipportmap maps network prefixes to a range of hosts and ports.
# The new destination is taken from the range provided by the map element.
# Same for the destination port.

# Note the use of the "interval" keyword in the typeof description.
# This is required so nftables knows that it has to ask for twice the
# amount of storage for each key-value pair in the map.

# ": ipv4_addr . inet_service" would allow associating one address and one port
# with each key.  But for this case, for each key, two addresses and two ports
# (The minimum and maximum values for both) have to be stored.
MONITOR
  • monitor [new | destroy] MONITOR_OBJECT monitor trace MONITOR_OBJECT := tables | chains | sets | rules | elements | ruleset

Monitor 命令可以监听nf_tables子系统产生的Netlink事件。这些事件要么与对象的创建和删除有关,要么与启用meta nftrace的报文有关。这些事件将以JSON或原生的nft格式打印到stdout。

可以使用MONITOR_OBJECT过滤与具体对象有关的事件。

使用newdestroy来过滤与特定动作有关的事件。

第二种形式的调用没有选项,只打印启用nftrace的报文生成的事件。

启用nftrace

为了启用nftrace,只需要在规则之后跟上下面statement即可:

代码语言:javascript复制
meta nftrace set 1

当然也可以为特定的报文启用nftrace,下面为tcp报文启用nftrace:

代码语言:javascript复制
ip protocol tcp meta nftrace set 1
使用单独chain来启用nftrace

推荐使用单独的chain来启用nftrace,下面用于跟踪prerouting chain,如果有已经有一个prerouting chain,则需要确保trace_chain的优先级更高。

代码语言:javascript复制
$ nft add chain filter trace_chain { type filter hook prerouting priority -301; }
$ nft add rule filter trace_chain meta nftrace set 1

在调试结束之后,删除创建的trace_chain即可:

代码语言:javascript复制
% nft delete chain filter trace_chain
monitor trace事件

在启用nftrace之后,就可以监控产生的跟踪事件:

代码语言:javascript复制
#Listen to all events, report in native nft format.
% nft monitor

#Listen to deleted rules, report in JSON format.
% nft -j monitor destroy rules

#Listen to both new and destroyed chains, in native nft format.
% nft monitor chains

#Listen to ruleset events such as table, chain, rule, set, counters and quotas, in native nft format.
% nft monitor ruleset

#Trace incoming packets from host 10.0.0.1.
% nft add rule filter input ip saddr 10.0.0.1 meta nftrace set 1
% nft monitor trace

kubernetes中启用nftables

kubernetes v1.29中需要在kube-proxy中启用NFTablesProxyMode feature gate并指定mode为nftables时才能使用nftables。下面是一个使用kubeadm启动单节点集群的例子

代码语言:javascript复制
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  token: abcdef.0123456789abcdef
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
  imagePullPolicy: IfNotPresent
  name: node
  taints: null
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
kind: ClusterConfiguration
kubernetesVersion: 1.29.0
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
scheduler: {}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
featureGates:
  NFTablesProxyMode: true
mode: nftables

0 人点赞