高性能 IP 黑白名单防火墙方案

温馨提示:
本文所述内容具有依赖性,可能因软硬条件不同而与预期有所差异,故请以实际为准,仅供参考。

本文旨在解决因需屏蔽海量 IP (上万个)导致的防火墙性能瓶颈问题。

方案基于 ipset 实现高效 (O(1) 复杂度) 的 IP 查询,并提供一个跨平台 (CentOS 7.4 / Ubuntu 16.04+)、支持双栈 (IPv4/IPv6)、白名单优先且支持手动更新的完整部署指南。

O(1)复杂度:执行时间恒定,无论输入数据的规模是 10 个、1 万个还是 1 亿个,执行该操作所需的时间几乎完全一样。

一、方案概述

  • 核心技术: ipset。将海量 IP 存入哈希表,防火墙仅需一条规则指向该集合,极大提升性能。
  • 支持平台:

    • CentOS 7.4 (使用 firewallddirect 规则)
    • Ubuntu 16.04+ (使用 ufw 并修改 before.rules / before6.rules)
  • 核心功能:

    • 双栈支持: 同时管理 IPv4 和 IPv6 的黑白名单。
    • 白名单优先: 白名单规则 (ACCEPT) 在黑名单规则 (DROP) 之前执行。
    • 重启安全:

      • Ubuntu: 使用 iptables-persistent 服务。
      • CentOS: 使用 @reboot cron 任务在启动时加载 ipset 列表。
    • 手动更新: 提供一个统一脚本 (update-ipsets.sh),在您更新中央列表后,登录服务器手动执行即可。
  • OCI 兼容: 本方案已针对 Oracle Cloud (OCI) Ubuntu 实例的网络特性进行优化,确保 ufw 不会阻断出站连接。

二、黑白名单准备

您需要在可通过 HTTP/HTTPS 访问的任意位置(如您自己的 Web 服务器、对象存储等)托管以下 4 个文本文件。

1、IPv4 白名单whitelist.txt,每行一个 IPv4 地址或 CIDR 块 (如 1.2.3.410.0.0.0/8)。

2、IPv4 黑名单blacklist.txt,每行一个 IPv4 地址或 CIDR 块。

3、IPv6 白名单whitelist6.txt,每行一个 IPv6 地址或 CIDR 块 (如 2001:db8::12001:db8::/32)。

4、IPv6 黑名单blacklist6.txt,每行一个 IPv6 地址或 CIDR 块。

⚠️ OCI (Oracle Cloud) 用户必须注意:
您的 whitelist.txt (IPv4 白名单) 文件中 必须 包含以下 IP,否则服务器将无法访问网络和 DNS:

  1. OCI 元数据服务: 169.254.169.254
  2. 您的 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 ipset

2、创建 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=100000

3、添加 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 DROP

4、重载 firewalld 使配置生效

firewall-cmd --reload

3.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 100000

3、修改 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/ipsets

6、重载 ufw

ufw reload

四、部署统一更新脚本

此脚本是您未来手动更新黑白名单的唯一工具。在所有服务器上执行:

1、创建脚本文件

nano /usr/local/bin/update-ipsets.sh

2、粘贴以下完整脚本内容

请务必修改顶部的 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 -e

2、添加 @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.sh

5、脚本会立即拉取 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 whitelistmatch-set blacklist 对应的行,查看 pkts 计数


ArmxMod for Typecho
个性化、自适应、功能强大的响应式主题

推广

 继续浏览关于 linux防火墙安全黑名单高性能白名单 的文章

 本文最后更新于 2025/12/30 13:43:58,可能因经年累月而与现状有所差异

 引用转载请注明: VirCloud's Blog > 运维 > 高性能 IP 黑白名单防火墙方案

精选评论

  1. timochan
    timochan 回复

    Mac OS X 10_15_7Chrome 142.0.0.0来自 香港 的大神

    这个主要是 iptables 默认情况下,匹配规则是线性的,效率很差;在 k8s 集群内,有大量的 service 可能随时在变更,service kube-proxy iptables 的更新效率差到离谱,特别是超大集群,换用 ipvs 就是 hash 匹配了,但还是有瓶颈;我是选择去掉 kube-proxy ,用 cilium 转发集群流量直接走 eBPF ,这样走内核,不过 iptables 这一层了。

    1. 欧文斯
      欧文斯 回复

      Windows 10Chrome 136.0.0.0来自 福建 的大神

      AI 说 iptables 性能最高,哈哈哈