nftables(4)表达式(2)主要表达式(PRIMARY EXPRESSIONS)

简介

上篇文章已经介绍了数据类型,如INTERGER TYPE、BITMASK TYPE、STRING TYPE、LINK LAYER ADDRESS TYPE、 IPV4 ADDRESS TYPE、 IPV6 ADDRESS TYPE、BOOLEAN TYPE、ICMP TYPE、CONNTRACK TYPES等。那么本篇文章主要介绍PRIMARY表达式的相关内容。

PRIMARY EXPRESSIONS

在防火墙规则配置中,主要表达式(Primary Expressions)是最低阶的表达式,它们代表数据包负载中的常量或单个数据项、元数据,或者来自有状态模块的数据。这些表达式是构建更复杂表达式的基础,用于在防火墙规则、路由决策或数据包处理逻辑中指定具体的条件或值。

META 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 }

类型

  • 未限定元数据表达式(Unqualified Meta Expressions):这些表达式直接使用元数据关键字(如markiif等),而不需要前缀meta。然而,在某些上下文中,为了清晰或遵循特定的语法规则,它们也可以被写作限定的形式。

  • 限定的元数据表达(Qualified Meta Expressions):这些表达式在元数据关键字前加上meta前缀,以明确指定它们是对元数据的引用。虽然大多数元数据项可以直接使用,但使用meta前缀可以提高代码的可读性和一致性。

常见的元数据项

  • l4proto:用于匹配数据包中的特定传输层协议(如TCP、UDP等)。对于IPv6数据包,它会跳过任何扩展头。

  • iif/oif 与 iifname/oifname:这些用于匹配数据包进入或离开的网络接口。iifoif基于接口索引进行匹配,而iifnameoifname则基于接口名称进行匹配。接口索引在接口被重命名时不会改变,但接口名称可能会改变。因此,在接口名称可能发生变化的情况下(如动态创建的接口),使用接口名称进行匹配可能更为灵活。

  • mark, nftrace, rtclassid 等:这些元数据项用于高级的网络处理任务,如数据包标记、跟踪和路由类设置。

动态接口和通配符匹配

  • 对于动态创建的接口(如tun/tap接口或拨号接口ppp),使用iifnameoifname进行匹配可能更为合适,因为它们不依赖于接口在系统启动时的存在。
  • iifnameoifname中,可以使用通配符(如星号*)来匹配接口名称的前缀。但是,请注意,与iptables不同,nftables不接受仅由通配符字符组成的接口名称。如果确实需要匹配字面量的星号字符,可以使用反斜杠\进行转义。

元数据表达式类型

关键字 描述 类型
length 数据包的长度(以字节为单位) integer (32-bit)
nfproto 真实的钩子协议族(仅在inet表中有效) integer (32-bit)
l4proto 第四层协议(跳过IPv6扩展头部) integer (8-bit)
protocol EtherType协议值 ether_type
priority TC数据包优先级 tc_handle
mark 数据包标记 mark
iif 输入接口索引 iface_index
iifname 输入接口名称 ifname
iiftype 输入接口类型 iface_type
oif 输出接口索引 iface_index
oifname 输出接口名称 ifname
oiftype 输出接口硬件类型 iface_type
sdif 从属设备输入接口索引 iface_index
sdifname 从属设备接口名称 ifname
skuid 与原始套接字关联的UID uid
skgid 与原始套接字关联的GID gid
rtclassid 路由领域 realm
ibrname 输入桥接接口名称 ifname
obrname 输出桥接接口名称 ifname
pkttype 数据包类型 pkt_type
cpu 处理数据包的CPU编号 integer (32-bit)
iifgroup 输入设备组 devgroup
oifgroup 输出设备组 devgroup
cgroup 控制组ID integer (32-bit)
random 伪随机数 integer (32-bit)
ipsec 数据包是否经过IPsec加密 boolean (1-bit)
iifkind 输入接口类型(更详细) 字符串/其他
oifkind 输出接口类型(更详细) 字符串/其他
time 数据包接收的绝对时间 Integer (32-bit) 或 字符串
day 一周中的哪一天 Integer (8-bit) 或 字符串
hour 一天中的哪个小时 字符串

