1. 脚本概述

cn-firewall-permit.sh 是一个高度自动化的 Linux 防火墙配置脚本。其核心目标是构建一个白名单机制的防火墙,仅允许来自中国 IP 段服务器所在内网网段已建立的连接(例如响应外部请求)以及可选的 SSH 服务访问服务器,从而极大地收缩暴露面,有效抵御来自国外的大部分网络扫描和攻击。

2. 核心功能

  • 白名单防火墙:将防火墙默认策略设置为 DROP(拒绝所有),然后通过 iptables/firewalldipset 技术,仅放行中国 IP、内网 IP、已建立连接和可选 SSH。

  • 动态更新中国 IP 段

    • 全量获取:定期从 APNIC 官方获取最新的中国 IP 分配数据。

    • ipset 管理:将获取到的 IP 段加载到 ipset 集合中,利用 ipset 的高效查找性能,避免为每个 IP 段创建单独的防火墙规则,提升规则匹配效率。

    • 原子性更新:采用双 ipset (例如 cn_ipv4cn_ipv4_temp) 交换的方式更新 IP 列表。先将新数据加载到临时集,再通过 ipset swap 命令瞬间交换两个集合的内容,确保更新过程中防火墙规则始终生效,无安全间隙。

    • 批量加载优化:(新增优化点)使用 ipset restore 命令替代循环 ipset add,将所有 add 命令一次性传递给内核,显著提升大量 IP 段(如数十万条)的加载速度。

  • 智能内网网段识别与放行

    • 自动发现:(优化逻辑)扫描所有网卡,自动识别并计算出属于 RFC1918 标准的内网网段(如 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8)。

    • 移除兜底:(变更)如果未发现任何 RFC1918 网段,脚本不再自动添加这些标准私有网段作为“兜底”,仅放行回环地址和用户自定义的网段。

    • 自定义网段:允许用户交互式或通过配置文件添加自定义的内网网段。

  • SSH 访问保障:可选择性地放行 SSH 服务端口(默认 22),确保管理员能够远程管理服务器。

  • IPv6 支持:可选择性地启用 IPv6 规则,同步中国 IPv6 网段并处理 IPv6 内网网段。

  • 多系统兼容:自动识别并适配主流 Linux 发行版(如 RHEL/CentOS/Fedora, Debian/Ubuntu, Alpine, openSUSE, Arch)的包管理器(yum/dnf/apt/apk/zypper/pacman)安装依赖。

  • 安全与健壮性

    • 依赖自动安装:自动检测并安装所需依赖(iproute2, ipset, iptables, firewalld, wget, bc 等)。

    • 防火墙类型检测:自动检测 firewalldiptables,并优先使用 firewalld

    • 规则备份:在修改防火墙规则前自动备份当前状态。

    • 网络连通性预检:配置前检查网关、DNS、SSH 端口连通性,降低配置错误导致断网的风险。

    • 配置后验证:配置完成后 ping 外网测试连通性,失败时自动回滚到备份。

    • 紧急恢复模式:提供 --recovery 选项,当防火墙配置错误导致无法访问时,可以快速恢复网络访问。

    • 错误处理优化:(修复点)修复了 set -e 在空数组上 grep 时的退出问题。

  • 自动化运维

    • 定时任务:自动创建 cron 任务,按设定频率更新中国 IP 段列表。

    • 静默模式:支持 --silent 参数,通过配置文件实现无人值守的批量部署。

    • 配置文件:支持 /etc/cn-firewall.conf 配置文件进行预设。

3. 使用方法

前提条件:

  • 操作系统:主流 Linux 发行版(Ubuntu, CentOS, Debian, RHEL, Alpine, openSUSE, Arch 等)。

  • 权限:必须以 root 用户或通过 sudo 执行。

下载脚本:

# 假设脚本文件已下载到当前目录,命名为 cn-firewall-permit.sh
chmod +x cn-firewall-permit.sh

交互式配置(推荐):

sudo ./cn-firewall-permit.sh

这将引导您完成所有配置步骤,包括是否启用 IPv6、是否放行自动发现的内网网段、是否添加自定义网段、设置更新频率和时间等。

静默模式配置(批量部署):

  1. 准备配置文件:创建一个配置文件,例如 /etc/cn-firewall.conf

    sudo nano /etc/cn-firewall.conf

    内容示例(可根据需要调整):

    # 更新策略
    UPDATE_INTERVAL=1        # 每1天更新一次
    UPDATE_TIME="04:00"      # 更新时间(凌晨4点)
    # 规则开关
    ENABLE_IPV6="false"      # 禁用IPv6规则
    ALLOW_SSH="true"         # 允许SSH访问
    SSH_PORT=22              # SSH端口
    # 自定义放行的内网网段(可选)
    CUSTOM_LAN_CIDRS=("192.168.100.0/24" "10.10.0.0/16")

  2. 执行静默配置

    sudo ./cn-firewall-permit.sh --silent --config /etc/cn-firewall.conf

    脚本将不进行任何交互,直接使用配置文件中的值或默认值完成配置。

其他选项:

  • 查看帮助

    ./cn-firewall-permit.sh -?  # 或 --help

  • 查看版本

    ./cn-firewall-permit.sh --version

  • 仅备份当前规则

    sudo ./cn-firewall-permit.sh --backup

  • 回滚到最近的备份

    sudo ./cn-firewall-permit.sh --rollback

  • 进入紧急恢复模式(断网时使用):

    sudo ./cn-firewall-permit.sh --recovery

4. 重要文件与目录

  • 主脚本./cn-firewall-permit.sh (执行脚本)

  • IP 段更新脚本/usr/local/bin/update-cn-ipset.sh (由主脚本创建)

  • 定时任务文件/etc/cron.d/update-cn-ip-firewall (由主脚本创建)

  • 日志文件/var/log/cn-firewall.log (记录脚本执行日志)

  • 备份目录/etc/firewall-cn-backup/ (存放防火墙规则备份)

  • 配置文件/etc/cn-firewall.conf (可选,用于静默模式)

5. 关键 ipset 集合

  • cn_ipv4:存储当前生效的中国 IPv4 网段。

  • cn_ipv6:(如果启用)存储当前生效的中国 IPv6 网段。

查看 ipset 内容:

sudo ipset list cn_ipv4  # 查看 IPv4 列表
sudo ipset list cn_ipv6  # 查看 IPv6 列表

6. 注意事项

  • 备份:虽然脚本会自动备份规则,但在生产环境执行前,仍建议手动备份当前防火墙状态。

  • 网络连通性:确保执行脚本时网络畅通,特别是能够访问 APNIC 服务器下载 IP 段数据。

  • SSH 端口:务必确认脚本检测到的 SSH 端口与您实际连接服务器的端口一致。

  • 内网网段:根据服务器实际网络环境,确认自动发现和手动添加的内网网段是否正确,以避免业务中断。

  • 错误处理:如果配置失败导致断网,可尝试通过带外管理(如 VNC 控制台)或执行 sudo ./cn-firewall-permit.sh --recovery 进行恢复。

源代码:

#!/bin/bash
# 兼容多系统(CentOS/Debian/Ubuntu等),修复IPset自启脚本缺失+systemd服务启动失败问题
# 核心修复:强制校验脚本存在性+自动重建缺失脚本+增强权限赋值
VERSION="v3.0-fixed(修复ipset-init缺失+systemd启动失败)"
CRON_FILE="/etc/cron.d/update-cn-ip-firewall"
IPSET_IPV4="cn_ipv4"
IPSET_IPV6="cn_ipv6"
IPSET_IPV4_TEMP="cn_ipv4_temp"
IPSET_IPV6_TEMP="cn_ipv6_temp"
APNIC_URL="http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest"
SCRIPT_PATH="/usr/local/bin/update-cn-ipset.sh"
BACKUP_DIR="/etc/firewall-cn-backup"
LOG_FILE="/var/log/cn-firewall.log"
FIREWALL_TYPE=""
UPDATE_INTERVAL=1
UPDATE_TIME="03:00"
# 默认只放行回环网段
LAN_CIDRS=("127.0.0.1/32" "::1/128")
DETECTED_LAN_CIDRS=() # 存储自动发现的内网网段
ENABLE_IPV6="false"
SILENT_MODE="false"
DEBUG_MODE="false"
CONFIG_FILE="/etc/cn-firewall.conf"
ALLOW_SSH="true"
SSH_PORT=22
CUSTOM_LAN_CIDRS=()
CUSTOM_DNS="" # 自定义DNS(留空自动探测,兼容国内外)
IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"
IPSET_INIT_SCRIPT="/etc/init.d/ipset-init"
IPSET_SYSTEMD_SERVICE="/etc/systemd/system/ipset-init.service"

# ===================== 颜色/日志函数 =====================
red() { echo -e "\033[31m$1\033[0m"; }
green() { echo -e "\033[32m$1\033[0m"; }
yellow() { echo -e "\033[33m$1\033[0m"; }
blue() { echo -e "\033[34m$1\033[0m"; }
cyan() { echo -e "\033[36m$1\033[0m"; }

