上篇文章我们介绍了SETS集合相关的内容,本篇文章主要介绍map,在nftables中,Map(映射)用于存储键值对,类似于许多编程语言中的关联数组/字典/哈希表。在nftables规则中,可以指定一个数据包字段(例如:tcp目的端口),然后引用一个映射来搜索具有与数据包字段值匹配的键的映射元素,并返回该映射元素的值(或者如果映射中没有匹配元素则返回失败)。
可以把map
想象成一个集合,但它不仅返回“在集合中/不在集合中”的结果,而且返回一个具体的值。在内部实现上,集合和map
都使用了相同的通用集合基础设施,因此它们共享许多相同的选项和语义。
maps和前面所介绍的集合非常相似也分为匿名映射(Anonymous Maps)和命名映射(Named Maps),匿名映射和命名映射的区别如下:
匿名映射 | 命名映射 | |
---|---|---|
定义方式 | 直接在规则中定义 | 事先声明,独立于规则存在 |
重用性 | 通常只在该规则中使用,不具有重用性 | 可以在多个规则中引用,具有高度的可重用性 |
可维护性 | 由于直接在规则中定义,可能较难维护 | 由于具有名称和独立的声明,更容易维护和修改 |
动态性 | 不支持动态更新(因为直接在规则中定义) | 支持动态更新,可以添加、删除或修改映射元素 |
适用场景 | 适用于简单的映射需求,快速定义和使用 | 适用于复杂的网络策略,需要高度灵活性和可维护性的场景 |
虽然这么区分,但是在nftables
的上下文中,实际上并没有直接称为“匿名映射”的明确概念,因为nftables
中的映射(sets)通常是通过名称来引用的,以便在多个规则中重复使用。然而,可以理解为在某些场景下,nftables
规则中直接使用了类似映射的功能,但这些功能并没有事先声明为独立的命名映射,而是直接在规则表达式中定义,这种情况可以视为一种“匿名”的使用方式。
不过,由于nftables
的规则和表达式设计并不直接支持传统意义上的“匿名映射”,我将通过下面的示例来说明如何在nftables
规则中模拟类似映射的功能,尽管这些并不是严格意义上的匿名映射。
如上图所示,这是前面我们在做测试的时候我们配置的内容,我们在规则中直接使用了IP地址列表,这可以视为一种“匿名”的映射方式。
- add map [family] table map { type type | typeof expression [flags flags ;] [elements = { element[, ...] } ;] [size size ;] [comment comment ;] [policy 'policy ;] }
- {delete | destroy | list | flush | reset } map [family] table map
- list maps [family]
命令/属性 | 描述 |
---|---|
add map | 在指定的表中添加一个新的Map。 |
delete map | 删除指定的Map。 |
destroy map | 删除指定的Map,如果不存在则不会失败。 |
list map | 显示指定Map中的元素。 |
flush map | 从指定的Map中移除所有元素。 |
reset map | 重置Map中所有元素的状态,例如计数器和配额的值。 |
[family] | Map所属的地址族,如ip 、ip6 等。 |
[table] | Map所属的表。 |
[map name] | 用户定义的Map名称。 |
type | Map元素的数据类型,如ipv4_addr , ipv6_addr , ether_addr , inet_proto , inet_service , mark , counter , quota (注意:counter 和quota 不能用作键)。 |
typeof | 通过表达式推导出的Map元素数据类型。 |
flags | Map标志,与Set标志相同。 |
elements | Map包含的元素,具体数据类型取决于type 或typeof 。 |
size | Map中元素的最大数量,无符号64位整数。 |
policy | Map策略,如performance (默认)或memory 。 |
标志 | 描述 |
---|---|
constant | Set/Map的内容在创建后永远不会改变。 |
dynamic | Set/Map必须支持从数据包路径使用add 、update 或delete 关键字进行更新。 |
interval | Set/Map必须能够存储间隔(范围)。 |
timeout | Set/Map必须支持元素超时(元素过期后自动删除)。 |
以上的这些参数是不是和我们前面所配置的set很相似,因为它们使用了相同的通用集合基础设施。
- {add | create | delete | destroy | get | reset } element [family] table set { ELEMENT[, ...] }
- ELEMENT := key_expression OPTIONS [: value_expression]
- OPTIONS := [timeout TIMESPEC] [expires TIMESPEC] [comment string]
- TIMESPEC := [numd][numh][numm][num[s]]
命令 | 描述 |
---|---|
add | 向集合或映射中添加一个或多个元素。如果元素已存在,则可能失败或更新现有元素(取决于上下文)。 |
create | 类似于add ,但要求添加的元素在集合或映射中必须不存在。如果已存在,则操作失败。 |
delete | 从集合或映射中删除一个或多个元素。在映射中,如果指定了值表达式,则必须完全匹配键和值才能删除;如果只指定了键表达式,则只根据键来删除。 |
destroy | 删除整个集合或映射,包括其所有元素。这是一个更彻底的操作,用于移除不再需要的集合或映射。 |
get | 检查集合或映射中是否包含特定的元素。对于大型或区间集合,如果元素存在,则可能返回包含该元素的区间而非元素本身。 |
reset | 重置与给定元素相关联的状态,如计数器或配额语句的值。这通常用于清除元素的状态信息。 |
选项 | 描述 |
---|---|
timeout | 为具有超时标志的集合或映射中的元素设置超时值。当元素达到指定的超时时间后,它可能会被自动删除或标记为过期。 |
expires | 设置给定元素的过期时间。这个选项主要用于规则集复制的场景,以指示元素何时应被视为过期。 |
comment | 为元素添加注释字段。这有助于在查看或调试规则集时提供有关元素的额外信息。 |
Verdict Maps (vmaps) 在 nftables
防火墙规则集中是一种特殊类型的映射(map),它们允许将元素直接映射到裁决(verdict)语句上。裁决语句决定了当匹配到特定规则时应该采取的动作,比如接受(accept)、拒绝(reject)或丢弃(drop)数据包。
使用 vmap
语句创建的 Verdict Maps 内部基于通用的集合(set)基础设施,因此它们共享一些语义和选项。某些文档中可能将 vmaps 称为字典(dictionaries),在 nftables
上下文中具有特定的用途和行为。
- expression vmap { VMAP_ELEMENTS }
- VMAP_ELEMENTS := VMAP_ELEMENT [, VMAP_ELEMENTS]
- VMAP_ELEMENT := key : verdict
裁决语句 | 描述 |
---|---|
accept | 终止规则集评估并接受数据包。数据包仍可能在后续钩子中被丢弃,例如在forward 钩子中接受的数据包可能在postrouting 钩子或之后评估的具有更高优先级编号的forward 基础链中被丢弃。 |
drop | 终止规则集评估并丢弃数据包。丢弃操作立即发生,不再评估其他链或钩子。一旦数据包被丢弃,后续链中不会再评估该数据包。 |
queue | 终止规则集评估并将数据包排队到用户空间。用户空间必须提供丢弃或接受裁决。如果接受,处理将在下一个基础链钩子中恢复,而不是在queue 裁决之后的规则。 |
continue | 继续评估规则集中的下一条规则。这是在没有裁决的情况下规则的默认行为。 |
return | 从当前链返回并继续在上一个链中的下一条规则进行评估。如果在基础链中发出,则等同于基础链的策略。 |
jump chain | 在链中的第一条规则处继续评估。当前规则集位置被推送到调用堆栈,并在新链完全评估或发出return 裁决后,评估将在那里继续。如果链中的规则发出绝对裁决,则立即终止规则集评估并执行特定操作。 |
goto chain | 类似于jump ,但当前位置不会被推送到调用堆栈,这意味着在新链评估完成后,将在上一个链(而不是包含goto 语句的链)中继续评估。 |
nftables
本身不直接支持“匿名映射”的概念,但我们可以将这个概念理解为那些没有显式命名的映射。然而,在nftables
的实际使用中,几乎所有的映射都是命名的,因为你需要一个引用来在规则中引用它们。
例如可以从逻辑上将对 TCP 和 UDP 数据包的处理规则拆分开来,那么vmaps是如何做的呢?
我们先清空现有的nftables表,然后进行配置:
- root@debian:~# nft flush ruleset
- root@debian:~# nft list ruleset
- root@debian:~# nft add table filter-vmap
- root@debian:~# nft add chain filter-vmap input-vmap { type filter hook input priority -200 \; }
- root@debian:~# nft list table filter-vmap
- table ip filter-vmap {
- chain input-vmap {
- type filter hook input priority -200; policy accept;
- }
- }
- root@debian:~# nft add chain filter-vmap tcp-vmap
- root@debian:~# nft add chain filter-vmap udp-vmap
- root@debian:~# nft list table filter-vmap
- table ip filter-vmap {
- chain input-vmap {
- type filter hook input priority -200; policy accept;
- }
-
- chain tcp-vmap {
- }
-
- chain udp-vmap {
- }
- }
- root@debian:~# nft add rule filter-vmap input-vmap meta l4proto vmap { tcp : jump tcp-vmap , udp : jump udp-vmap }
- root@debian:~# nft list table filter-vmap
- table ip filter-vmap {
- chain input-vmap {
- type filter hook input priority -200; policy accept;
- meta l4proto vmap { tcp : jump tcp-vmap, udp : jump udp-vmap }
- }
-
- chain tcp-vmap {
- }
-
- chain udp-vmap {
- }
- }
现在已经分流了,我们可以对效果进行测试。如果我们需要匹配放行从192.168.140.248访问本机的80端口的tcp流量应该怎么做呢?
在上部分匿名映射中,我们可以可以从逻辑上将对 TCP 和 UDP 数据包的处理规则拆分开来,那么命名映射该如何做的呢?
我们先清空现有的nftables表,然后进行配置:
引用vmap分流策略
- root@debian:~# nft flush ruleset
- root@debian:~# nft list ruleset
- root@debian:~# nft add table filter-name-vmap
- root@debian:~# nft add chain filter-name-vmap input-name-tcp
- root@debian:~# nft add chain filter-name-vmap input-name-udp
- root@debian:~# nft list table filter-name-vmap
- table ip filter-name-vmap {
- chain input-name-tcp {
- }
-
- chain input-name-udp {
- }
- }
- root@debian:~# nft add chain filter-name-vmap input-name-vmap { type filter hook input priority -200 \; }
- root@debian:~# nft list table filter-name-vmap
- table ip filter-name-vmap {
- chain input-name-tcp {
- }
-
- chain input-name-udp {
- }
-
- chain input-name-vmap {
- type filter hook input priority -200; policy accept;
- }
- }
- root@debian:~# nft add map filter-name-vmap input-name-vmap { type inet_proto : verdict \; }
- root@debian:~# nft list table filter-name-vmap
- table ip filter-name-vmap {
- map input-name-vmap {
- type inet_proto : verdict
- }
-
- chain input-name-tcp {
- }
-
- chain input-name-udp {
- }
-
- chain input-name-vmap {
- type filter hook input priority -200; policy accept;
- }
- }
- root@debian:~# nft add element filter-name-vmap input-name-vmap { tcp : jump input-name-tcp , udp : jump input-name-udp }
- root@debian:~# nft list table filter-name-vmap
- table ip filter-name-vmap {
- map input-name-vmap {
- type inet_proto : verdict
- elements = { tcp : jump input-name-tcp, udp : jump input-name-udp }
- }
-
- chain input-name-tcp {
- }
-
- chain input-name-udp {
- }
-
- chain input-name-vmap {
- type filter hook input priority -200; policy accept;
- }
- }
- root@debian:~# nft add rule filter-name-vmap input-name-vmap meta l4proto vmap @input-name-vmap
- root@debian:~# nft list table filter-name-vmap
- table ip filter-name-vmap {
- map input-name-vmap {
- type inet_proto : verdict
- elements = { tcp : jump input-name-tcp, udp : jump input-name-udp }
- }
-
- chain input-name-tcp {
- }
-
- chain input-name-udp {
- }
-
- chain input-name-vmap {
- type filter hook input priority -200; policy accept;
- meta l4proto vmap @input-name-vmap
- }
- }
如果我们需要匹配阻止从192.168.140.248访问本机的80端口的tcp流量应该怎么做呢?我们在匿名映射那里配置的是放心,这里我们配置阻止。
通过我们前篇文章介绍的sets集合,和本篇文章介绍的maps和vmaps,可以极大的提升防火墙策略的灵活程度,我们所介绍的示例只是基础的规则应用和原理,更多的功能搭配和更复杂的事项方式需要自己根据需求自行进行探索。