Linux 服务器与 OpenWRT 如何用 ipset 自动封堵攻击 IP 黑名单
简答:curl 定时拉取 TrapCloud 的 TXT 黑名单,整批导入 ipset 集合后原子 swap 切换, iptables 只需一条引用该集合的 DROP 规则;nftables 系统用 named set 同理。 全程无第三方依赖,几十万 IP 的匹配开销与几十个几乎相同。
直接把几万条 IP 写成防火墙规则是错误做法——规则是线性匹配,会拖垮转发性能。 正确姿势是内核集合(ipset 或 nftables set)加单条引用规则。 下面两个脚本可直接复制使用,内置行数安全闸(拉取失败绝不清空现有防线)与原子切换(更新瞬间无保护空窗)。
方案一:ipset + iptables(通用)
适合 CentOS / Debian / Ubuntu 及多数仍用 iptables 的系统,保存为 /usr/local/bin/tc_sync.sh 并加执行权限:
#!/bin/sh
# TrapCloud 攻击 IP 黑名单 -> ipset 自动同步(iptables 方案)
URL="https://www.trapcloud.cn/api/export?key=你的APIKey&type=all&level=all&service=all&hours=72&format=txt"
SET="trapcloud"
TMP="/tmp/tc_block.txt"
curl -fsS -m 60 "$URL" -o "$TMP" || exit 1
# 安全闸:必须是合法 IP 且条数达标,否则保留现有名单
CNT=$(grep -Ec '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' "$TMP")
[ "$CNT" -lt 100 ] && exit 1
ipset create "$SET" hash:ip maxelem 300000 -exist
ipset create "$SET"_tmp hash:ip maxelem 300000 -exist
ipset flush "$SET"_tmp
sed -n -E 's/^([0-9]{1,3}(\.[0-9]{1,3}){3})$/add '"$SET"'_tmp \1/p' "$TMP" \
| ipset restore -!
ipset swap "$SET"_tmp "$SET" # 原子切换,无保护空窗
ipset destroy "$SET"_tmp
# 首次运行时挂上 DROP 规则(已存在则跳过)
iptables -C INPUT -m set --match-set "$SET" src -j DROP 2>/dev/null \
|| iptables -I INPUT -m set --match-set "$SET" src -j DROP方案二:nftables(新系统 / OpenWRT fw4)
Debian 12+、RHEL 9+ 与 fw4 版 OpenWRT 默认 nftables:
#!/bin/sh
# nftables 方案(Debian 12+ / RHEL 9+ / OpenWRT fw4)
URL="https://www.trapcloud.cn/api/export?key=你的APIKey&type=all&level=all&service=all&hours=72&format=txt"
TMP="/tmp/tc_block.txt"
curl -fsS -m 60 "$URL" -o "$TMP" || exit 1
CNT=$(grep -Ec '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' "$TMP")
[ "$CNT" -lt 100 ] && exit 1
# 首次运行:建表、建集合、挂一条 drop 规则
nft list table inet trapcloud >/dev/null 2>&1 || {
nft add table inet trapcloud
nft "add set inet trapcloud blocklist { type ipv4_addr; size 300000; }"
nft "add chain inet trapcloud input { type filter hook input priority -10; }"
nft add rule inet trapcloud input ip saddr @blocklist drop
}
# 整批替换集合内容
{
echo "flush set inet trapcloud blocklist"
sed -n -E 's/^([0-9]{1,3}(\.[0-9]{1,3}){3})$/add element inet trapcloud blocklist { \1 }/p' "$TMP"
} > /tmp/tc_block.nft
nft -f /tmp/tc_block.nft定时更新
# 每小时同步一次(订阅配额每天 100 次,24 次/天在配额内)
17 * * * * /usr/local/bin/tc_sync.sh >/dev/null 2>&1OpenWRT 用户也可以不写脚本:banIP 插件支持自定义源(Custom Feed), 把导出 URL 配置为自定义黑名单源即可。等级与时间窗的选择同其它设备: hours=72 全量约十几万条,level=high 约数万条,level=critical 约数千条, 小内存路由器建议从 critical 起步。
常见问题
用 ipset 而不是逐条 iptables 规则。ipset 是内核哈希集合,几十万个 IP 的匹配开销与几十个几乎相同;iptables 只需一条引用该集合的 DROP 规则。名单更新用「新建临时集合、整批导入、原子 swap」的方式,更新瞬间也不会出现保护空窗。
不会。ipset 的 hash:ip 集合按哈希查找,单包匹配是常数时间,与条目数无关;内存占用在几十 MB 量级。主流家用路由器跑 OpenWRT 也能承载,条目上限可通过 maxelem 参数设定。
ipset 集合存在于内核内存,重启即清空。解决办法:把同步脚本挂进 cron(如每小时一次),开机后第一次执行就会重建集合;要求开机即时生效的,可在网络就绪后的启动脚本里先跑一次同步脚本。
两种方式:一是 banIP 插件支持自定义源(Custom Feed),把 TrapCloud 导出 URL 配置为自定义黑名单源即可;二是直接用本文的 ipset / nftables 脚本加 crontab,fw4(nftables)版本的 OpenWRT 用 nft 方案。
脚本的自动拉取需要订阅 API Key。免费档可在邮箱注册后每天手动下载 1 次近 24 小时黑名单(TXT 纯文本),保存为 ip.txt 后用脚本里的导入部分(ipset restore + swap)手动灌入,同样可用。
这是安全闸:如果网络故障或鉴权失败导致拉到空文件或错误页,直接灌入会把现有防护名单清空。脚本先校验内容是合法 IP 且条数达标,不达标就保留现有名单不动,宁可这一轮不更新也不清空防线。