log_info() {
    local msg="$1"
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [INFO] ${msg}" >> "${LOG_FILE}"
    if [ "${SILENT_MODE}" = "false" ]; then
        green "✅ ${msg}"
    fi
}
log_warn() {
    local msg="$1"
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [WARN] ${msg}" >> "${LOG_FILE}"
    if [ "${SILENT_MODE}" = "false" ]; then
        yellow "⚠️  ${msg}"
    fi
}
log_error() {
    local msg="$1"
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [ERROR] ${msg}" >> "${LOG_FILE}"
    if [ "${SILENT_MODE}" = "false" ]; then
        red "❌ ${msg}"
    fi
    exit 1
}
log_debug() {
    if [ "${DEBUG_MODE}" = "true" ]; then
        local msg="$1"
        local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
        echo "[${timestamp}] [DEBUG] ${msg}" >> "${LOG_FILE}"
        if [ "${SILENT_MODE}" = "false" ]; then
            cyan "🔍 ${msg}"
        fi
    fi
}

# ===================== 核心工具函数 =====================
show_help() {
    echo -e "
CN Firewall Config Tool ${VERSION}"
    echo -e "================================================"
    echo -e "功能:配置防火墙仅放行中国IP+内网网段+SSH,修复IPset自启脚本缺失问题"
    echo -e "================================================"
    echo -e "用法:$0 [选项]
"
    echo -e "核心选项:"
    echo -e "  -?/--help         显示此帮助信息并退出"
    echo -e "  --version         显示版本号并退出"
    echo -e "  --silent          静默模式(无交互,使用配置文件/默认值)"
    echo -e "  --config FILE     指定配置文件路径(默认:/etc/cn-firewall.conf)"
    echo -e "  --debug           启用调试模式(输出详细日志)"
    echo -e "
运维选项:"
    echo -e "  --backup          仅备份当前防火墙规则"
    echo -e "  --rollback        回滚到最近的防火墙规则备份"
    echo -e "  --recovery        直接进入紧急恢复模式(断网时使用)"
    echo -e "  --fix-ipset       仅修复IPset自启脚本+systemd服务(快速修复)"
    echo -e "
示例:"
    echo -e "  $0                # 交互式配置(推荐)"
    echo -e "  $0 --silent       # 静默模式配置(批量部署)"
    echo -e "  $0 --fix-ipset    # 快速修复IPset自启问题(无需重新配置防火墙)"
    echo -e "  $0 --backup       # 仅备份当前规则"
    echo -e "
日志文件:${LOG_FILE}"
    echo -e "备份目录:${BACKUP_DIR}"
    echo -e "================================================
"
    exit 0
}

check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        log_error "必须以root用户执行(sudo ./xxx.sh)"
    fi
}

init_env() {
    touch "${LOG_FILE}"
    chmod 600 "${LOG_FILE}"
    mkdir -p "${BACKUP_DIR}"
    # 强制创建ipset配置目录(确保存在,避免脚本写入失败)
    mkdir -p /etc/sysconfig/ipset
    chmod 755 /etc/sysconfig/ipset
    log_info "脚本环境初始化完成(版本:${VERSION})"
}

load_config() {
    if [ -f "${CONFIG_FILE}" ]; then
        log_info "加载配置文件:${CONFIG_FILE}"
        # shellcheck source=/dev/null
        source "${CONFIG_FILE}"
    else
        log_warn "配置文件不存在,使用默认值"
        # 默认配置
        UPDATE_INTERVAL=1
        UPDATE_TIME="03:00"
        ENABLE_IPV6="false"
        ALLOW_SSH="true"
        SSH_PORT=22
        CUSTOM_LAN_CIDRS=()
    fi
}

