ipset通过哈希表实现o(1)查表,显著优于iptables线性匹配的o(n)复杂度;需显式设置maxelem防溢出,批量导入优先使用restore命令,动态更新应采用swap原子切换。

直接用 iptables -A 加上万条 -s IP -j DROP 规则,匹配速度会断崖式下跌,因为规则链是线性结构,每次查包都要逐条比对,复杂度是 O(n)。而 ipset 把 IP 列表转成内核级哈希表,iptables 只需一次查表,复杂度接近 O(1),10 万条和 100 万条几乎没差别。
选对类型和参数,避免运行时崩溃
hash:ip 和 hash:net 是最常用也最高效的两类,但不能混用:
- 封单个 IPv4 地址,用 hash:ip:内存低、插入快,命令如
ipset create blackv4 hash:ip maxelem 1200000 hashsize 16384 - 封网段(比如 /24、/16),必须用 hash:net:它原生支持 CIDR,
hash:ip对 192.168.1.0/24 这类会报错 - maxelem 必须显式设大:默认仅 65536,超限直接报 “Set is full”,建议按峰值上浮 20%
- hashsize 设为略大于 maxelem 的 2 的幂(如 8192、16384),能显著减少哈希冲突,提升命中率
批量导入不走 shell 循环,改用 restore
用 while read ip; do ipset add blackv4 $ip; done 导入 10 万行,实际要启动 10 万个进程,几分钟都跑不完,还容易中断失败。
- 正确做法是生成标准 restore 文件,例如
blacklist.ipset内容为:
create blackv4 hash:ip maxelem 1200000
add blackv4 1.1.1.1
add blackv4 2.2.2.2
add blackv4 192.168.1.0/24
- 执行
ipset restore -f blacklist.ipset,百万条秒级完成 - 原始列表是纯 IP 文本?用
awk '{print "add blackv4 " $1}' list.txt > blacklist.ipset一键转换 - 导入前确保集合未被 iptables 引用,否则 restore 会失败;可先临时删规则:
iptables -D INPUT -m set --match-set blackv4 src -j DROP
iptables 引用要准,位置和模块一个都不能少
集合建好了,但规则写错就完全不生效,常见硬伤有三个:
- 必须加载 -m set 模块,漏掉就报 “No chain/target/match by that name”
- 规则必须插在链首:
iptables -I INPUT,不能用-A追加;若前面已有-j ACCEPT,DROP 规则永远轮不到执行 - IPv4 集合(hash:ip / hash:net)只匹配 IPv4 流量,封 IPv6 要另建 hash:ip6 集合并加独立规则
动态更新不丢包,靠 swap 原子切换
直接 ipset flush blackv4 && ipset restore 会在清空瞬间放行所有黑名单 IP,形成安全窗口。
- 正确方式是维护两个集合:
blackv4(线上生效)和blackv4_new(后台构建) - 新名单准备就绪后,执行
ipset swap blackv4 blackv4_new,毫秒级切换,无连接中断 - 切完立刻
ipset destroy blackv4_new释放内核内存,否则残留空集合仍占资源 - swap 前确认两个集类型一致,否则报 “Set cannot be swapped: different types”











