当服务器遭遇CC攻击、撞库扫描或恶意爬虫时,传统的逐个IP封禁方式会让防火墙规则数量爆炸,iptables在匹配数千条规则时性能会急剧下降。ufw本身只是一个iptables的前端管理工具,并不擅长处理海量IP集合。正确的做法是让ufw调用ipset,将大量IP地址聚合到一个哈希集合中,防火墙只需匹配一次集合即可完成封禁,效率提升几个数量级。
ipset的核心优势ipset是Linux内核提供的IP集合管理工具,它将大量IP地址、网段或端口存储在内核内存的哈希表中。防火墙匹配时不需要逐条遍历规则,而是通过哈希查找在O(1)时间内完成匹配。这意味着封禁100个IP和封禁10万个IP,对系统性能的影响几乎相同。ipset支持多种集合类型:hash:ip用于单个IP,hash:net用于网段,hash:ip,port用于IP加端口组合,bitmap:ip则适用于连续IP段且内存占用更小。
安装必要组件Ubuntu系统默认通常已安装ufw,但ipset可能需要手动安装。执行以下命令完成安装:
sudo apt update sudo apt install ipset ufw -y
确认ipset已正确加载内核模块:
sudo lsmod | grep ip_set
如果没有输出,手动加载模块:
sudo modprobe ip_set创建ipset集合
创建一个名为blacklist的IP哈希集合,timeout参数指定IP的默认过期时间(单位秒),合理的过期时间可以避免集合无限膨胀:
sudo ipset create blacklist hash:ip timeout 86400
这条命令创建了一个IP哈希集合,每个条目默认24小时后自动过期。如果想创建永久集合,去掉timeout参数即可。对于网段封禁场景,使用hash:net类型:
sudo ipset create blacknet hash:net
查看已创建的集合:
sudo ipset list手动管理IP封禁
向集合添加IP地址:
sudo ipset add blacklist 192.168.1.100
添加时可以指定该条目的独立过期时间(覆盖默认值):
sudo ipset add blacklist 192.168.1.100 timeout 3600
删除某个IP:
sudo ipset del blacklist 192.168.1.100
测试某个IP是否在集合中:
sudo ipset test blacklist 192.168.1.100
批量添加IP,将IP列表保存为文件每行一个IP,然后用脚本循环添加:
while read ip; do sudo ipset add blacklist "$ip" -exist; done < ip_list.txt
-exist参数表示如果IP已存在则静默跳过,避免脚本中断。
配置ufw调用ipsetipset创建好之后,需要让ufw在防火墙规则中引用这个集合。ufw本身不直接支持ipset语法,但可以通过修改底层iptables规则实现。编辑ufw的before.rules文件,在filter表部分添加ipset匹配规则:
sudo nano /etc/ufw/before.rules
在文件中找到*filter这一行,在它之后、COMMIT之前添加以下内容:
*filter :ufw-before-input - [0:0] :ufw-before-output - [0:0] :ufw-before-forward - [0:0] # 添加ipset黑名单规则 -A ufw-before-input -m set --match-set blacklist src -j DROP -A ufw-before-input -m set --match-set blacknet src -j DROP
这段规则的含义是:在ufw处理入站流量之前,先检查源IP是否在blacklist或blacknet集合中,如果匹配则直接DROP。规则放在ufw-before-input链中,确保它优先于ufw的其他放行规则执行。如果还需要封禁出站流量,在ufw-before-output链中添加类似规则:
-A ufw-before-output -m set --match-set blacklist dst -j DROP
保存文件后重新加载ufw使规则生效:
sudo ufw reload
验证规则是否生效,查看iptables的ufw-before-input链:
sudo iptables -L ufw-before-input -n -v
应该能看到包含match-set的两条DROP规则,并且数据包计数会随着被封IP的访问而增长。
持久化ipset集合ipset集合存储在内存中,系统重启后会丢失。需要配置持久化机制,安装ipset-persistent包:
sudo apt install ipset-persistent -y
安装过程中会提示保存当前集合,选择"是"。之后手动保存当前集合:
sudo ipset save | sudo tee /etc/iptables/ipsets
ipset-persistent通过systemd服务在开机时自动恢复集合,检查服务状态:
sudo systemctl status ipset-persistent
确保服务已启用:
sudo systemctl enable ipset-persistent自动化封禁策略
手动添加IP适合临时处理,面对持续攻击需要自动化。最常见的方式是通过日志分析自动提取恶意IP。以SSH暴力破解为例,使用fail2ban配合ipset能实现高效封禁。但如果你希望更轻量的方案,可以自己编写监控脚本。
下面是一个基于journalctl监控SSH失败登录并自动加入ipset的脚本示例:
#!/bin/bash
# 监控SSH失败登录并自动封禁
IPSET_NAME="blacklist"
THRESHOLD=5 # 24小时内失败次数阈值
journalctl -u ssh -f -n 0 | while read line; do
if echo "$line" | grep -q "Failed password"; then
ip=$(echo "$line" | grep -oP 'from \K[\d.]+')
if [ -n "$ip" ]; then
# 统计该IP在日志中的失败次数
count=$(journalctl -u ssh --since "24 hours ago" | grep "Failed password" | grep -c "$ip")
if [ "$count" -ge "$THRESHOLD" ]; then
sudo ipset add "$IPSET_NAME" "$ip" -exist
echo "$(date): Blocked $ip (failures: $count)"
fi
fi
fi
done
这个脚本实时跟踪SSH日志,当某个IP在24小时内失败次数达到阈值时自动加入ipset。将脚本设置为systemd服务即可实现开机自启和持续运行。
对于Web服务器,可以分析Nginx或Apache访问日志,提取异常高频访问的IP:
#!/bin/bash
# 分析Nginx日志封禁高频IP
IPSET_NAME="blacklist"
RATE_LIMIT=100 # 每分钟请求数阈值
LOG_FILE="/var/log/nginx/access.log"
tail -f "$LOG_FILE" | while read line; do
# 提取当前时间前一分钟的日志行数统计
current_minute=$(date +"%d/%b/%Y:%H:%M")
# 获取访问量异常的IP
awk -v minute="$current_minute" '$0 ~ minute {print $1}' "$LOG_FILE" | \
sort | uniq -c | sort -rn | \
while read count ip; do
if [ "$count" -gt "$RATE_LIMIT" ]; then
sudo ipset add "$IPSET_NAME" "$ip" -exist
echo "$(date): Rate-limited $ip ($count req/min)"
fi
done
sleep 60
done
高级技巧:分层封禁策略
对于生产环境,建议采用分层封禁策略。创建多个ipset集合,分别对应不同的封禁级别和过期时间:
# 短期封禁(1小时),用于临时频率限制 sudo ipset create block_tmp hash:ip timeout 3600 # 中期封禁(24小时),用于可疑行为 sudo ipset create block_mid hash:ip timeout 86400 # 永久封禁,用于确认的恶意IP sudo ipset create block_perm hash:ip
在ufw的before.rules中为三个集合都添加DROP规则:
-A ufw-before-input -m set --match-set block_perm src -j DROP -A ufw-before-input -m set --match-set block_mid src -j DROP -A ufw-before-input -m set --match-set block_tmp src -j DROP
自动化脚本根据行为的严重程度将IP加入不同级别的集合。例如,单次扫描加入block_tmp,持续扫描升级到block_mid,确认的攻击源直接加入block_perm。这种策略既避免了误封正常用户的长期影响,又能对真正的恶意IP实施持久封禁。
性能监控与调优大规模封禁场景下需要关注ipset的性能表现。查看集合的哈希表使用情况:
sudo ipset list blacklist | head -20
输出中会显示SizeInMemory(内存占用)和Number of entries(条目数量)。hash:ip类型的集合默认最大条目数为65536,如果需要更大容量,创建时指定maxelem参数:
sudo ipset create blacklist hash:ip maxelem 500000
哈希表大小也会影响性能,通过hashsize参数调整桶数量,合理的桶数量能减少哈希冲突:
sudo ipset create blacklist hash:ip hashsize 16384 maxelem 500000
使用ipset的统计功能查看匹配命中率,在iptables规则后添加注释以便识别:
sudo iptables -L ufw-before-input -n -v | grep match-set
数据包计数列显示了规则实际拦截的流量,这个数字可以帮助评估封禁策略的有效性。如果某个集合的命中率很低,说明里面的IP可能已经不再活跃,可以考虑清理过期条目或调整策略。
故障排查如果添加了规则但IP没有被封禁,按以下步骤排查:首先确认ipset集合中确实存在该IP:
sudo ipset test blacklist 目标IP
然后检查iptables规则是否正确加载:
sudo iptables -S ufw-before-input | grep match-set
如果规则存在但没有数据包计数增长,可能是规则顺序问题。ufw-before-input链中,ipset规则必须放在放行规则之前。使用sudo iptables -L ufw-before-input -n -v --line-numbers查看规则顺序,必要时调整before.rules中规则的位置。
另一个常见问题是ipset模块未加载,导致iptables无法识别match-set。检查内核模块:
lsmod | grep xt_set
如果没有输出,执行sudo modprobe xt_set加载。对于使用云服务器的情况,某些定制内核可能默认不包含ipset模块,需要更换为标准内核或自行编译模块。
虽然前面提到了轻量脚本方案,但fail2ban仍然是生产环境中最成熟的自动化封禁工具。让fail2ban使用ipset而非iptables添加规则,可以大幅提升大规模封禁时的性能。编辑fail2ban的jail.local配置文件,将banaction改为ipset:
[DEFAULT] banaction = iptables-ipset-proto6 banaction_allports = iptables-ipset-proto6-allports
fail2ban会自动创建名为f2b-xxx的ipset集合并管理其中的IP。查看fail2ban创建的集合:
sudo ipset list | grep f2b
这种集成方式结合了fail2ban灵活的检测规则和ipset的高效匹配能力,是处理大规模封禁的最佳实践。
ufw与ipset的组合为Ubuntu服务器提供了一套轻量且高效的大规模IP封禁方案。核心思路是将IP列表从iptables规则链中剥离出来,放入内核级哈希表进行快速匹配。通过合理的分层封禁策略、自动化脚本和持久化配置,可以构建一个既能应对突发攻击又不影响正常服务的防护体系。这套方案完全基于系统自带工具,不需要安装额外的WAF或反向代理,资源占用极低,特别适合中小规模服务器的安全加固。