# 验证配置项,修复CUSTOM_LAN_CIDRS未定义问题
validate_config() {
    log_info "开始验证配置项"
    # 验证 UPDATE_INTERVAL
    if ! validate_interval "${UPDATE_INTERVAL:-0}"; then
        log_warn "配置项 UPDATE_INTERVAL (${UPDATE_INTERVAL:-0}) 无效,使用默认值 1"
        UPDATE_INTERVAL=1
    fi
    # 验证 UPDATE_TIME
    if ! validate_time "${UPDATE_TIME:-00:00}"; then
        log_warn "配置项 UPDATE_TIME (${UPDATE_TIME:-00:00}) 无效,使用默认值 03:00"
        UPDATE_TIME="03:00"
    fi
    # 验证 SSH_PORT
    if ! echo "${SSH_PORT:-0}" | grep -E '^[0-9]+$' >/dev/null 2>&1 || [ "${SSH_PORT:-0}" -lt 1 ] || [ "${SSH_PORT:-0}" -gt 65535 ]; then
        log_warn "配置项 SSH_PORT (${SSH_PORT:-0}) 无效,使用默认值 22"
        SSH_PORT=22
    fi

    # 检查 CUSTOM_LAN_CIDRS 是否已定义,未定义则初始化为空数组
    if ! declare -p CUSTOM_LAN_CIDRS >/dev/null 2>&1; then
        CUSTOM_LAN_CIDRS=()
    fi

    # 验证 CUSTOM_LAN_CIDRS
    local valid_custom_lan=()
    if [ ${#CUSTOM_LAN_CIDRS[@]} -gt 0 ]; then
        for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
            if validate_cidr "${cidr}"; then
                valid_custom_lan+=("${cidr}")
            else
                log_warn "配置项 CUSTOM_LAN_CIDRS 中的网段 (${cidr}) 无效,已跳过"
            fi
        done
    fi
    CUSTOM_LAN_CIDRS=("${valid_custom_lan[@]}")
    log_info "配置项验证完成"
}

# ===================== 紧急恢复功能 =====================
emergency_recovery() {
    if [ "${SILENT_MODE}" = "true" ]; then
        log_error "静默模式下不支持紧急恢复操作"
    fi
    clear
    blue "========================================"
    blue "            🔥 紧急恢复模式 🔥            "
    blue "========================================"
    yellow "⚠️  此功能用于防火墙配置错误导致无法访问时的紧急恢复 ⚠️"
    echo ""
    # 检测防火墙类型
    if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
        FIREWALL_TYPE="firewalld"
    elif command -v iptables >/dev/null 2>&1; then
        FIREWALL_TYPE="iptables"
    else
        log_error "未检测到任何防火墙(firewalld/iptables)"
    fi
    cyan "当前系统防火墙类型:${FIREWALL_TYPE}"
    echo ""
    echo "请选择恢复方式:"
    echo "1) 临时放行所有入站(重启后失效,安全)"
    echo "2) 恢复防火墙默认配置(永久生效,谨慎)"
    echo "3) 仅恢复SSH端口访问(保留其他规则)"
    echo "4) 退出紧急恢复模式"
    echo ""
    while true; do
        read -p "请输入选择(1-4):" recover_choice
        case "${recover_choice}" in
            1)
                if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
                    firewall-cmd --set-default-zone=public
                    firewall-cmd --reload
                    log_info "firewalld已临时切换到public区域(放行所有入站)"
                else
                    iptables -F
                    iptables -P INPUT ACCEPT
                    ip6tables -F
                    ip6tables -P INPUT ACCEPT
                    log_info "iptables已清空规则并设置默认策略为ACCEPT(放行所有入站)"
                fi
                yellow "提示:此修改重启后失效,需重新配置规则"
                break
                ;;
            2)
                read -p "⚠️  此操作会重置防火墙所有规则,是否确认?(y/N):" confirm_reset
                confirm_reset=${confirm_reset:-N}
                if [ "${confirm_reset}" = "y" ] || [ "${confirm_reset}" = "Y" ]; then
                    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
                        systemctl stop firewalld
                        rm -rf /etc/firewalld/zones/*
                        systemctl start firewalld
                        firewall-cmd --set-default-zone=public
                        firewall-cmd --reload
                        log_info "firewalld已恢复默认配置(public区域,放行所有)"
                    else
                        iptables -F && iptables -X && iptables -P INPUT ACCEPT && iptables -P OUTPUT ACCEPT && iptables -P FORWARD ACCEPT
                        ip6tables -F && ip6tables -X && ip6tables -P INPUT ACCEPT && ip6tables -P OUTPUT ACCEPT && ip6tables -P FORWARD ACCEPT
                        # 保存默认规则
                        if [ -f /etc/redhat-release ]; then
                            iptables-save > /etc/sysconfig/iptables
                            ip6tables-save > /etc/sysconfig/ip6tables
                        elif [ -f /etc/debian_version ]; then
                            iptables-save > /etc/iptables/rules.v4
                            ip6tables-save > /etc/iptables/rules.v6
                        fi
                        log_info "iptables已恢复默认配置(所有端口放行)"
                    fi
                    log_warn "防火墙已恢复默认配置,需重新执行本脚本配置CN IP拦截规则"
                else
                    log_warn "已取消恢复默认配置操作"
                fi
                break
                ;;
            3)
                read -p "请输入SSH端口(默认22):" ssh_port
                ssh_port=${ssh_port:-22}
                if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
                    firewall-cmd --permanent --add-port="${ssh_port}/tcp"
                    firewall-cmd --reload
                    log_info "已放行SSH端口${ssh_port}(firewalld)"
                else
                    iptables -A INPUT -p tcp --dport "${ssh_port}" -j ACCEPT
                    ip6tables -A INPUT -p tcp --dport "${ssh_port}" -j ACCEPT
                    if [ -f /etc/redhat-release ]; then
                        iptables-save > /etc/sysconfig/iptables
                    elif [ -f /etc/debian_version ]; then
                        iptables-save > /etc/iptables/rules.v4
                    fi
                    log_info "已放行SSH端口${ssh_port}(iptables)"
                fi
                yellow "提示:仅放行SSH端口,其他规则保持不变"
                break
                ;;
            4)
                log_info "退出紧急恢复模式"
                exit 0
                ;;
            *)
                log_error "输入无效,请选择1-4"
                ;;
        esac
    done
    log_info "紧急恢复操作完成!建议恢复访问后重新检查防火墙规则"
    exit 0
}

# ===================== 规则备份/回滚 =====================
backup_firewall_rules() {
    log_info "开始备份当前防火墙规则"
    local backup_path="${BACKUP_DIR}/$(date +%Y%m%d_%H%M%S)"
    mkdir -p "${backup_path}"
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        cp -r /etc/firewalld/zones "${backup_path}/" 2>/dev/null || true
        cp -r /etc/firewalld/ipsets "${backup_path}/" 2>/dev/null || true
        firewall-cmd --list-all-zones > "${backup_path}/firewalld-all-rules.txt"
    else
        iptables-save > "${backup_path}/iptables.rules"
        ip6tables-save > "${backup_path}/ip6tables.rules"
        # 备份ipset配置
        ipset save > "${backup_path}/ipset.conf" 2>/dev/null
    fi
    # 保留最近3个备份,清理旧备份
    ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null | tail -n +4 | xargs rm -rf >/dev/null 2>&1
    log_info "防火墙规则已备份到:${backup_path}"
    export BACKUP_PATH="${backup_path}"
}

rollback_firewall_rules() {
    if [ "${SILENT_MODE}" = "true" ]; then
        log_error "静默模式下不支持手动回滚操作"
    fi
    blue "=== 防火墙规则回滚 ==="
    local backup_dirs=($(ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null))
    if [ ${#backup_dirs[@]} -eq 0 ]; then
        log_error "未找到任何规则备份"
        return 1
    fi
    echo "可用的备份版本:"
    for i in "${!backup_dirs[@]}"; do
        echo "$((i+1))) $(basename "${backup_dirs[$i]}")"
    done
    echo ""
    read -p "请选择要回滚的版本序号(默认1,最新备份):" rollback_idx
    rollback_idx=${rollback_idx:-1}
    local target_backup="${backup_dirs[$((rollback_idx-1))]}"
    log_info "开始回滚到备份版本:$(basename "${target_backup}")"
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        systemctl stop firewalld
        cp -r "${target_backup}/zones"/* /etc/firewalld/zones/ 2>/dev/null || true
        cp -r "${target_backup}/ipsets"/* /etc/firewalld/ipsets/ 2>/dev/null || true
        systemctl start firewalld
        firewall-cmd --reload
    else
        iptables-restore < "${target_backup}/iptables.rules"
        ip6tables-restore < "${target_backup}/ip6tables.rules"
        # 恢复ipset配置
        if [ -f "${target_backup}/ipset.conf" ]; then
            ipset restore < "${target_backup}/ipset.conf"
        fi
        # 持久化
        if [ -f /etc/redhat-release ]; then
            iptables-save > /etc/sysconfig/iptables
            ip6tables-save > /etc/sysconfig/ip6tables
        elif [ -f /etc/debian_version ]; then
            iptables-save > /etc/iptables/rules.v4
            ip6tables-save > /etc/iptables/rules.v6
        fi
    fi
    log_info "已回滚防火墙规则到:$(basename "${target_backup}")"
}

attempt_rollback() {
    log_error "配置后网络连通性失败,尝试自动回滚规则..."
    # 在子 Shell 中执行 rollback,避免其 exit 影响主流程
    if ( rollback_firewall_rules 2>/dev/null ); then
        log_info "自动回滚成功!"
        log_error "回滚完成,终止主流程。请检查网络和防火墙配置。"
    else
        log_error "自动回滚失败!请手动执行 $0 --recovery"
    fi
}

# ===================== 网络连通性检测(兼容国内外DNS) =====================
check_network_connectivity() {
    log_info "开始网络连通性预检(兼容国内外主机)"
    local gateway=$(ip route show default | awk '/default/ {print $3}' | head -1)
    # 多组DNS,国内优先 + 国际兜底
    local dns_list=(
        "223.5.5.5"    # 阿里云DNS(国内优选)
        "119.29.29.29" # 腾讯云DNS(国内优选)
        "8.8.8.8"      # Google DNS(国际兜底)
        "1.1.1.1"      # Cloudflare DNS(国际兜底)
        "208.67.222.222" # OpenDNS(国际兜底)
    )
    local usable_dns=""
    local ssh_port_config=$(grep -E '^Port ' /etc/ssh/sshd_config | awk '{print $2}' | head -1)
    local ssh_port=${ssh_port_config:-22}
    log_debug "检测到 SSH 配置端口: ${ssh_port}"

    # 检测网关
    if [ -n "${gateway}" ] && ping -c 1 -W 2 "${gateway}" >/dev/null 2>&1; then
        log_info "网关(${gateway})可达"
    else
        log_warn "网关不可达,继续配置可能导致断网"
        if [ "${SILENT_MODE}" = "false" ]; then
            read -p "是否继续?(y/N):" continue_flag
            continue_flag=${continue_flag:-N}
            if [ "${continue_flag}" != "y" ] && [ "${continue_flag}" != "Y" ]; then
                log_info "用户终止配置操作"
                exit 0
            fi
        fi
    fi

    # 优先使用自定义DNS,否则自动遍历DNS列表
    if [ -n "${CUSTOM_DNS}" ]; then
        log_info "使用用户自定义DNS(${CUSTOM_DNS})进行检测"
        usable_dns="${CUSTOM_DNS}"
        # 验证自定义DNS是否可用
        if ! ping -c 1 -W 2 "${usable_dns}" >/dev/null 2>&1; then
            log_error "用户自定义DNS(${CUSTOM_DNS})不可达,终止配置"
        fi
    else
        # 自动探测可用DNS
        log_info "正在探测可用DNS服务器(国内优先,国际兜底)"
        for dns in "${dns_list[@]}"; do
            if ping -c 1 -W 2 "${dns}" >/dev/null 2>&1; then
                usable_dns="${dns}"
                log_info "找到可用DNS(${dns}),使用该DNS进行连通性检测"
                break
            fi
            log_debug "DNS(${dns})不可达,尝试下一个"
        done

        # 若没有可用DNS,直接报错终止
        if [ -z "${usable_dns}" ]; then
            log_error "所有预设DNS服务器均不可达,无法完成网络预检,终止配置"
        fi
    fi

    # 使用可用DNS进行检测
    log_info "DNS(${usable_dns})可达"

    # 检测SSH端口
    if ss -tuln | grep -E ":${ssh_port}\s" >/dev/null 2>&1; then
        log_info "SSH端口(${ssh_port})已监听"
    else
        log_warn "SSH端口(${ssh_port})未监听,需确认SSH服务状态"
    fi
}

verify_network_after_config() {
    log_info "开始配置后网络连通性验证(兼容国内外主机)"
    # 同样使用多组DNS兜底
    local dns_list=(
        "223.5.5.5"    # 阿里云DNS(国内)
        "119.29.29.29" # 腾讯云DNS(国内)
        "8.8.8.8"      # Google DNS(国际)
        "1.1.1.1"      # Cloudflare DNS(国际)
    )
    local test_success=0

    # 遍历DNS,只要有一个能ping通即认为网络正常
    for test_ip in "${dns_list[@]}"; do
        if ping -c 1 -W 2 "${test_ip}" >/dev/null 2>&1; then
            log_info "配置后网络连通性正常(通过DNS ${test_ip} 验证)"
            test_success=1
            break
        fi
        log_debug "配置后验证:DNS ${test_ip} 不可达,尝试下一个"
    done

    # 所有DNS都不可达,触发回滚
    if [ ${test_success} -eq 0 ]; then
        attempt_rollback
        exit 1
    fi
}

# ===================== 核心:精准计算内网网段(过滤公网IP) =====================
# IP转整数
ip_to_int() {
    local ip="$1"
    local a b c d
    IFS=. read -r a b c d <<< "${ip}"
    echo $((a * 256**3 + b * 256**2 + c * 256 + d))
}

# 整数转IP
int_to_ip() {
    local int="$1"
    local d=$((int % 256))
    local c=$(((int / 256) % 256))
    local b=$(((int / 256**2) % 256))
    local a=$(((int / 256**3) % 256))
    echo "${a}.${b}.${c}.${d}"
}

# 根据IP和掩码计算精准网段
calculate_network_cidr() {
    local ip="$1"
    local mask="$2"
    # 处理掩码格式(支持24或255.255.255.0)
    if echo "${mask}" | grep -E '\.' >/dev/null 2>&1; then
        # 掩码是点分十进制(如255.255.255.0),转成前缀长度
        local mask_int=$(ip_to_int "${mask}")
        local bin_mask=$(echo "obase=2; ${mask_int}" | bc)
        mask=$(echo "${bin_mask}" | tr -d '0' | wc -c)
        mask=$((mask - 1))
    fi
    # 计算网络地址
    local ip_int=$(ip_to_int "${ip}")
    local mask_int=$((0xFFFFFFFF << (32 - mask)))
    local network_int=$((ip_int & mask_int))
    local network_ip=$(int_to_ip "${network_int}")
    echo "${network_ip}/${mask}"
}

# 精准识别内网IP(仅RFC1918私有IP)
detect_lan_cidrs() {
    log_info "开始精准识别当前网卡的内网网段(仅RFC1918私有IP)"
    # 安全初始化数组
    DETECTED_LAN_CIDRS=()

    # 步骤1:找到活跃网卡(有默认路由的网卡)
    local active_iface=$(ip route show default | awk '/default/ {print $5}' | head -1)
    if [ -z "${active_iface}" ]; then
        # 无默认路由,取第一个非lo网卡
        active_iface=$(ip link show | awk -F': ' '/^[0-9]+:/ && !/lo/ {print $2; exit}' | cut -d'@' -f1)
        log_info "未找到默认路由网卡,使用第一个非回环网卡:${active_iface}"
    else
        log_info "识别到活跃网卡(默认路由):${active_iface}"
    fi

    local found=0

    # 提取活跃网卡的内网IP和掩码(仅过滤RFC1918私有IP)
    local ip_cidrs_active=$(ip addr show "${active_iface}" 2>/dev/null | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
    for ip_cidr in ${ip_cidrs_active}; do
        local ip=$(echo "${ip_cidr}" | cut -d/ -f1)
        # 判断IP是否属于RFC1918
        if echo "$ip" | grep -E '^(192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|10\.)' >/dev/null 2>&1; then
            local mask=$(echo "${ip_cidr}" | cut -d/ -f2)
            # 计算精准内网网段
            local network_cidr=$(calculate_network_cidr "${ip}" "${mask}")
            DETECTED_LAN_CIDRS+=("${network_cidr}")
            log_info "活跃网卡${active_iface}的内网IP段:${ip_cidr} → 精准内网网段:${network_cidr}"
            found=1
        fi
    done

    # 兜底:识别所有网卡的内网IP(仅RFC1918)
    log_info "开始扫描所有网卡以发现 RFC1918 私有IP网段"
    local all_ips_cidr=$(ip addr show 2>/dev/null | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
    for ip_cidr in ${all_ips_cidr}; do
        local ip=$(echo "${ip_cidr}" | cut -d/ -f1)
        [ "$ip" = "127.0.0.1" ] && continue
        if echo "$ip" | grep -E '^(192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|10\.)' >/dev/null 2>&1; then
            local mask=$(echo "${ip_cidr}" | cut -d/ -f2)
            local network_cidr=$(calculate_network_cidr "$ip" "$mask")
            # 避免重复添加
            if ! printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | grep -Fxq "${network_cidr}" 2>/dev/null; then
                DETECTED_LAN_CIDRS+=("${network_cidr}")
                log_info "识别到内网IP段:${ip_cidr} → 精准内网网段:${network_cidr}"
                found=1
            else
                log_debug "内网网段 ${network_cidr} 已存在,跳过添加"
            fi
        fi
    done

    if [ $found -eq 0 ]; then
        log_warn "未扫描到任何 RFC1918 私有IP网段"
        log_info "服务器可能直接使用公网IP。"
    fi

    # 去重
    DETECTED_LAN_CIDRS=($(printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | sort -u))
    # 禁用IPv6时过滤
    if [ "${ENABLE_IPV6}" = "false" ]; then
        DETECTED_LAN_CIDRS=($(printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | grep -E '\.'))
    fi

    log_info "自动发现内网网段完成:${DETECTED_LAN_CIDRS[*]:-}"
}

validate_cidr() {
    local cidr="$1"
    local ip=$(echo "${cidr}" | cut -d/ -f1)
    local mask=$(echo "${cidr}" | cut -d/ -f2)
    if ! echo "${mask}" | grep -E '^[0-9]+$' >/dev/null 2>&1; then
        return 1
    fi
    # IPv4校验
    if echo "${ip}" | grep -E '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' >/dev/null 2>&1; then
        [ "${mask}" -ge 0 ] && [ "${mask}" -le 32 ] && return 0
    # IPv6校验
    elif echo "${ip}" | grep -E '^([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}$' >/dev/null 2>&1 && [ "${ENABLE_IPV6}" = "true" ]; then
        [ "${mask}" -ge 0 ] && [ "${mask}" -le 128 ] && return 0
    fi
    return 1
}

configure_lan_allow() {
    detect_lan_cidrs # 调用检测函数
    if [ "${SILENT_MODE}" = "false" ]; then
        blue "=== 配置内网网段放行规则 ==="
        echo "当前已放行的网段:"
        for cidr in "${LAN_CIDRS[@]}"; do
            echo "   - ${cidr}"
        done
        echo ""
        # 展示自动发现的网段
        local array_len=${#DETECTED_LAN_CIDRS[@]}
        if [ $array_len -gt 0 ]; then
            echo "自动发现的内网网段:"
            for cidr in "${DETECTED_LAN_CIDRS[@]}"; do
                echo "   - ${cidr}"
            done
            read -p "是否将自动发现的内网网段添加到放行列表?(y/n,默认y):" add_detected
            add_detected=${add_detected:-y}
            if [ "${add_detected}" = "y" ] || [ "${add_detected}" = "Y" ]; then
                LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
                log_info "已添加自动发现的内网网段:${DETECTED_LAN_CIDRS[*]:-}"
            else
                log_warn "用户选择不添加自动发现的内网网段"
            fi
            echo ""
        else
            log_warn "未自动发现任何内网网段"
        fi
        read -p "是否需要添加自定义内网网段?(y/n,默认n):" add_custom_lan
        add_custom_lan=${add_custom_lan:-n}
        if [ "${add_custom_lan}" = "y" ] || [ "${add_custom_lan}" = "Y" ]; then
            while true; do
                read -p "请输入自定义CIDR内网网段(示例:192.168.1.0/24 或 10.0.0.0/8):" custom_cidr
                if validate_cidr "${custom_cidr}"; then
                    if printf "%s\n" "${LAN_CIDRS[@]}" | grep -x "${custom_cidr}" >/dev/null 2>&1; then
                        log_warn "内网网段${custom_cidr}已存在,无需重复添加"
                    else
                        LAN_CIDRS+=("${custom_cidr}")
                        log_info "已添加自定义内网网段:${custom_cidr}"
                    fi
                    read -p "是否继续添加?(y/n,默认n):" continue_add
                    continue_add=${continue_add:-n}
                    [ "${continue_add}" != "y" ] && break
                else
                    log_error "CIDR格式无效!示例:192.168.1.0/24(IPv4)或 fd00::/8(IPv6)"
                fi
            done
        fi
    fi
    # 静默模式加载自定义网段
    if [ "${SILENT_MODE}" = "true" ]; then
        if [ ${#CUSTOM_LAN_CIDRS[@]} -gt 0 ]; then
            for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
                if validate_cidr "${cidr}"; then
                    LAN_CIDRS+=("${cidr}")
                    log_info "静默模式添加自定义内网网段:${cidr}"
                else
                    log_error "自定义内网网段${cidr}格式无效,已跳过"
                fi
            done
        fi
    fi
    # 最终去重
    LAN_CIDRS=($(printf "%s\n" "${LAN_CIDRS[@]}" | sort -u))
    log_info "最终放行的网段列表:${LAN_CIDRS[*]:-}"
}

configure_ipv6_switch() {
    if [ "${SILENT_MODE}" = "false" ]; then
        blue "=== 配置IPv6规则开关 ==="
        read -p "是否启用IPv6相关规则?(y/n,默认n):" enable_ipv6
        enable_ipv6=${enable_ipv6:-n}
        ENABLE_IPV6=$([ "${enable_ipv6}" = "y" ] && echo "true" || echo "false")
    fi
    if [ "${ENABLE_IPV6}" = "true" ]; then
        log_info "已启用IPv6规则"
    else
        log_info "已禁用IPv6规则,仅配置IPv4"
    fi
}

# ===================== 依赖安装(日志优化版)=====================
install_dependencies() {
    log_info "开始检测系统依赖"
    if [ -f /etc/os-release ]; then
        source /etc/os-release
        OS_ID="${ID}"; OS_VERSION_ID="${VERSION_ID}"
    else
        if [ -f /etc/redhat-release ]; then OS_ID="centos"; fi
    fi
    log_info "识别到系统:${OS_ID} ${OS_VERSION_ID:-unknown}"

    case "${OS_ID}" in
        rhel|centos|fedora)
            if [ "${OS_VERSION_ID}" = "7" ]; then
                INSTALL_CMD="yum install -y --nogpgcheck --skip-broken"
            else
                INSTALL_CMD="dnf install -y --nogpgcheck --skip-broken"
            fi
            NET_TOOL="iproute"
            # 安装iptables-services(CentOS持久化工具)
            log_info "预安装CentOS专属iptables持久化工具:iptables-services"
            ${INSTALL_CMD} iptables-services >/dev/null 2>&1
            ;;
        debian|ubuntu)
            apt-get update -y >/dev/null 2>&1
            INSTALL_CMD="apt install -y --no-install-recommends --fix-missing"
            NET_TOOL="iproute2"
            ;;
        alpine)
            INSTALL_CMD="apk add --no-cache --allow-untrusted"
            NET_TOOL="iproute2"
            ;;
        suse|opensuse-leap|opensuse-tumbleweed)
            INSTALL_CMD="zypper install -y --no-gpg-checks --non-interactive"
            NET_TOOL="iproute2"
            ;;
        arch|manjaro)
            INSTALL_CMD="pacman -S --noconfirm --needed"
            NET_TOOL="iproute2"
            ;;
        *)
            log_warn "未知系统,尝试通用安装命令(yum/apt)"
            if command -v yum >/dev/null 2>&1; then
                INSTALL_CMD="yum install -y --nogpgcheck"
                NET_TOOL="iproute2"
            elif command -v apt >/dev/null 2>&1; then
                INSTALL_CMD="apt install -y"
                NET_TOOL="iproute2"
            else
                log_error "不支持的系统,手动安装依赖后重试"
                exit 1
            fi
            ;;
    esac

    DEPS_NEEDED=("$NET_TOOL" "bc" "ipset" "wget" "iptables")
    [ "${OS_ID:-}" = "centos" ] && DEPS_NEEDED+=("firewalld")

    # 检查是否全部已安装
    local missing_deps=()
    # 定义包名到关键命令的映射
    declare -A PKG_TO_CMD=(
        ["iproute2"]="ip"
        ["iproute"]="ip"
        ["bc"]="bc"
        ["ipset"]="ipset"
        ["wget"]="wget"
        ["iptables"]="iptables"
        ["firewalld"]="firewall-cmd"
        ["iptables-services"]="iptables"
    )
    for dep in "${DEPS_NEEDED[@]}"; do
        pkg_name="${dep%%:*}"
        cmd_name="${PKG_TO_CMD[$pkg_name]:-$pkg_name}"
        if ! command -v "$cmd_name" >/dev/null 2>&1; then
            missing_deps+=("$dep")
        else
            log_info "依赖${pkg_name}已安装(命令: $cmd_name),跳过"
        fi
    done

    if [ ${#missing_deps[@]} -eq 0 ]; then
        log_info "依赖检测已完成,所有组件均已安装"
        return 0
    fi

    log_info "正在安装缺失依赖:${missing_deps[*]}"

    # 安装函数(带兜底)
    install_with_fallback() {
        local dep="$1"
        local fallback="${2:-}"
        local pkg_name="${dep%%:*}"
        local cmd_name="${PKG_TO_CMD[$pkg_name]:-$pkg_name}"
        if command -v "$cmd_name" >/dev/null 2>&1; then
            log_info "依赖${dep}已安装,跳过"
            return 0
        fi
        # 安装主包
        log_info "安装依赖:${dep}"
        if ${INSTALL_CMD} "${dep}"; then
            log_info "依赖${dep}安装成功"
            return 0
        else
            log_error "主包${dep}安装失败!"
        fi
        # 尝试备选包
        if [ -n "${fallback}" ]; then
            log_warn "尝试安配备选包:${fallback}"
            if ${INSTALL_CMD} "${fallback}"; then
                log_info "备选包${fallback}安装成功"
                return 0
            else
                log_error "备选包${fallback}安装失败!"
            fi
        fi
        # 按系统输出修复建议
        case "${OS_ID}" in
            debian|ubuntu)
                log_error "依赖${dep}安装失败!修复建议:
1. apt --fix-broken install -y
2. apt clean && apt autoclean
3. apt install -y ${dep}"
                ;;
            rhel|centos|fedora)
                local pkg_cmd=$([ "${OS_VERSION_ID}" -ge 8 ] && echo "dnf" || echo "yum")
                log_error "依赖${dep}安装失败!修复建议:
1. ${pkg_cmd} clean all && ${pkg_cmd} makecache
2. ${pkg_cmd} install -y ${dep}"
                ;;
            *)
                log_error "依赖${dep}安装失败,请手动安装后重试"
                ;;
        esac
        exit 1
    }

    for dep in "${missing_deps[@]}"; do
        local fallback=""
        case "$dep" in
            iproute2|iproute) fallback="iproute";;
            bc) fallback="bc-bin";;
            ipset) fallback="iptables-ipset";;
            wget) fallback="wget-minimal";;
            iptables) fallback="iptables-services";;
            firewalld) fallback="firewalld-services";;
            iptables-services) fallback="iptables";;
        esac
        install_with_fallback "${dep}" "${fallback}"
    done

    # 验证关键依赖
    local critical_deps=("ip" "bc" "ipset" "wget" "iptables")
    local missing_critical=()
    for dep in "${critical_deps[@]}"; do
        if ! command -v "${dep}" >/dev/null 2>&1; then
            missing_critical+=("${dep}")
        fi
    done
    if [ ${#missing_critical[@]} -gt 0 ]; then
        log_error "关键依赖缺失:${missing_critical[*]}!请手动安装后重试"
    fi
    log_info "所有依赖安装完成(全系统兼容模式)"
}

# ===================== 防火墙配置 =====================
detect_firewall() {
    log_info "开始检测系统防火墙类型"
    if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
        FIREWALL_TYPE="firewalld"
        log_info "检测到firewalld已启用"
    elif command -v iptables >/dev/null 2>&1; then
        FIREWALL_TYPE="iptables"
        log_info "检测到iptables已安装"
        # 启用iptables-services服务(CentOS持久化)
        systemctl enable --now iptables >/dev/null 2>&1
        [ "${ENABLE_IPV6}" = "true" ] && systemctl enable --now ip6tables >/dev/null 2>&1
    else
        log_warn "未检测到任何防火墙,开始自动安装"
        if [ "${OS_ID:-rhel}" = "rhel" ] || [ "${OS_ID:-centos}" = "centos" ]; then
            ${INSTALL_CMD} firewalld >/dev/null 2>&1
            systemctl enable --now firewalld >/dev/null 2>&1
            FIREWALL_TYPE="firewalld"
        else
            ${INSTALL_CMD} iptables >/dev/null 2>&1
            FIREWALL_TYPE="iptables"
            systemctl enable --now iptables >/dev/null 2>&1
        fi
        log_info "已安装并启用${FIREWALL_TYPE}"
    fi
}

# 检测系统初始化类型(chkconfig/systemd)
detect_init_system() {
    if command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then
        echo "systemd"
    elif command -v chkconfig >/dev/null 2>&1 && [ -f /etc/init.d/functions ]; then
        echo "chkconfig"
    else
        echo "unknown"
    fi
}

# 核心修复:强制校验并重建ipset-init脚本
force_rebuild_ipset_init_script() {
    log_info "【修复步骤】开始校验并重建ipset-init脚本(解决文件缺失问题)"
    # 1. 强制删除无效脚本(若存在但不可执行)
    if [ -f "${IPSET_INIT_SCRIPT}" ] && [ ! -x "${IPSET_INIT_SCRIPT}" ]; then
        rm -f "${IPSET_INIT_SCRIPT}"
        log_warn "发现无效ipset-init脚本(无执行权限),已删除"
    fi
    # 2. 重建脚本(无论是否存在,强制覆盖,确保完整性)
    # 先创建父目录,避免No such file or directory错误
    mkdir -p $(dirname "${IPSET_INIT_SCRIPT}")
    cat > "${IPSET_INIT_SCRIPT}" << 'EOF'
#!/bin/bash
# chkconfig: 2345 10 90
# description: IPset auto load configuration on boot

IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"

case "$1" in
    start)
        # 加载 IPset 配置
        if [ -f "${IPSET_CONF}" ]; then
            ipset restore < "${IPSET_CONF}"
            echo "IPset configuration loaded successfully"
        else
            echo "IPset config file not found"
        fi
        ;;
    stop)
        # 保存当前 IPset 配置
        mkdir -p $(dirname "${IPSET_CONF}")
        ipset save > "${IPSET_CONF}"
        echo "IPset configuration saved successfully"
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac
exit 0
EOF
    # 3. 强制赋予执行权限(755,确保所有用户可执行,避免权限不足)
    chmod 755 "${IPSET_INIT_SCRIPT}"
    # 4. 验证脚本存在性和可执行性
    if [ -f "${IPSET_INIT_SCRIPT}" ] && [ -x "${IPSET_INIT_SCRIPT}" ]; then
        log_info "ipset-init脚本重建成功,路径:${IPSET_INIT_SCRIPT},权限:755"
    else
        log_error "ipset-init脚本重建失败!请手动创建该文件"
    fi
}

create_ipset_script() {
    log_info "开始创建IP段更新脚本"
    # 创建父目录,避免No such file or directory错误
    mkdir -p $(dirname "${SCRIPT_PATH}")
    cat > "${SCRIPT_PATH}" << 'EOF'
#!/bin/bash
set -eu
# 配置项
IPSET_IPV4="cn_ipv4"
IPSET_IPV6="cn_ipv6"
IPSET_IPV4_TEMP="cn_ipv4_temp"
IPSET_IPV6_TEMP="cn_ipv6_temp"
TMP_FILE="/tmp/apnic-cn-ips.tmp"
RETRY_COUNT=3
RETRY_INTERVAL=5
LOG_FILE="/var/log/cn-firewall.log"
ENABLE_IPV6="${ENABLE_IPV6:-false}"
IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"
# 日志函数
log_info() {
    local msg="$1"
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [IPSET] ${msg}" >> "${LOG_FILE}"
    echo "[${timestamp}] [IPSET] ${msg}" >&2
}
log_error() {
    local msg="$1"
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [IPSET] ${msg}" >> "${LOG_FILE}"
    echo "[${timestamp}] [IPSET] ${msg}" >&2
    exit 1
}

# 下载函数(带重试)
download_apnic_data() {
    local retry=0
    local downloaded=false
    while [ ${retry} -lt ${RETRY_COUNT} ]; do
        if wget --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
                --header="Accept: text/plain" \
                -O- "http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest" > "${TMP_FILE}"; then
            log_info "APNIC IP段数据下载成功"
            downloaded=true
            break
        fi
        retry=$((retry+1))
        log_info "下载失败,${RETRY_INTERVAL}秒后重试(${retry}/${RETRY_COUNT})"
        sleep ${RETRY_INTERVAL}
    done
    if [ "$downloaded" = false ]; then
        log_error "下载失败,已重试${RETRY_COUNT}次"
    fi
    if [ ! -s "${TMP_FILE}" ]; then
        log_error "下载的文件为空"
    fi
}
# 主逻辑
log_info "开始更新CN IP段数据"
download_apnic_data
# --- IPv4 处理 ---
if ! ipset list "${IPSET_IPV4}" -n >/dev/null 2>&1; then
    log_info "目标IPv4 ipset (${IPSET_IPV4}) 不存在,正在创建..."
    ipset create "${IPSET_IPV4}" hash:net family inet
fi
# 清理临时ipset
ipset destroy "${IPSET_IPV4_TEMP}" 2>/dev/null || true
# 创建临时ipset
ipset create "${IPSET_IPV4_TEMP}" hash:net family inet
# 批量添加IPv4
temp_commands_v4="/tmp/ipset_commands_v4.tmp"
{
    awk -F'|' '$2=="CN" && $3=="ipv4" {print "add '"${IPSET_IPV4_TEMP}"' " $4 "/" 32-log($5)/log(2)}' "${TMP_FILE}"
} > "${temp_commands_v4}"
# 批量加载
cat "${temp_commands_v4}" | ipset restore
rm -f "${temp_commands_v4}"
# 交换ipset
ipset swap "${IPSET_IPV4}" "${IPSET_IPV4_TEMP}"
ipset destroy "${IPSET_IPV4_TEMP}"

# --- IPv6 处理(如果启用) ---
if [ "${ENABLE_IPV6}" = "true" ]; then
    if ! ipset list "${IPSET_IPV6}" -n >/dev/null 2>&1; then
        log_info "目标IPv6 ipset (${IPSET_IPV6}) 不存在,正在创建..."
        ipset create "${IPSET_IPV6}" hash:net family inet6
    fi
    # 清理临时ipset
    ipset destroy "${IPSET_IPV6_TEMP}" 2>/dev/null || true
    # 创建临时ipset
    ipset create "${IPSET_IPV6_TEMP}" hash:net family inet6
    # 批量添加IPv6
    temp_commands_v6="/tmp/ipset_commands_v6.tmp"
    {
        awk -F'|' '$2=="CN" && $3=="ipv6" {print "add '"${IPSET_IPV6_TEMP}"' " $4 "/" $5}' "${TMP_FILE}"
    } > "${temp_commands_v6}"
    # 批量加载
    cat "${temp_commands_v6}" | ipset restore
    rm -f "${temp_commands_v6}"
    # 交换ipset
    ipset swap "${IPSET_IPV6}" "${IPSET_IPV6_TEMP}"
    ipset destroy "${IPSET_IPV6_TEMP}"
fi

# 清理临时文件
rm -f "${TMP_FILE}"
# 保存ipset配置(持久化)
mkdir -p $(dirname "${IPSET_CONF}")
ipset save > "${IPSET_CONF}"
# 输出统计
ipv4_count=$(ipset list "${IPSET_IPV4}" 2>/dev/null | grep -c "entries" || echo 0)
ipv6_count=$([ "${ENABLE_IPV6}" = "true" ] && ipset list "${IPSET_IPV6}" 2>/dev/null | grep -c "entries" || echo 0)
log_info "IPset更新完成:IPv4(${ipv4_count}条) | IPv6(${ipv6_count}条)"
log_info "IPset配置已自动保存到 ${IPSET_CONF}"
EOF
    chmod +x "${SCRIPT_PATH}"
    log_info "IP段更新脚本已创建:${SCRIPT_PATH}"

    # ===== 核心修复:强制重建ipset-init脚本 + 兼容chkconfig/systemd =====
    force_rebuild_ipset_init_script

    # 2. 检测系统初始化类型,自动配置对应自启方式
    local init_system=$(detect_init_system)
    log_info "检测到系统初始化类型:${init_system}"

    if [ "${init_system}" = "chkconfig" ]; then
        # CentOS 6 等chkconfig系统
        chkconfig --add ipset-init >/dev/null 2>&1
        chkconfig ipset-init on >/dev/null 2>&1
        /etc/init.d/ipset-init start >/dev/null 2>&1
        log_info "已配置chkconfig开机自启(IPset服务)"
    elif [ "${init_system}" = "systemd" ]; then
        # CentOS 7+/Debian/Ubuntu 等systemd系统
        # 强制创建/覆盖systemd服务文件,确保路径正确
        mkdir -p $(dirname "${IPSET_SYSTEMD_SERVICE}")
        cat > "${IPSET_SYSTEMD_SERVICE}" << 'EOF'
[Unit]
Description=IPset Auto Load Configuration
After=network.target iptables.service firewalld.service

[Service]
Type=oneshot
ExecStart=/etc/init.d/ipset-init start
ExecStop=/etc/init.d/ipset-init stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
        # 重新加载systemd配置,启用并启动服务(修复启动失败问题)
        systemctl daemon-reload >/dev/null 2>&1
        systemctl disable ipset-init >/dev/null 2>&1 # 先禁用,避免残留配置冲突
        systemctl enable ipset-init >/dev/null 2>&1
        systemctl stop ipset-init >/dev/null 2>&1 # 停止失败的服务
        systemctl start ipset-init >/dev/null 2>&1
        log_info "已配置systemd开机自启(IPset服务),服务文件:${IPSET_SYSTEMD_SERVICE}"
    else
        log_warn "未知系统初始化类型,需手动配置IPset开机自启"
    fi

    log_info "IPset开机自启配置完成(已修复脚本缺失问题)"
}

init_ipset() {
    log_info "开始初始化IPset并加载CN IP段"
    ENABLE_IPV6="${ENABLE_IPV6}" bash "${SCRIPT_PATH}"
    log_info "IPset初始化完成"
}

# firewalld规则配置
configure_firewalld_rules() {
    log_info "开始配置firewalld规则"
    firewall-cmd --set-default-zone=drop >/dev/null

    # 放行内网网段
    for cidr in "${LAN_CIDRS[@]}"; do
        if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
            firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv4 source address=${cidr} accept" >/dev/null
        else
            firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source address=${cidr} accept" >/dev/null
        fi
    done

    # 放行CN IP
    firewall-cmd --permanent --zone=drop --add-rich-rule="rule source ipset=${IPSET_IPV4} accept" >/dev/null

    # IPv6规则
    if [ "${ENABLE_IPV6}" = "true" ]; then
        firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source ipset=${IPSET_IPV6} accept" >/dev/null
    fi

    # 放行SSH
    if [ "${ALLOW_SSH}" = "true" ]; then
        local ssh_port=${SSH_PORT:-22}
        firewall-cmd --permanent --zone=drop --add-port="${ssh_port}/tcp" >/dev/null
        log_info "已放行SSH端口:${ssh_port}"
    fi

    # 重载规则
    firewall-cmd --reload >/dev/null
    firewall-cmd --runtime-to-permanent >/dev/null
    log_info "firewalld规则配置完成"
}

# iptables规则配置
configure_iptables_rules() {
    log_info "开始配置iptables规则"
    # 先删除重复规则
    iptables -D INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
    iptables -D INPUT -s 127.0.0.1 -j ACCEPT 2>/dev/null || true
    iptables -D INPUT -m set --match-set "${IPSET_IPV4}" src -j ACCEPT 2>/dev/null || true
    if [ "${ALLOW_SSH}" = "true" ]; then
        iptables -D INPUT -p tcp --dport "${SSH_PORT}" -j ACCEPT 2>/dev/null || true
    fi
    # 设置默认策略
    iptables -P INPUT DROP
    # 放行已建立连接
    iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    # 放行内网网段
    for cidr in "${LAN_CIDRS[@]}"; do
        if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
            iptables -A INPUT -s "${cidr}" -j ACCEPT
        fi
    done
    # 放行CN IP
    iptables -A INPUT -m set --match-set "${IPSET_IPV4}" src -j ACCEPT
    # 放行SSH
    if [ "${ALLOW_SSH}" = "true" ]; then
        local ssh_port=${SSH_PORT:-22}
        iptables -A INPUT -p tcp --dport "${ssh_port}" -j ACCEPT
        log_info "已放行SSH端口:${ssh_port}"
    fi
    # IPv6规则
    if [ "${ENABLE_IPV6}" = "true" ]; then
        ip6tables -P INPUT DROP
        ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
        # 放行IPv6内网网段
        for cidr in "${LAN_CIDRS[@]}"; do
            if ! echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
                ip6tables -A INPUT -s "${cidr}" -j ACCEPT
            fi
        done
        # 放行IPv6 CN IP
        ip6tables -A INPUT -m set --match-set "${IPSET_IPV6}" src -j ACCEPT
        # IPv6 SSH
        [ "${ALLOW_SSH}" = "true" ] && ip6tables -A INPUT -p tcp --dport "${SSH_PORT:-22}" -j ACCEPT
    fi
    # 保存规则
    if [ -f /etc/redhat-release ]; then
        iptables-save > /etc/sysconfig/iptables
        [ "${ENABLE_IPV6}" = "true" ] && ip6tables-save > /etc/sysconfig/ip6tables
    elif [ -f /etc/debian_version ]; then
        apt install -y iptables-persistent >/dev/null 2>&1
        iptables-save > /etc/iptables/rules.v4
        [ "${ENABLE_IPV6}" = "true" ] && ip6tables-save > /etc/iptables/rules.v6
    fi
    log_info "iptables规则配置完成并持久化"
}

# ===================== 定时任务配置 =====================
# 检测是否已有定时任务
check_existing_cron() {
    local cron_exists=0
    if [ -f "${CRON_FILE}" ]; then
        if grep -q "${SCRIPT_PATH}" "${CRON_FILE}"; then
            cron_exists=1
        fi
    fi
    echo "${cron_exists}"
}

# 更新/创建定时任务
update_cron_job() {
    local hour=$(echo "${UPDATE_TIME}" | cut -d: -f1)
    local minute=$(echo "${UPDATE_TIME}" | cut -d: -f2)
    local cron_expr="${minute} ${hour} $( [ "${UPDATE_INTERVAL}" -eq 1 ] && echo "*" || echo "*/${UPDATE_INTERVAL}" ) * *"
    local new_cron="${cron_expr} root ENABLE_IPV6=${ENABLE_IPV6} ${SCRIPT_PATH} >/dev/null 2>&1"
    local cron_exists=$(check_existing_cron)
    if [ "${cron_exists}" -eq 1 ]; then
        # 使用sed替换现有任务,兼容多系统
        sed -i "s|.*${SCRIPT_PATH}.*|${new_cron}|" "${CRON_FILE}"
        log_info "已更新现有定时任务:${new_cron}"
    else
        # 新建定时任务文件
        echo "${new_cron}" > "${CRON_FILE}"
        chmod 644 "${CRON_FILE}"
        log_info "已创建新定时任务:${new_cron}"
    fi
    # 重启crond服务
    if command -v systemctl >/dev/null 2>&1; then
        systemctl restart crond >/dev/null 2>&1 || systemctl restart cron >/dev/null 2>&1
    else
        service crond restart >/dev/null 2>&1 || service cron restart >/dev/null 2>&1
    fi
    log_info "定时任务服务已重启"
}

