温馨提示:
本文所述内容具有依赖性,可能因软硬条件不同而与预期有所差异,故请以实际为准,仅供参考。
本文旨在解决因需屏蔽海量 IP (上万个)导致的防火墙性能瓶颈问题。
方案基于 ipset 实现高效 (O(1) 复杂度) 的 IP 查询,并提供一个跨平台 (CentOS 7.4 / Ubuntu 16.04+)、支持双栈 (IPv4/IPv6)、白名单优先且支持手动更新的完整部署指南。
O(1)复杂度:执行时间恒定,无论输入数据的规模是 10 个、1 万个还是 1 亿个,执行该操作所需的时间几乎完全一样。
一、方案概述
- 核心技术:
ipset。将海量 IP 存入哈希表,防火墙仅需一条规则指向该集合,极大提升性能。 支持平台:
- CentOS 7.4 (使用
firewalld的direct规则) - Ubuntu 16.04+ (使用
ufw并修改before.rules/before6.rules)
- CentOS 7.4 (使用
核心功能:
- 双栈支持: 同时管理 IPv4 和 IPv6 的黑白名单。
- 白名单优先: 白名单规则 (
ACCEPT) 在黑名单规则 (DROP) 之前执行。 重启安全:
- Ubuntu: 使用
iptables-persistent服务。 - CentOS: 使用
@rebootcron 任务在启动时加载ipset列表。
- Ubuntu: 使用
- 手动更新: 提供一个统一脚本 (
update-ipsets.sh),在您更新中央列表后,登录服务器手动执行即可。
- OCI 兼容: 本方案已针对 Oracle Cloud (OCI) Ubuntu 实例的网络特性进行优化,确保
ufw不会阻断出站连接。
二、黑白名单准备
您需要在可通过 HTTP/HTTPS 访问的任意位置(如您自己的 Web 服务器、对象存储等)托管以下 4 个文本文件。
1、IPv4 白名单:whitelist.txt,每行一个 IPv4 地址或 CIDR 块 (如 1.2.3.4 或 10.0.0.0/8)。
2、IPv4 黑名单:blacklist.txt,每行一个 IPv4 地址或 CIDR 块。
3、IPv6 白名单:whitelist6.txt,每行一个 IPv6 地址或 CIDR 块 (如 2001:db8::1 或 2001:db8::/32)。
4、IPv6 黑名单:blacklist6.txt,每行一个 IPv6 地址或 CIDR 块。
⚠️ OCI (Oracle Cloud) 用户必须注意:
您的whitelist.txt(IPv4 白名单) 文件中 必须 包含以下 IP,否则服务器将无法访问网络和 DNS:
- OCI 元数据服务:
169.254.169.254- 您的 DNS 服务器: 运行
cat /etc/resolv.conf查看nameserver。如果是169.254.169.254则已包含。如果是公共 DNS (如8.8.8.8),也必须将其添加到白名单。
三、服务器配置 (一次性)
请根据您的操作系统,执行对应的一次性配置。
3.1. CentOS 7.4 (firewalld) 配置
注意:由于 CentOS 7.4 的 firewalld 版本过低,不支持 priority,我们使用 direct 规则
1、安装 ipset:
yum install -y ipset2、创建 ipset 集合 (永久):
# IPv4
firewall-cmd --permanent --new-ipset=whitelist --type=hash:net --option=family=inet --option=maxelem=10000
firewall-cmd --permanent --new-ipset=blacklist --type=hash:net --option=family=inet --option=maxelem=100000
# IPv6
firewall-cmd --permanent --new-ipset=whitelist6 --type=hash:net --option=family=inet6 --option=maxelem=10000
firewall-cmd --permanent --new-ipset=blacklist6 --type=hash:net --option=family=inet6 --option=maxelem=1000003、添加 direct 防火墙规则 (永久):
(此方法利用 _allow 链优先于 _deny 链执行的特性,实现白名单优先)
# IPv4 规则
firewall-cmd --permanent --direct --add-rule ipv4 filter IN_public_allow 0 -m set --match-set whitelist src -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter IN_public_deny 0 -m set --match-set blacklist src -j DROP
# IPv6 规则
firewall-cmd --permanent --direct --add-rule ipv6 filter IN_public_allow 0 -m set --match-set whitelist6 src -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv6 filter IN_public_deny 0 -m set --match-set blacklist6 src -j DROP4、重载 firewalld 使配置生效:
firewall-cmd --reload3.2. Ubuntu 16.04+ (ufw) 配置
注意:此配置已针对 OCI 兼容性优化,规则被放置在 ESTABLISHED 之后
1、安装 ipset 及持久化工具:
- Ubuntu 18.04 / 20.04:
apt-get update
apt-get install -y ipset iptables-persistent- Ubuntu 16.04:
apt-get update
apt-get install -y ipset ipset-persistent安装 iptables-persistent 时,会弹出粉色窗口询问是否保存当前 IPv4/IPv6 规则,均选择 <Yes>,防止服务器在 ufw 出现故障时完全失去防护
2、创建 ipset 集合 (运行时):
# IPv4
ipset create whitelist hash:net family inet maxelem 10000
ipset create blacklist hash:net family inet maxelem 100000
# IPv6
ipset create whitelist6 hash:net family inet6 maxelem 10000
ipset create blacklist6 hash:net family inet6 maxelem 1000003、修改 ufw IPv4 规则文件:
编辑 /etc/ufw/before.rules (vim /etc/ufw/before.rules),找到 RELATED,ESTABLISHED 规则块,在它之后、INVALID 规则之前,插入您的 ipset 规则:
...
# quickly process packets for which we already have a connection
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# --- 自定义 IPSETv4 规则 (必须在 ESTABLISHED 之后) ---
-A ufw-before-input -m set --match-set whitelist src -j ACCEPT
-A ufw-before-input -m set --match-set blacklist src -j DROP
# --- 结束 ---
# drop INVALID packets (logs these in loglevel medium and higher)
-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
...4、修改 ufw IPv6 规则文件:
编辑 /etc/ufw/before6.rules (vim /etc/ufw/before6.rules),同样,在 RELATED,ESTABLISHED 规则块之后插入:
...
# quickly process packets for which we already have a connection
-A ufw6-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw6-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# --- 自定义 IPSETv6 规则 (必须在 ESTABLISHED 之后) ---
-A ufw6-before-input -m set --match-set whitelist6 src -j ACCEPT
-A ufw6-before-input -m set --match-set blacklist6 src -j DROP
# --- 结束 ---
# multicast ping replies are part of the ok icmp codes for INPUT (rfc4890,
...5、保存所有 ipset (用于重启恢复):
mkdir -p /etc/iptables/
ipset save > /etc/iptables/ipsets6、重载 ufw:
ufw reload四、部署统一更新脚本
此脚本是您未来手动更新黑白名单的唯一工具。在所有服务器上执行:
1、创建脚本文件:
nano /usr/local/bin/update-ipsets.sh2、粘贴以下完整脚本内容:
请务必修改顶部的 4 个 _URL 变量为您自己的 URL:
#!/bin/bash
# --- 配置: 请修改为您自己的 URL ---
# (IPv4)
BLACKLIST_URL="http://your-server.com/blacklist.txt"
BLACKLIST_SET_NAME="blacklist"
BLACKLIST_MAX_ELEM=100000
WHITELIST_URL="http://your-server.com/whitelist.txt"
WHITELIST_SET_NAME="whitelist"
WHITELIST_MAX_ELEM=10000
# (IPv6)
BLACKLIST6_URL="http://your-server.com/blacklist6.txt"
BLACKLIST6_SET_NAME="blacklist6"
BLACKLIST6_MAX_ELEM=100000
WHITELIST6_URL="http://your-server.com/whitelist6.txt"
WHITELIST6_SET_NAME="whitelist6"
WHITELIST6_MAX_ELEM=10000
# --- 结束配置 ---
# 封装一个函数来处理 ipset 更新
# 用法: process_list <URL> <SET_NAME> <MAX_ELEM> <FAMILY> <TEMP_FILE>
process_list() {
local url=$1
local set_name=$2
local max_elem=$3
local family=$4
local list_file=$5
local temp_set_name="${set_name}_temp"
echo "正在处理 $set_name ($family)..."
# 1. 下载列表
curl -s -L "$url" -o "$list_file"
if [ $? -ne 0 ]; then
echo "错误: $set_name 列表下载失败: $url"
return 1
fi
if [ ! -s "$list_file" ]; then
echo "警告: $set_name 列表文件为空。将清空 $set_name 集合。"
fi
# 2. 创建临时 ipset (指定 family)
ipset create "$temp_set_name" hash:net maxelem "$max_elem" family "$family" -exist
ipset flush "$temp_set_name"
# 3. 填充临时 set
awk 'NF && !/^#/ && !seen[$0]++ {print "add '$temp_set_name' " $0 " -exist"}' "$list_file" | ipset restore
# 4. 原子化替换 (指定 family)
ipset create "$set_name" hash:net maxelem "$max_elem" family "$family" -exist
ipset swap "$temp_set_name" "$set_name"
# 5. 销毁临时 set
ipset destroy "$temp_set_name"
rm "$list_file"
echo "$set_name 已成功更新。"
}
# --- 执行 ---
echo "开始更新 IP 列表..."
process_list "$BLACKLIST_URL" "$BLACKLIST_SET_NAME" "$BLACKLIST_MAX_ELEM" "inet" "/tmp/blacklist.new.txt"
process_list "$WHITELIST_URL" "$WHITELIST_SET_NAME" "$WHITELIST_MAX_ELEM" "inet" "/tmp/whitelist.new.txt"
process_list "$BLACKLIST6_URL" "$BLACKLIST6_SET_NAME" "$BLACKLIST6_MAX_ELEM" "inet6" "/tmp/blacklist6.new.txt"
process_list "$WHITELIST6_URL" "$WHITELIST6_SET_NAME" "$WHITELIST6_MAX_ELEM" "inet6" "/tmp/whitelist6.new.txt"
# --- 持久化 (仅 Ubuntu) ---
if [ -f /usr/bin/apt-get ]; then
echo "Ubuntu/Debian: 正在保存所有 ipset 以便重启后持久化..."
mkdir -p /etc/iptables/
ipset save > /etc/iptables/ipsets
elif [ -f /usr/bin/yum ]; then
echo "CentOS/RHEL: ipset 运行时已更新。"
fi
echo "所有 IPv4 和 IPv6 列表更新完成。"
3、赋予执行权限:
chmod +x /usr/local/bin/update-ipsets.sh五、配置重启安全策略
此步骤确保服务器重启后,ipset 列表能自动恢复,它不会定时更新,只在开机时执行一次。
1、编辑 crontab:
crontab -e2、添加 @reboot 任务:
确保 crontab 中只有这一行关于此脚本的任务
# 系统重启时自动执行一次黑白名单更新,确保重启后防护不失效
@reboot /usr/local/bin/update-ipsets.sh > /dev/null 2>&1六、手动更新
所有配置已完成。您的日常维护流程如下:
1、您在本地编辑 4 个中央黑白名单文件 (blacklist.txt, whitelist.txt, blacklist6.txt, whitelist6.txt)。
2、您将这些文件上传到您的中央服务器 (确保 URL 正确)。
3、您 SSH 登录到每一台需要更新的服务器 (CentOS 或 Ubuntu)。
4、在每台服务器上,手动执行:
/usr/local/bin/update-ipsets.sh5、脚本会立即拉取 4 个列表,并以原子化方式热更新防火墙规则。
七、如何验证规则是否生效
您可以通过检查 iptables 的数据包计数器 (pkts) 来确认规则是否命中。
1、检查 ipset 列表是否加载 (所有系统):
ipset list检查 Number of entries: 是否大于 0
2、检查 iptables 命中计数 (CentOS):
# 检查 IPv4 黑名单
iptables -vL IN_public_deny -n
# 检查 IPv4 白名单
iptables -vL IN_public_allow -n
# 检查 IPv6 黑名单
ip6tables -vL IN_public_deny -n
# 检查 IPv6 白名单
ip6tables -vL IN_public_allow -n查看 pkts 列的数字,如果大于 0,说明规则已成功拦截/放行数据包
3、检查 iptables 命中计数 (Ubuntu):
# 检查 IPv4 黑/白名单
iptables -vL ufw-before-input -n
# 检查 IPv6 黑/白名单
ip6tables -vL ufw6-before-input -n在输出中找到 match-set whitelist 和 match-set blacklist 对应的行,查看 pkts 计数
Mac OS X 10_15_7Chrome 142.0.0.0来自 香港 的大神
这个主要是 iptables 默认情况下,匹配规则是线性的,效率很差;在 k8s 集群内,有大量的 service 可能随时在变更,service kube-proxy iptables 的更新效率差到离谱,特别是超大集群,换用 ipvs 就是 hash 匹配了,但还是有瓶颈;我是选择去掉 kube-proxy ,用 cilium 转发集群流量直接走 eBPF ,这样走内核,不过 iptables 这一层了。
Windows 10Chrome 136.0.0.0来自 福建 的大神
AI 说 iptables 性能最高,哈哈哈