处理大规模网络流量时,防火墙规则数量一旦突破数万条,iptables 的线性遍历机制就会成为系统性能的噩梦。当黑名单 IP 达到数十万甚至百万级别时,每个数据包都要在数千条规则中逐一比对,CPU 负载会直线飙升,网络延迟显著增加,严重时直接导致服务不可用。这不是危言耸听,而是真实生产环境中经常遇到的困境。解决这个问题的关键,就是把 O(n) 的链表匹配优化为 O(1) 的哈希查找,而 ipset 正是完成这项任务的利器。

ipset 与 iptables 的本质区别

理解为什么 ipset 能解决性能问题,需要先明白 iptables 的工作机制。iptables 的规则按照链表顺序存储,当数据包到达时,内核从第一条规则开始逐条匹配,直到命中或遍历完整个链。如果你定义了 10 万条 IP 黑名单规则,每个数据包最坏情况下需要比对 10 万次。这在大流量场景下是不可接受的。ipset 则完全不同,它在内核空间中维护一个哈希表结构,无论集合中有多少个 IP 地址,查找时间都接近常数级别。一条 iptables 规则配合一个包含 100 万个 IP 的 ipset 集合,匹配效率与集合大小几乎无关。这就是性能提升的根本原因。

ipset 支持的数据类型与适用场景

ipset 提供了多种集合类型,针对不同场景选择正确的类型至关重要。hash:ip 是最常用的类型,存储单个 IP 地址,适用于 IP 黑名单或白名单。hash:net 存储网段,比如 192.168.1.0/24,适合封禁整个 IP 段。hash:ip,port 同时存储 IP 和端口组合,可以精确到某个 IP 的特定端口进行封禁。hash:net,iface 则增加了网络接口维度,适合多网卡环境下的精细化控制。还有 bitmap:ip 类型,它使用位图存储连续的 IP 范围,内存占用极低,但要求 IP 范围必须连续。对于百万级黑名单场景,hash:ip 是首选,因为它在大规模数据下仍能保持稳定的查找性能。

安装与基础配置

CentOS 7 及更高版本默认仓库中已包含 ipset,直接通过 yum 安装即可:

yum install -y ipset

安装完成后,建议将 ipset 服务设置为开机自启,并确保在 iptables 启动之前加载集合:

systemctl enable ipset
systemctl start ipset

ipset 的持久化机制依赖 /etc/sysconfig/ipset 配置文件,服务停止时会自动将当前集合保存到此文件,启动时自动恢复。也可以手动执行 ipset save 命令将当前所有集合导出到指定文件。

创建第一个 IP 黑名单集合

创建一个名为 blacklist 的 hash:ip 类型集合,基础命令如下:

ipset create blacklist hash:ip

在实际生产环境中,建议添加一些关键参数来优化性能和容量。hashsize 参数定义哈希表的初始桶数量,数值越大哈希冲突越少但占用内存越多。maxelem 参数定义集合能存储的最大元素数量。timeout 参数可以为每个条目设置过期时间,到期自动删除,非常适合临时封禁场景。一个面向百万级规模的集合创建命令应该这样写:

ipset create blacklist hash:ip hashsize 4096 maxelem 1000000 timeout 86400

hashsize 设置为 4096 意味着初始有 4096 个哈希桶,对于百万级数据量,这个值可以适当调大以减少冲突。maxelem 直接设定为 1000000,明确告诉内核预留足够空间。timeout 86400 表示每个 IP 默认 24 小时后自动解封,如果不需要自动过期可以省略此参数。

添加、删除与查询 IP 地址

向集合中添加 IP 地址的操作非常简单:

ipset add blacklist 192.168.1.100

批量添加时,不要循环调用 ipset add 命令,因为每次调用都会触发内核态切换,效率极低。正确做法是将 IP 列表写入文件,然后用 ipset restore 命令一次性导入:

# 准备 ipset 格式的文本文件,内容如:
add blacklist 10.0.0.1
add blacklist 10.0.0.2
add blacklist 10.0.0.3

# 批量导入
ipset restore < /path/to/ip_list.txt

删除单个 IP 使用 ipset del 命令,清空整个集合用 ipset flush,销毁集合用 ipset destroy。测试某个 IP 是否在集合中,使用 ipset test 命令,返回码为 0 表示存在:

ipset test blacklist 192.168.1.100 && echo "IP已封禁"

查看集合统计信息用 ipset list blacklist,输出中会显示集合类型、当前元素数量、内存占用等关键指标,这些信息对监控和调优非常重要。

将 ipset 与 iptables 联动

创建好 ipset 集合后,需要将其嵌入到 iptables 规则中才能真正生效。核心规则只有一条:

iptables -I INPUT -m set --match-set blacklist src -j DROP

这条规则的含义是,所有进入本机的数据包,如果源 IP 地址匹配 blacklist 集合中的任意条目,直接丢弃。注意规则放置的位置很重要,建议放在 INPUT 链的最前面,避免黑名单 IP 被后续的 ACCEPT 规则放行。如果服务器同时承担转发功能,还需要在 FORWARD 链上添加类似规则:

iptables -I FORWARD -m set --match-set blacklist src -j DROP

保存 iptables 规则同样重要,CentOS 中使用 service iptables save 或 iptables-save > /etc/sysconfig/iptables 确保持久化。务必保证 ipset 服务在 iptables 之前启动,否则 iptables 恢复规则时找不到对应的集合会导致恢复失败。

百万级黑名单的性能调优策略

当黑名单规模达到百万级别时,默认参数往往不够用,需要针对性地调优。首先是 hashsize 的调整,它直接影响哈希冲突率。hashsize 应该设置为预期最大元素数量的四分之一到二分之一之间。对于 100 万条目,hashsize 设置为 262144 或 524288 比较合适。注意 hashsize 必须是 2 的幂次方,且创建后无法修改,只能销毁重建。