元表达式特定类型

类型         描述
iface_index 接口索引(32位数字)。可以以数字形式指定,也可以作为现有接口的名称指定。
ifname 接口名称(16字节字符串)。不需要实际存在。
iface_type 接口类型(16位数字)。
uid 用户ID(32位数字)。可以以数字形式指定,也可以作为用户名指定。
gid 组ID(32位数字)。可以以数字形式指定,也可以作为组名指定。
realm 路由领域(32位数字)。可以以数字形式指定,也可以作为/etc/iproute2/rt_realms中定义的符号名称指定。
devgroup_type 设备组(32位数字)。可以以数字形式指定,也可以作为/etc/iproute2/group中定义的符号名称指定。
pkt_type 数据包类型:host(发送给本地主机)、broadcast(发送给所有)、multicast(发送给组)、other(发送给另一台主机)。
ifkind 接口类型(16字节字符串)。参见ip-link(8)手册页中的TYPES列表。
time 可以是整数或ISO格式的日期。例如:"2019-06-06 17:00"。小时和秒是可选的,如果省略,则默认为午夜。以下三个是等价的:"2019-06-06"、"2019-06-06 00:00"和"2019-06-06 00:00:00"。当给定整数时,假定为UNIX时间戳。
day 可以是星期几的名称("Monday"、"Tuesday"等),或者是0到6之间的整数。字符串匹配不区分大小写,并且不需要完全匹配(例如,"Mon"可以匹配"Monday")。当给定整数时,0代表星期日,6代表星期六。
hour 表示24小时制中的小时的字符串。秒是可选的。例如,17:00和17:00:00是等价的。

使用举例

# 限定元表达式
filter output meta oif eth0  # 此规则指定输出接口为 "eth0" 的数据包
filter forward meta iifkind { "tun", "veth" } #  此规则表示转发数据包中输入接口种类为 "tun" 或 
"veth" 的数据包将被匹配

# 不限定元表达式
filter output oif eth0 #直接使用了 meta 键指定输出接口为 "eth0" 的数据包

raw prerouting meta ipsec exists accept #这条规则是在原始表(raw table)的 prerouting 链中,
检查数据包是否经过了 IPsec 处理。如果数据包已经经过了 IPsec 加密处理,存在相应的元数据信息,则执
行 accept 操作,即允许数据包继续处理。

Socket expression 

Socket expression 是一种在网络过滤或监控系统中使用的表达式,它允许你根据特定的条件来搜索或匹配已经打开的 TCP/UDP 套接字(socket)及其相关属性。这些属性可能与经过网络接口的数据包相关联。Socket expression 提供了强大的灵活性,用于在网络数据包处理流程中实施复杂的规则或策略。

搜索已打开的套接字

Socket expression 允许你查找已建立(established)或已绑定(bound,但不一定处于监听状态)的套接字。这些套接字可能绑定到特定的 IP 地址和端口上,也可能绑定到非本地(即非 127.0.0.1 或 ::1)地址上。这种能力对于监控特定应用程序的网络活动或实施基于套接字的网络策略非常有用。

匹配套接字属性

除了简单地查找套接字外,Socket expression 还可以用来匹配套接字的特定属性。这些属性可能包括套接字的类型(TCP 或 UDP)、状态(如已建立、监听等)、绑定的 IP 地址和端口等。通过匹配这些属性,你可以更精确地控制哪些数据包应该被允许或拒绝。

cgroupv2 套接字匹配

另一个高级功能是匹配套接字所属的 cgroupv2 层级。cgroupv2(Control Groups version 2)是 Linux 内核中的一种功能,用于限制、记录和隔离进程组所使用的物理资源(如 CPU、内存、网络带宽等)。通过 Socket expression,你可以根据套接字所属的 cgroupv2 层级来匹配数据包。这对于实施基于进程组或容器的网络策略特别有用。

