nft set 不能替代 ipset 的 timeout 功能,因其不支持 per-entry 超时,添加 timeout 会报语法错误;需用 nft map 存时间戳并由用户态判断过期。

为什么 nft set 不能直接替代 ipset 的 timeout 功能
因为 nft set 本身不支持 per-entry 超时(timeout),只有 nft map 配合 ct timeout 或用户态定时器才能模拟。你写 add element inet Filter blackhole { 192.168.1.10 timeout 30s } 会报错:Error: syntax error, unexpected timeout。
常见错误现象:照着 ipset 的用法往 nft set 里加 timeout,命令直接失败;或者误以为 auto-merge 或 gc-interval 能清理过期项——它们只管内存回收,不管逻辑过期。
-
nft set适合静态或长期有效的黑白名单(如固定封禁 IP) - 需要自动过期的条目,必须用
nft map+ 用户态脚本(比如用date和awk定期扫描清理)或配合 conntrack 超时策略 - 性能上,
set查询是 O(1) 哈希查找;map在无索引 key 时也是 O(1),但维护成本更高
如何用 nft map 实现带时间戳的动态黑名单
核心思路:把 IP 当 key,把 unix 时间戳当 value,查表时用当前时间减去 value 判断是否超时。nft 本身不提供时间函数,所以匹配逻辑必须由用户态完成,nft 只负责存和查。
使用场景:ssh 暴力破解临时封禁(封 10 分钟)、http 接口限流后的冷却期、CI 测试环境临时放行。
- 定义 map:
nft add map inet filter ip_blacklist { type ipv4_addr : time; }(注意:time是伪类型,实际存整数秒) - 插入带时间戳条目:
echo $(date +%s) | xargs -I{} nft add element inet filter ip_blacklist { 192.168.1.20 : {} } - 匹配规则要分两步:先
lookup获取时间戳,再由 shell 脚本判断是否过期;nft 规则本身不能做减法运算 - 兼容性提醒:
time类型在 kernel ipv4_addr : ct label + 外部标记,或退化为定期nft delete
ipset 迁移到 nft 的三步落地检查清单
不是所有 ipset 功能都有直接对应,迁移前必须逐项验证,否则上线后丢包或漏封。
- 检查
ipset类型:如果原用hash:net,iface(网段+接口绑定),nft 没有等价结构,只能拆成多个set或改用fib表匹配 - 确认 timeout 粒度:ipset 支持毫秒级
timeout,nft map 存的是秒级整数,误差在 1 秒内可接受,但高频短时封禁(如每 500ms 封一个)不适合 - 验证原子性:ipset 的
add/del是原子的;nft 的add element单条是原子的,但批量操作需用nft -f加载脚本,否则中间状态可能生效 - 路径依赖:旧脚本里写的
/sbin/ipset -L blacklist得替换成nft list map inet filter blacklist,注意输出格式完全不同,解析逻辑要重写
真正容易被忽略的点:规则加载顺序与 set 更新时机
很多人以为只要 nft add element 成功,新 IP 下一秒就进黑名单——其实不一定。nft 规则匹配发生在数据包进入 hook 点的瞬间,而 set 内容更新是异步生效的,尤其在高并发插入时,存在极短窗口期(微秒级)导致漏匹配。
更麻烦的是:如果你用 cron 每分钟 reload 整个 ruleset(nft -f /etc/nftables.conf),所有 set 会被清空重建,期间新连接可能绕过黑名单。
- 正确做法:对活跃 set 使用
nft add/delete element增量更新,永不 reload 全局配置 - 避免在
preroutinghook 里直接 lookup 大型 set(>10 万条),kernel 查找延迟会上升,建议拆成多个小 set 按地域/用途分离 - 调试时别只信
nft list set,要用tcpdump -n -i any host 192.168.1.20实测封禁效果,因为路由决策可能早于 nft hook