4. nftables集合与映射
在nftables中,集合(set)与映射(map)是解决“同一规则要匹配大量地址、端口或网段”的核心工具。如果不用集合,一条规则只能匹配一个具体值,遇到几十个IP或端口时就要写几十条规则,既难维护又影响性能。集合允许把多个元素放进一个容器,规则只需引用这个容器;映射则更进一步,把“匹配值”与“结果值”关联起来,实现类似查表转发的效果。理解匿名集合、命名集合、区间集合、连接集合以及map与verdict map的差异,是写出高效nftables规则集的关键。
匿名集合与命名集合
匿名集合直接写在规则内部,用花括号列出元素,生命周期只跟随这条规则,不能被其他规则复用,也不能单独增删元素。它适合临时、一次性的匹配场景。例如只允许来自三个管理地址的SSH访问:
nft add rule inet filter input tcp dport 22 ip saddr { 10.0.0.5, 10.0.0.6, 10.0.0.7 } accept
命名集合则先创建、后引用,拥有独立名字,可被多条规则共享,也能动态增删元素。创建时通常要声明元素类型和可选的超时、大小限制:
nft add set inet filter mgmt_hosts { type ipv4_addr; }
nft add element inet filter mgmt_hosts { 10.0.0.5, 10.0.0.6 }
nft add rule inet filter input tcp dport 22 ip saddr @mgmt_hosts accept
引用命名集合时用 @集合名。命名集合的优势在于:规则本身不变,只需增删集合元素即可调整策略,非常适合与自动化脚本、fail2ban类工具或配置管理系统配合。
区间集合与连接集合
当需要匹配连续范围时,使用区间集合。区间用 起始-结束 表示,nftables内部会将其优化为区间树,匹配效率远高于逐条列举。例如封禁一段地址:
nft add set inet filter blacklist { type ipv4_addr; flags interval; }
nft add element inet filter blacklist { 192.168.10.0/24, 203.0.113.100-203.0.113.200 }
注意 flags interval 必须声明,否则nftables会把 192.168.10.0/24 当作单个地址处理而报错。区间集合也支持CIDR前缀与显式区间混用。
连接集合(concatenation set)用于同时匹配多个字段的组合,元素类型写成逗号分隔的多个类型。典型场景是“源地址+目的端口”白名单:
nft add set inet filter svc_allow { type ipv4_addr . inet_service; }
nft add element inet filter svc_allow { 10.0.0.8 . 443, 10.0.0.9 . 8080 }
nft add rule inet filter input ip saddr . tcp dport @svc_allow accept
连接集合把原本需要两条规则分别匹配的“与”关系压缩为一次查表,既减少规则数量,也避免因规则顺序造成的误判。
映射与verdict map
映射(map)是“键→值”的集合,规则匹配键后可以取出值用于后续判断或改写。普通map的值是数据,例如把源地址映射到标记:
nft add map inet filter mark_map { type ipv4_addr : mark; }
nft add element inet filter mark_map { 10.0.0.8 : 0x10, 10.0.0.9 : 0x20 }
nft add rule inet filter input ip saddr map @mark_map meta mark set ip saddr map @mark_map
verdict map的值是裁决动作(accept、drop、jump等),因此可以直接用一次查表决定数据包命运,非常适合做“按源地址分流”的策略路由式过滤:
nft add map inet filter policy_map { type ipv4_addr : verdict; }
nft add element inet filter policy_map { 10.0.0.8 : accept, 10.0.0.9 : drop, 10.0.0.10 : jump guest_chain }
nft add rule inet filter input ip saddr map @policy_map
这条规则没有显式写accept或drop,裁决完全由map查表结果决定。若键不存在,规则不匹配,继续执行后续规则,因此通常要在map规则之后补一条默认drop或return。
动态更新与工程实践
命名集合和映射的真正价值在于运行时更新。管理员可以在不重载规则集的情况下增删元素,已建立的连接不受影响。常用命令包括:
nft add element inet filter mgmt_hosts { 10.0.0.20 }
nft delete element inet filter mgmt_hosts { 10.0.0.6 }
nft list set inet filter mgmt_hosts
nft flush set inet filter mgmt_hosts
对于需要自动过期的场景,可以在创建集合时加 timeout 和 size:
nft add set inet filter temp_block { type ipv4_addr; flags timeout; timeout 1h; size 65536; }
这样加入的元素默认1小时后自动移除,适合临时封禁。若元素需要不同超时,可在添加时单独指定:nft add element inet filter temp_block { 203.0.113.50 timeout 30m }。
实际排障时,常见问题有三类。第一,忘记给区间集合加 flags interval,导致CIDR被当作单地址而报错。第二,连接集合的元素类型顺序与规则中字段顺序不一致,例如集合声明为 inet_service . ipv4_addr,规则却写成 ip saddr . tcp dport,查表必然失败。第三,verdict map未命中时没有兜底规则,数据包会继续向下匹配,可能被后续宽松规则放行。建议在verdict map规则之后显式添加默认策略,并用 nft list ruleset 与 nft -a list ruleset 核对规则句柄和集合内容。
综合来看,匿名集合适合写死在规则里的少量固定值;命名集合适合需要复用和动态维护的清单;区间集合解决连续范围匹配;连接集合解决多字段组合匹配;map用于键值关联改写;verdict map用于查表裁决。掌握这六种形态的声明方式、引用语法和更新命令,就能把nftables规则集从“逐条堆叠”提升为“结构化查表”,在地址规模增长时仍保持清晰与高效。