socket {transparent | mark | wildcard}
socket cgroupv2 level NUM
名称 描述 类型
transparent 在找到的套接字中,IP_TRANSPARENT套接字选项的值。它可以是0或1。 boolean
mark 套接字的标记值(SOL_SOCKET, SO_MARK)。 mark
wildcard 表示套接字是否绑定了通配符地址(例如,0.0.0.0或::0)。 boolean
cgroupv2 此套接字所属的cgroup v2的层次结构(或标识符)。 cgroupv2

使用举例

# 标记与透明套接字相对应的数据包。"socket wildcard 0"意味着不匹配绑定到通配符地址的监听套接字(这通常是您想要的)。  
table inet x {  
    chain y {  
        type filter hook prerouting priority mangle; policy accept;  
        # 如果数据包对应于一个启用了IP_TRANSPARENT选项的套接字,并且该套接字不是绑定到通配符地址的,  
        # 则将该数据包的标记设置为0x00000001,并接受该数据包。  
        socket transparent 1 socket wildcard 0 mark set 0x00000001 accept  
    }  
}  
  
# 追踪标记值为15的套接字对应的数据包。  
table inet x {  
    chain y {  
        type filter hook prerouting priority mangle; policy accept;  
        # 如果数据包的标记值等于0x0000000f,则设置nftrace标志以进行追踪(这通常需要额外的内核配置或工具来实际捕获追踪信息)。  
        socket mark 0x0000000f nftrace set 1  
    }  
}  
  
# 将数据包的标记设置为套接字的标记。  
table inet x {  
    chain y {  
        type filter hook prerouting priority mangle; policy accept;  
        # 如果数据包的目标端口是8080(TCP),则将数据包的标记设置为与该数据包相关联的套接字的标记。  
        tcp dport 8080 mark set socket mark  
    }  
}  
  
# 对cgroupv2 "user.slice"在层级1上的数据包进行计数。  
table inet x {  
    chain y {  
        type filter hook input priority filter; policy accept;  
        # 对于输入方向的数据包,如果该数据包对应的套接字属于cgroupv2层级1下的"user.slice",  
        # 则对该数据包进行计数(这通常用于监控和统计特定cgroup的网络活动)。  
        socket cgroupv2 level 1 "user.slice" counter  
    }  
}

osf expression

操作系统指纹(Operating System Fingerprinting),用于识别和确定目标系统的操作系统类型及其版本。这种技术通过分析从目标系统发出的网络数据包中的特定信息来实现。这种技术涉及分析具有 SYN 位设置的数据包中的某些特征,以推断远程主机上运行的操作系统详情。比较通常关注的数据包属性包括窗口大小、最大段大小(MSS)、选项及其顺序、不分片(DF)标志以及其他 TCP/IP 数据包属性。通过检查这些数据包属性,并将它们与已知与各种操作系统相关的模式进行比较,可以了解特定设备上可能运行的操作系统类型,而无需主动从事任何侵入式的扫描或探测活动。

osf [ttl {loose | skip}] {name | version}

osf属性

名称 描述 类型
ttl 对数据包的生存时间(TTL)进行检查,以确定操作系统类型。TTL(Time-To-Live)是数据包在网络中可以经过的路由器数量的最大值。 string
version 对数据包进行操作系统版本的检查。这通常涉及对数据包中特定字段的详细分析,以推断操作系统的具体版本。
name 要匹配的操作系统签名的名称。这些签名通常基于操作系统在网络通信中特有的行为或数据包特征。所有签名都可以在pf.os文件(或类似文件,取决于使用的工具或系统)中找到。如果表达式无法检测到匹配的操作系统签名,则可以使用"unknown"来表示未知或无法识别的操作系统。 string

可用的TTL值检查选项