# 验证更新间隔
validate_interval() {
    local input="$1"
    echo "${input}" | grep -E '^[0-9]+$' >/dev/null 2>&1 && [ "${input}" -ge 1 ] && [ "${input}" -le 365 ]
}

# 验证更新时间
validate_time() {
    local input="$1"
    echo "${input}" | grep -E '^([01][0-9]|2[0-3]):[0-5][0-9]$' >/dev/null 2>&1
}

# 配置更新策略
configure_update_strategy() {
    if [ "${SILENT_MODE}" = "false" ]; then
        blue "=== 配置IP段自动更新策略 ==="
        local cron_exists=$(check_existing_cron)
        if [ "${cron_exists}" -eq 1 ]; then
            yellow "当前已有定时任务,将在确认后更新规则"
        fi
        # 更新频率
        while true; do
            read -p "请输入更新频率(天,默认${UPDATE_INTERVAL},1-365):" input_interval
            input_interval=${input_interval:-${UPDATE_INTERVAL}}
            if validate_interval "${input_interval}"; then
                UPDATE_INTERVAL="${input_interval}"
                break
            else
                log_error "输入无效!请输入1-365之间的数字"
            fi
        done
        # 更新时间
        while true; do
            read -p "请输入更新时间(HH:MM,默认${UPDATE_TIME},示例:00:30):" input_time
            input_time=${input_time:-${UPDATE_TIME}}
            if validate_time "${input_time}"; then
                UPDATE_TIME="${input_time}"
                break
            else
                log_error "输入无效!请按格式输入(如03:00、18:30)"
            fi
        done
    fi
    log_info "更新策略确认:每${UPDATE_INTERVAL}天的${UPDATE_TIME}自动更新IP段"
    update_cron_job
}