内核参数调优同样关键。ipset 在内核中通过 netfilter 框架工作,相关的连接跟踪表容量需要相应增大。编辑 /etc/sysctl.conf 添加以下参数:

net.netfilter.nf_conntrack_max = 2097152
net.netfilter.nf_conntrack_buckets = 524288
net.netfilter.nf_conntrack_tcp_timeout_established = 1200

执行 sysctl -p 使其生效。nf_conntrack_max 控制连接跟踪表的最大条目数,百万级黑名单场景下连接数通常也很高,默认的 65536 远远不够。nf_conntrack_buckets 是哈希桶数量,同样需要相应调大。如果服务器内存充足,还可以调整 ipset 的内存分配行为,在创建集合时添加 skbinfo 参数开启扩展记账功能,但会增加内存开销,按需开启。

内存占用是需要重点关注的指标。hash:ip 类型每个条目大约占用 40 到 60 字节,100 万个 IP 大约需要 50MB 到 60MB 内存。加上哈希表本身的开销,总共约 80MB 到 100MB。这个内存占用量对于现代服务器来说完全在可接受范围内。如果内存紧张,可以考虑使用 bitmap:ip 类型,但它要求 IP 地址连续,灵活性较差。

自动化黑名单更新机制

静态黑名单在实际攻防中效果有限,攻击者会不断更换 IP。构建动态更新机制才能真正发挥 ipset 的价值。常见的做法是编写定时脚本,从威胁情报源拉取最新黑名单并更新到集合中。以下是一个实用的更新脚本框架:

#!/bin/bash
# 定义临时集合名称,避免更新期间影响业务
TMP_SET="blacklist_tmp"
LIVE_SET="blacklist"

# 创建临时集合
ipset create $TMP_SET hash:ip hashsize 262144 maxelem 1000000

# 从多个威胁情报源拉取 IP 列表并导入临时集合
curl -s https://example.com/feeds/ips.txt | while read ip; do
    ipset add $TMP_SET $ip 2>/dev/null
done

# 原子性替换:交换临时集合与正式集合
ipset swap $TMP_SET $LIVE_SET

# 销毁旧集合(此时旧集合已变为临时集合的名称)
ipset destroy $TMP_SET

这个脚本的核心技巧在于使用 ipset swap 命令进行原子性替换。swap 操作是瞬时的,不会出现规则空窗期,保证业务流量不受影响。如果直接 flush 再重新添加,中间会有短暂时间黑名单失效,这在安全场景下是不可接受的。脚本可以配置为每 30 分钟或每小时执行一次,根据威胁情报的更新频率来定。

多维度集合的分层管理

实际生产环境中,不建议把所有黑名单 IP 都塞进一个集合。分层管理能带来更好的灵活性和可维护性。可以按照封禁级别创建多个集合:level1 存放确认的恶意 IP,永久封禁;level2 存放可疑 IP,设置 24 小时过期时间;level3 存放临时封禁的 IP,1 小时后自动解封。还可以按地域创建集合,比如封禁特定国家的 IP 段。iptables 规则中按优先级匹配不同集合:

iptables -I INPUT -m set --match-set level1 src -j DROP
iptables -I INPUT -m set --match-set level2 src -j DROP
iptables -I INPUT -m set --match-set level3 src -j DROP

这种分层架构便于对不同级别的威胁采取差异化处置,也方便后期分析和统计。配合日志记录,可以清晰了解各级别黑名单的命中情况,为策略调整提供数据支撑。

监控与故障排查

部署 ipset 黑名单后,持续的监控必不可少。定期检查集合的元素数量是否异常增长,内存占用是否在合理范围内。可以通过脚本定时采集 ipset list 的输出并存入监控系统。关注 iptables 规则的命中计数,确认黑名单规则确实在生效:

iptables -L INPUT -v -n | grep "match-set blacklist"

输出中的 pkts 和 bytes 字段就是命中次数和流量字节数。如果这个数字长期为零,说明黑名单没有匹配到任何流量,需要检查规则顺序或集合内容是否正确。常见故障包括 ipset 服务未启动导致 iptables 恢复失败、集合名称拼写错误、hashsize 设置过小导致性能下降等。排查时先确认 ipset list 能否正常输出集合信息,再检查 iptables 规则中 --match-set 参数引用的集合名称是否一致。

ipset 的日志功能默认不开启,如果需要记录被黑名单拦截的数据包,可以在 DROP 之前添加一条 LOG 规则,但注意日志量可能非常巨大,建议只对部分集合开启或使用限速日志。

与其他安全组件的协同

ipset 并非孤立的安全工具,它可以与 fail2ban 紧密集成。fail2ban 默认使用 iptables 直接添加规则,在大量 IP 被封禁后性能同样会下降。配置 fail2ban 使用 ipset 作为后端,可以大幅提升其处理能力。在 fail2ban 的 action 配置中指定 ipset 动作即可。此外,如果使用 DDoS 防护设备或云防火墙,ipset 可以作为本地层面的补充防线,处理那些绕过上游防护的漏网之鱼。在 CDN 回源场景中,将 CDN 节点 IP 加入 ipset 白名单,配合黑名单使用,能构建更精细的访问控制体系。

百万级 IP 黑名单在 CentOS 上稳定运行的关键,在于理解 ipset 的哈希查找原理,合理规划集合类型和参数,建立自动化的更新流程,并配合持续的监控与调优。这套方案经过大量生产环境验证,能将防火墙匹配效率从线性时间降低到常数时间,即使黑名单规模持续增长,系统性能依然保持稳定。对于面临大规模扫描、暴力破解、CC 攻击的服务器来说,这是投入产出比最高的优化手段之一。