如果没有传递TTL属性,则会创建一个真实的IP头部,并直接进行TTL值的指纹比对。这种方法通常适用于局域网(LANs)。

  • loose(宽松):检查IP头部的TTL值是否小于指纹中的TTL值。这种方法适用于全局可路由的地址,因为它允许一定的TTL值差异,可能是由于数据包在到达目标之前经过了不同数量的路由器。
  • skip(跳过):完全不进行TTL值的比较。这意味着在操作系统指纹识别过程中,TTL值将不会被用作判断依据。

使用举例

# 在不比较 TTL 的情况下接受与 "Linux" 操作系统类型签名匹配的数据包。
# 如果数据包的操作系统指纹与 "Linux" 匹配,那么这个规则允许通过并执行默认策略(accept)
table inet x {
    chain y {
        type filter hook input priority filter; policy accept;
        osf ttl skip name "Linux"
    }
}

fib expression

FIB(Forwarding Information Base,转发信息库)是路由器用来决定如何转发数据包到目的地的核心数据库。它包含了关于如何基于数据包的目的地地址将其路由到正确的下一跳地址或输出接口的详细信息。

fib {saddr | daddr | mark | iif | oif} [. ...] {oif | oifname | type}
关键字 描述 类型
oif 输出接口索引 integer (32 bit)
oifname 输出接口名称 string
type 地址类型 fib_addrtype*

使用举例

# 检查数据包是否有反向路径。它基于源地址和输入接口查找路由信息。如果找不到匹配的路由,输出接口索引将为零,然后将对应的数据包丢弃。
filter prerouting fib saddr . iif oif missing drop
# 在这个示例中,'saddr . iif' 根据源地址和输入接口查找路由信息。
# oif 从路由信息中选择输出接口索引。
# 如果未找到源地址/输入接口组合的路由,则输出接口索引为零。
# 如果将输入接口作为输入键的一部分指定,则输出接口索引始终与输入接口索引相同或为零。
# 如果仅给出 'saddr oif',那么 oif 可以是任何接口索引或零。

# 对于目的地址不在传入接口上配置的数据包进行处理。如果目的地址不是本地、广播或多播类型,则将这些数据包丢弃。
filter prerouting fib daddr . iif type != { local, broadcast, multicast } drop


#在特定的 'blackhole' 表(0xdead)中执行查找操作,根据目的地址的标记类型来进行不同的操作:blackhole 表示丢弃数据包,prohibit 表示跳转到受限制操作,unreachable 表示丢弃数据包。
filter prerouting meta mark set 0xdead fib daddr . mark type vmap { blackhole : drop, prohibit : jump prohibited, unreachable : drop }

 routing expression

路由表达式是指与数据包相关联的路由数据。

rt [ip | ip6] {classid | nexthop | mtu | ipsec}

路由表达式类型

关键字 描述 类型
classid 路由领域(Routing realm),用于分类或标识路由的特定组或区域。realm 路由领域(Routing Realm)(32位数字)。可以指定为数字,或者在/etc/iproute2/rt_realms文件中定义的符号名称。通过为路由分配不同的领域值,可以更容易地管理和控制数据包的路由选择。这些领域值可以是任意32位数字,但为了便于理解和记忆,可以在/etc/iproute2/rt_realms文件中为这些数字定义符号名称。这样,在配置路由时,就可以使用这些符号名称来代替数字,从而使配置更加清晰和易于维护。 realm
nexthop 路由的下一跳地址,可以是IPv4或IPv6地址 ipv4_addr/ipv6_addr
mtu 路由的TCP最大报文段大小(TCP Maximum Segment Size),用于TCP连接的MTU发现 integer (16 bit)
ipsec 指示路由是否通过IPsec隧道或传输模式进行 boolean(真/假)

ipsec expression

ipsec表达式是指与报文相关联的ipsec数据。
需要使用in或out关键字来指定表达式是应该检查入站策略还是出站策略。in关键字可用于预路由、输入和转发钩子。out关键字适用于转发、输出和发送后钩子。可选关键字spnum可用于匹配链中的特定状态,默认值为0。