# ===================== 验证/状态检查 =====================
verify_rules() {
    log_info "开始验证防火墙配置"
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        local default_zone=$(firewall-cmd --get-default-zone)
        log_info "firewalld默认区域:${default_zone}"
        [ "${default_zone}" != "drop" ] && log_warn "firewalld默认区域未设置为drop"
    else
        local input_policy=$(iptables -S INPUT 2>/dev/null | head -1 | awk '{print $3}')
        log_info "iptables INPUT默认策略:${input_policy}"
        [ "${input_policy}" != "DROP" ] && log_warn "iptables INPUT默认策略未设置为DROP"
    fi
    log_info "防火墙配置验证完成"
}

# 检查防火墙和ipset持久化状态(增强版,验证修复结果)
check_persistence_status() {
    log_info "检查防火墙和ipset持久化状态(修复验证)..."
    local status_messages=()
    local has_warn=0
    # 检查防火墙服务
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        if systemctl is-enabled --quiet firewalld 2>/dev/null && systemctl is-active --quiet firewalld 2>/dev/null; then
            status_messages+=("✅ firewalld 服务已启用并运行")
        else
            status_messages+=("❌ firewalld 服务未正确启用或运行")
            has_warn=1
        fi
    else # iptables
        if command -v iptables-services >/dev/null 2>&1 || [ -f /etc/sysconfig/iptables ]; then
            if systemctl is-enabled --quiet iptables 2>/dev/null && systemctl is-active --quiet iptables 2>/dev/null; then
                status_messages+=("✅ iptables-services 服务已启用并运行(CentOS持久化)")
            else
                status_messages+=("⚠️  iptables-services 服务未运行,但规则已持久化到 /etc/sysconfig/iptables")
                has_warn=1
            fi
        elif command -v netfilter-persistent >/dev/null 2>&1; then
            if systemctl is-enabled --quiet netfilter-persistent 2>/dev/null && systemctl is-active --quiet netfilter-persistent 2>/dev/null; then
                status_messages+=("✅ netfilter-persistent 服务已启用并运行(iptables持久化)")
            else
                status_messages+=("❌ netfilter-persistent 服务未正确启用或运行")
                has_warn=1
            fi
        else
            status_messages+=("⚠️  未找到iptables持久化工具,但规则已手动保存")
            has_warn=1
        fi
    fi
    # 检查ipset脚本(核心修复验证)
    if [ -f "${IPSET_INIT_SCRIPT}" ] && [ -x "${IPSET_INIT_SCRIPT}" ]; then
        status_messages+=("✅ ipset-init 脚本存在且可执行:${IPSET_INIT_SCRIPT}")
    else
        status_messages+=("❌ ipset-init 脚本缺失或无执行权限")
        has_warn=1
    fi
    # 检查ipset服务
    local init_system=$(detect_init_system)
    if [ "${init_system}" = "chkconfig" ]; then
        if chkconfig --list ipset-init 2>/dev/null | grep -q "on"; then
            status_messages+=("✅ IPset chkconfig 自启已启用")
        else
            status_messages+=("❌ IPset chkconfig 自启未启用")
            has_warn=1
        fi
    elif [ "${init_system}" = "systemd" ]; then
        if systemctl is-enabled --quiet ipset-init 2>/dev/null && systemctl is-active --quiet ipset-init 2>/dev/null; then
            status_messages+=("✅ IPset systemd 服务已启用并运行(修复成功)")
        else
            status_messages+=("⚠️  IPset systemd 服务状态异常,可执行 'systemctl restart ipset-init' 手动修复")
            has_warn=1
        fi
    else
        status_messages+=("⚠️  未知初始化系统,无法检测IPset自启状态")
        has_warn=1
    fi
    # 检查ipset是否存在
    if ipset list "${IPSET_IPV4}" -n >/dev/null 2>&1; then
        status_messages+=("✅ IPset '${IPSET_IPV4}' 存在")
    else
        status_messages+=("❌ IPset '${IPSET_IPV4}' 不存在")
        has_warn=1
    fi
    if [ "${ENABLE_IPV6}" = "true" ]; then
        if ipset list "${IPSET_IPV6}" -n >/dev/null 2>&1; then
            status_messages+=("✅ IPset '${IPSET_IPV6}' 存在")
        else
            status_messages+=("❌ IPset '${IPSET_IPV6}' 不存在")
            has_warn=1
        fi
    fi
    # 检查ipset配置文件
    if [ -f "${IPSET_CONF}" ] && [ -s "${IPSET_CONF}" ]; then
        status_messages+=("✅ IPset 配置文件存在且非空:${IPSET_CONF}")
    else
        status_messages+=("❌ IPset 配置文件缺失或为空:${IPSET_CONF}")
        has_warn=1
    fi
    # 输出所有状态信息
    for msg in "${status_messages[@]}"; do
        echo "${msg}"
    done
    # 提示异常处理
    if [ ${has_warn} -eq 1 ]; then
        log_warn "存在部分状态异常,请根据上述提示排查(可执行 $0 --fix-ipset 快速修复)"
    else
        log_info "所有持久化状态正常(IPset自启修复成功)"
    fi
}

# ===================== 快速修复IPset功能(独立入口) =====================
fix_ipset_only() {
    log_info "进入IPset快速修复模式(仅修复自启脚本+systemd服务)"
    check_root
    init_env
    # 强制重建ipset-init脚本
    force_rebuild_ipset_init_script
    # 检测初始化系统,修复自启配置
    local init_system=$(detect_init_system)
    if [ "${init_system}" = "systemd" ]; then
        # 修复systemd服务
        if [ ! -f "${IPSET_SYSTEMD_SERVICE}" ]; then
            log_info "创建缺失的systemd服务文件:${IPSET_SYSTEMD_SERVICE}"
            mkdir -p $(dirname "${IPSET_SYSTEMD_SERVICE}")
            cat > "${IPSET_SYSTEMD_SERVICE}" << 'EOF'
[Unit]
Description=IPset Auto Load Configuration
After=network.target iptables.service firewalld.service

[Service]
Type=oneshot
ExecStart=/etc/init.d/ipset-init start
ExecStop=/etc/init.d/ipset-init stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
        fi
        # 重新加载配置,重启服务
        systemctl daemon-reload
        systemctl disable ipset-init >/dev/null 2>&1
        systemctl enable ipset-init >/dev/null 2>&1
        systemctl stop ipset-init >/dev/null 2>&1
        systemctl start ipset-init >/dev/null 2>&1
        # 验证服务状态
        if systemctl is-active --quiet ipset-init 2>/dev/null; then
            log_info "IPset systemd服务修复成功,状态正常"
        else
            log_warn "IPset systemd服务启动失败,可手动执行:systemctl status ipset-init 查看详情"
        fi
    elif [ "${init_system}" = "chkconfig" ]; then
        # 修复chkconfig自启
        chkconfig --add ipset-init >/dev/null 2>&1
        chkconfig ipset-init on >/dev/null 2>&1
        /etc/init.d/ipset-init restart >/dev/null 2>&1
        log_info "IPset chkconfig服务修复成功"
    fi
    # 验证ipset配置
    if [ -f "${IPSET_CONF}" ] && [ -s "${IPSET_CONF}" ]; then
        # 重新加载ipset规则
        /etc/init.d/ipset-init start >/dev/null 2>&1
        log_info "IPset规则重新加载成功"
    else
        log_warn "IPset配置文件为空,若需加载CN IP段,请执行:${SCRIPT_PATH}"
    fi
    log_info "IPset快速修复操作完成"
}