ipsec {in | out} [ spnum NUM ]  {reqid | spi}
ipsec {in | out} [ spnum NUM ]  {ip | ip6} {saddr | daddr}

Ipsec expression types

Keyword Description Type
reqid Request ID integer (32 bit)
spi Security Parameter Index integer (32 bit)
saddr Source address of the tunnel ipv4_addr/ipv6_addr
daddr Destination address of the tunnel ipv4_addr/ipv6_addr

numgen expression 

numgen 表达式用于创建一个数字生成器,其操作模式由 inc(递增)或 random(随机)关键字控制。这个表达式在需要动态生成一系列数字时非常有用,特别是在编程、脚本编写、或任何需要自动化数值处理的场景中。

numgen {inc | random} mod NUM [ offset NUM ]

numgen:这是启动数字生成器的关键字。
{inc | random}:这指定了生成器的操作模式。inc 表示递增模式,其中生成的每个数字都是前一个数字的递增值;random 表示随机模式,其中每次生成的数字都是随机的。
mod NUM:这指定了一个上限(模数),生成的数字不会超过这个值。模数(NUM)用于确保生成的数字在一个指定的范围内。
[offset NUM]:这是一个可选参数,允许你为生成的数字添加一个固定的偏移量。这意味着每个生成的数字都会先按照 inc 或 random 模式计算,然后再加上这个偏移量。

使用举例

add rule nat prerouting dnat to numgen inc mod 2 map \
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }
目的:这个规则的目的是在192.168.10.100和192.168.20.200这两个IP地址之间实现轮询(Round-Robin)负载均衡。
机制:通过numgen inc mod 2,每次请求都会生成一个递增的数字(从0开始),然后这个数字对2取模,结果只能是0或1。根据这个结果,请求会被重定向到map中指定的IP地址。
映射:如果生成的数字是0,则请求被重定向到192.168.10.100;如果是1,则重定向到192.168.20.200。由于数字是递增的,并且每次都对2取模,因此这实现了在两个IP地址之间的交替选择,即轮询。

add rule nat prerouting dnat to numgen random mod 10 map \
        { 0-2 : 192.168.10.100, 3-9 : 192.168.20.200 }
目的:这个规则的目的是根据概率将请求分配到两个IP地址,但带有一定的偏置,使得192.168.20.200接收的请求更多。
机制:通过numgen random mod 10,每次请求都会生成一个0到9之间的随机数字。然后,这个数字被用来在map中查找对应的IP地址。
映射:如果生成的数字在0到2之间(包含0和2),则请求被重定向到192.168.10.100;如果数字在3到9之间(包含3和9),则请求被重定向到192.168.20.200。由于3到9的范围比0到2的范围大,因此192.168.20.200接收的请求数量大约是192.168.10.100的三倍(8/3 ≈ 2.67,但实际上由于随机性,比例可能略有不同)。

hash expressions

jhash(通常称为Jenkins Hash)和symhash(对称哈希)是两种常用的哈希函数,用于生成一个数字,该数字可以作为决策的依据,比如决定将数据包发送到哪个服务器。这些哈希函数通过特定的算法处理输入数据(如数据包头部信息),并输出一个固定范围的数值。

Jenkins Hash(jhash)

特点

  • 灵活性jhash允许你通过表达式指定数据包头部中哪些参数用于哈希计算,甚至可以进行拼接(concatenation)来创建更复杂的输入。
  • 参数
    • 输入数据:这是你想要哈希的数据,可能是数据包头部的字段拼接而成的字符串。
    • 模数(modulus):通过mod关键字指定的一个上限值,确保哈希函数返回的数字不会超过这个值。这对于确保负载均衡的均匀分布非常关键。
    • 种子(seed)(可选):一个初始值,用于哈希函数的计算中。不同的种子值会导致相同的输入数据产生不同的哈希值,这有助于在分布式系统中避免哈希碰撞。
    • 偏移量(offset)(可选):允许你将返回的哈希值增加一个固定的偏移量,这在某些特定场景下可能有用。