# ===================== 主流程入口 =====================
main() {
    # 解析命令行参数
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -?|--help)
                show_help
                ;;
            --version)
                echo "CN Firewall Config Tool ${VERSION}"
                exit 0
                ;;
            --silent)
                SILENT_MODE="true"
                shift
                ;;
            --config)
                CONFIG_FILE="$2"
                shift 2
                ;;
            --debug)
                DEBUG_MODE="true"
                shift
                ;;
            --backup)
                check_root
                init_env
                detect_firewall
                backup_firewall_rules
                exit 0
                ;;
            --rollback)
                check_root
                init_env
                detect_firewall
                rollback_firewall_rules
                exit 0
                ;;
            --recovery)
                check_root
                emergency_recovery
                exit 0
                ;;
            --fix-ipset)
                fix_ipset_only
                exit 0
                ;;
            *)
                log_error "无效参数:$1!使用 $0 --help 查看帮助"
                shift
                ;;
        esac
    done

    # 核心流程执行
    check_root
    init_env
    load_config
    validate_config
    check_network_connectivity
    install_dependencies
    detect_firewall
    backup_firewall_rules
    configure_lan_allow
    configure_ipv6_switch
    create_ipset_script
    init_ipset

    # 按防火墙类型配置规则
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        configure_firewalld_rules
    else
        configure_iptables_rules
    fi

    configure_update_strategy
    verify_rules
    verify_network_after_config
    check_persistence_status

    # 最终汇总信息
    clear
    blue "================================================"
    green "🎉  防火墙+IPset自启配置全部完成(版本:${VERSION})"
    blue "================================================"
    echo ""
    green "✅  核心配置信息:"
    echo "    防火墙类型:${FIREWALL_TYPE}"
    echo "    IPset IPv4名称:${IPSET_IPV4}"
    echo "    IPset IPv6名称:${IPSET_IPV6}(启用状态:${ENABLE_IPV6})"
    echo "    放行SSH端口:${SSH_PORT}(放行状态:${ALLOW_SSH})"
    echo "    放行网段:${LAN_CIDRS[*]:-默认回环网段}"
    echo "    IP段更新频率:每${UPDATE_INTERVAL}天 ${UPDATE_TIME}"
    echo ""
    green "✅  关键文件路径:"
    echo "    IP段更新脚本:${SCRIPT_PATH}"
    echo "    IPset配置文件:${IPSET_CONF}"
    echo "    IPset自启脚本:${IPSET_INIT_SCRIPT}"
    echo "    IPset systemd服务:${IPSET_SYSTEMD_SERVICE}"
    echo "    定时任务文件:${CRON_FILE}"
    echo "    日志文件:${LOG_FILE}"
    echo "    备份目录:${BACKUP_DIR}"
    echo ""
    green "✅  常用操作命令:"
    echo "    手动更新IP段:${SCRIPT_PATH}"
    echo "    查看IPset规则:ipset list ${IPSET_IPV4}"
    echo "    查看防火墙状态:${FIREWALL_TYPE} status"
    echo "    备份规则:$0 --backup"
    echo "    回滚规则:$0 --rollback"
    echo "    紧急恢复:$0 --recovery"
    echo "    快速修复IPset:$0 --fix-ipset"
    echo ""
    blue "================================================"
    log_info "所有操作执行完毕,服务器重启后IPset规则将自动加载"
}

# 启动主流程
main "$@"