应用场景

  • 在负载均衡器中,jhash可以用来根据数据包的某些特征(如源IP、目的IP、端口号等)计算出一个哈希值,然后根据这个哈希值决定将数据包转发到哪个后端服务器。

对称哈希(symhash)

特点

  • 对称性:尽管symhash的确切特性可能因实现而异,但“对称”一词通常意味着该哈希函数在某些方面是对称的,比如输入数据的微小变化可能导致哈希值在数值空间中的相对位置发生对称变化。然而,这只是一个概念性的解释,具体实现可能有所不同。
  • 参数:与jhash类似,symhash也可能需要输入数据、模数、种子和偏移量等参数。

应用场景

  • jhash相似,symhash也可以用于负载均衡中,通过哈希数据包的特征来决定其路由。不过,由于symhash的具体实现和特性可能因环境而异,因此其在实际应用中的表现也可能有所不同。

举例使用

#对于所有进入网络的数据包,系统会根据数据包的源IP地址(ip saddr)使用jhash来计算一个哈希值。然后,这个哈希值会被mod 2操作,结果是0或1。根据这个结果,数据包的目的地址(DNAT)会被重定向到两个IP地址之一:
如果哈希值mod 2的结果是0,数据包的目的地址将被更改为192.168.10.100。
如果哈希值mod 2的结果是1,数据包的目的地址将被更改为192.168.20.200。
add rule nat prerouting dnat to jhash ip saddr mod 2 map \
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }

# 使用 symhash 函数,根据对称性哈希算法,在两个指定的 IP 地址(192.168.10.100 和 192.168.20.200)之间进行负载均衡。
对称哈希函数考虑更多的对称性和平衡性,以确保流量被均匀地分配到这两个 IP 地址上,从而实现对称性负载均衡。
add rule nat prerouting dnat to symhash mod 2 map \
        { 0 : 192.168.10.100, 1 : 192.168.20.200 }

相关推荐

  1. nftables(4)表达式(2)主要表达式(PRIMARY EXPRESSIONS)

    2024-07-10 17:32:08       9 阅读
  2. nftables(5)表达式(3)PAYLOAD EXPRESSIONS

    2024-07-10 17:32:08       9 阅读
  3. CMake官方教程4--使用表达式生成器

    2024-07-10 17:32:08       26 阅读
  4. 学习记录之数学表达式4

    2024-07-10 17:32:08       17 阅读
  5. 重构与优化-条件表达式优化(4

    2024-07-10 17:32:08       15 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 17:32:08       5 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 17:32:08       5 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 17:32:08       4 阅读
  4. Python语言-面向对象

    2024-07-10 17:32:08       5 阅读

热门阅读

  1. C++八股(三)之虚函数

    2024-07-10 17:32:08       11 阅读
  2. Linux下mysql数据库的导入与导出以及查看端口

    2024-07-10 17:32:08       11 阅读
  3. Mybatis-Flex各种查询,强烈建议收藏

    2024-07-10 17:32:08       13 阅读
  4. Mybatis-plus学习

    2024-07-10 17:32:08       8 阅读
  5. mysql函数 last_insert_id()

    2024-07-10 17:32:08       11 阅读
  6. DateTimeUtils

    2024-07-10 17:32:08       7 阅读
  7. CSS:选择器 / 14种类型

    2024-07-10 17:32:08       10 阅读
  8. css中文字书写方向

    2024-07-10 17:32:08       9 阅读
  9. 19.JWT

    19.JWT

    2024-07-10 17:32:08      10 阅读
  10. 实证Stata代码命令汇总

    2024-07-10 17:32:08       10 阅读
  11. 将 build.gradle 配置从 Groovy 迁移到 Kotlin

    2024-07-10 17:32:08       11 阅读
  12. MySQL数据库字符集utf8mb4的排序规则介绍

    2024-07-10 17:32:08       9 阅读