1. 脚本作用

cn-firewall-permit.sh 是一个 Linux 入站防火墙白名单配置脚本,用于限制服务器访问来源。

核心逻辑是:默认拒绝非白名单入站访问,仅放行中国 IP、已建立连接、SSH 管理端口、局域网网段和用户自定义 IP/CIDR。


主要功能

  • 自动安装并配置防火墙规则;

  • 自动下载并维护中国 IP 集合;

  • 使用 ipset 提升大量 IP 段匹配效率;

  • 支持 iptablesfirewalld

  • 支持 IPv4,IPv6 可选;

  • 支持自动发现局域网网段并放行;

  • 支持自定义 IP/CIDR 放行;

  • 支持 SSH 端口保护;

  • 支持中国 IP 集合定时更新;

  • 支持规则备份、回滚和紧急恢复;

  • 支持静默模式批量部署。


主菜单

1) 自动安装:放行中国IP集合
2) 自定义放行:查看已有、增加或修改放行白名单
3) 配置IP集自动更新策略
0) 退出

选项说明

1)自动安装

执行完整安装流程,配置中国 IP 集合、防火墙规则、SSH 放行、IPset 自启和定时更新任务。

2)自定义放行

用于管理额外白名单,例如:

  • 局域网网段;

  • 办公网公网 IP;

  • 堡垒机 IP;

  • 监控平台 IP;

  • 指定 CIDR 网段。

3)配置 IP 集自动更新策略

单独设置中国 IP 集合的更新频率和更新时间。


放行规则

默认放行:

  • 中国 IP 集合;

  • 已建立连接;

  • 回环地址;

  • SSH 端口;

  • 自动发现的局域网网段;

  • 自定义 IP/CIDR。

默认拒绝:

  • 其他非白名单入站来源。


常用命令

赋权:

chmod +x cn-firewall-permit.sh

交互运行:

sudo ./cn-firewall-permit.sh

查看状态:

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

备份规则:

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

回滚规则:

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

紧急恢复:

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

修复 IPset 自启:

sudo ./cn-firewall-permit.sh --fix-ipset

重要文件


配置文件示例

UPDATE_INTERVAL=1
UPDATE_TIME="03:00"

ENABLE_IPV6="false"
ALLOW_SSH="true"
SSH_PORT=22

CUSTOM_LAN_CIDRS=(
  "192.168.113.0/24"
)

CUSTOM_ALLOW_IPS=(
  "1.2.3.4"
  "203.0.113.0/24"
)

注意事项

  • 首次运行建议通过控制台、VNC、IPMI 或云厂商控制台操作;

  • 执行前确认 SSH 端口正确;

  • 如果只放行中国 IP,国外网络可能无法访问;

  • 如需远程管理,建议提前加入固定公网 IP 白名单;

  • 出现断连时,可通过控制台执行 --recovery 恢复。

源代码:

#!/bin/bash
# 兼容多系统(CentOS/Debian/Ubuntu等),修复IPset自启脚本缺失+systemd服务启动失败问题
# 核心修复:强制校验脚本存在性+自动重建缺失脚本+增强权限赋值
VERSION="v3.4-startmenu(开局配置更新策略+二级自定义放行管理)"
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_ALLOW_IPS=() # 自定义放行IP/CIDR,支持 1.2.3.4、1.2.3.0/24、IPv6/IPv6-CIDR
CUSTOM_ALLOW_CIDRS=() # 运行时归一化后的自定义放行地址列表
WHITELIST_MODE="cn_only" # cn_only=自动安装仅放行中国IP集合;custom=进入自定义放行管理;lan=兼容旧配置
START_ACTION="install" # install=自动安装CN IP集,custom_allow=自定义放行管理
CUSTOM_DNS="" # 自定义DNS(留空自动探测,兼容国内外)
IPTABLES_CHAIN="CN_FIREWALL_INPUT"
IP6TABLES_CHAIN="CN_FIREWALL_INPUT"
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集合,开局配置更新策略,并提供二级自定义放行管理(局域网网段/IP-CIDR)"
    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 "  --status          查看当前防火墙/IPset/自定义放行状态"
    echo -e "
示例:"
    echo -e "  $0                # 开局菜单:自动安装CN IP集 / 自定义放行管理 / 配置更新策略"
    echo -e "  $0 --silent       # 静默模式配置(批量部署)"
    echo -e "  $0 --fix-ipset    # 快速修复IPset自启问题(无需重新配置防火墙)"
    echo -e "  $0 --backup       # 仅备份当前规则"
    echo -e "  $0 --status       # 查看当前状态"
    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=()
        CUSTOM_ALLOW_IPS=()
        WHITELIST_MODE="cn_only"
    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

    # 验证白名单模式
    case "${WHITELIST_MODE:-lan}" in
        lan|custom|cn_only)
            ;;
        1)
            WHITELIST_MODE="cn_only"
            ;;
        2)
            WHITELIST_MODE="custom"
            ;;
        3)
            WHITELIST_MODE="cn_only"
            ;;
        *)
            log_warn "配置项 WHITELIST_MODE (${WHITELIST_MODE:-}) 无效,使用默认值 lan"
            WHITELIST_MODE="cn_only"
            ;;
    esac

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

    # 验证 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[@]}")

    # 验证 CUSTOM_ALLOW_IPS,并统一转成 CIDR,便于防火墙规则处理
    local valid_custom_allow=()
    if [ ${#CUSTOM_ALLOW_IPS[@]} -gt 0 ]; then
        for item in "${CUSTOM_ALLOW_IPS[@]}"; do
            local normalized=""
            normalized=$(normalize_ip_or_cidr "${item}" 2>/dev/null || true)
            if [ -n "${normalized}" ]; then
                valid_custom_allow+=("${normalized}")
            else
                log_warn "配置项 CUSTOM_ALLOW_IPS 中的地址 (${item}) 无效,已跳过"
            fi
        done
    fi
    if [ ${#valid_custom_allow[@]} -gt 0 ]; then
        CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${valid_custom_allow[@]}" | sort -u))
    fi

    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_warn "配置后网络连通性失败,尝试自动回滚规则..."
    # 在子 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
}

# 将单个IP或CIDR统一转换为CIDR格式:IPv4 -> /32,IPv6 -> /128
normalize_ip_or_cidr() {
    local item="$1"
    item=$(echo "${item}" | tr -d '[:space:]')
    [ -z "${item}" ] && return 1

    if echo "${item}" | grep -q '/'; then
        if validate_cidr "${item}"; then
            echo "${item}"
            return 0
        fi
        return 1
    fi

    # IPv4 单IP
    if echo "${item}" | 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
        echo "${item}/32"
        return 0
    fi

    # IPv6 单IP:这里允许压缩格式,最终交给 ip6tables/firewalld 处理
    if echo "${item}" | grep -E '^[0-9a-fA-F:]+$' >/dev/null 2>&1 && echo "${item}" | grep -q ':'; then
        if [ "${ENABLE_IPV6}" = "true" ]; then
            echo "${item}/128"
            return 0
        else
            log_warn "IPv6地址 ${item} 已跳过,因为当前未启用IPv6规则"
            return 1
        fi
    fi

    return 1
}


select_start_action() {
    if [ "${SILENT_MODE}" = "true" ]; then
        START_ACTION="install"
        WHITELIST_MODE="${WHITELIST_MODE:-cn_only}"
        log_info "静默模式执行自动安装,白名单模式:${WHITELIST_MODE},更新策略使用配置文件/默认值"
        return 0
    fi

    while true; do
        blue "=== CN Firewall 开局菜单 ==="
        echo "请选择操作:"
        echo "  1) 自动安装:放行中国IP集合(原脚本安装/交互式安装流程)"
        echo "  2) 自定义放行:查看已有、增加或修改放行白名单"
        echo "  3) 配置IP集自动更新策略(更新频率/更新时间)"
        echo "  0) 退出"
        echo ""
        echo "当前更新策略:每${UPDATE_INTERVAL}天 ${UPDATE_TIME}"
        echo "说明:SSH端口仍会按配置单独放行,避免远程管理被锁死。"
        echo ""
        read -p "请输入选择(0-3,默认1):" start_choice
        start_choice=${start_choice:-1}
        case "${start_choice}" in
            1)
                START_ACTION="install"
                WHITELIST_MODE="cn_only"
                log_info "已选择:自动安装,仅放行中国IP集合 + SSH"
                break
                ;;
            2)
                START_ACTION="custom_allow"
                WHITELIST_MODE="custom"
                log_info "已选择:自定义放行管理"
                break
                ;;
            3)
                configure_update_strategy
                save_runtime_config
                echo ""
                read -p "更新策略已保存,按回车返回开局菜单..." _
                ;;
            0)
                log_info "用户退出开局菜单"
                exit 0
                ;;
            *)
                yellow "输入无效,请输入 0-3"
                ;;
        esac
    done
}

reset_to_cn_only_allow() {
    LAN_CIDRS=("127.0.0.1/32")
    [ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
    CUSTOM_ALLOW_CIDRS=()
    log_info "自动安装模式:仅放行中国IP集合、回环地址与SSH端口"
}

print_allow_lists() {
    green "当前配置放行列表:"
    echo "  内网/回环放行:${LAN_CIDRS[*]:-无}"
    echo "  自定义IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]:-无}"
    echo ""
}

show_runtime_allow_rules() {
    green "当前运行中规则摘录:"
    if [ "${FIREWALL_TYPE}" = "iptables" ]; then
        echo "IPv4链 ${IPTABLES_CHAIN}:"
        iptables -S "${IPTABLES_CHAIN}" 2>/dev/null || echo "  未发现IPv4独立链"
        if [ "${ENABLE_IPV6}" = "true" ]; then
            echo "IPv6链 ${IP6TABLES_CHAIN}:"
            ip6tables -S "${IP6TABLES_CHAIN}" 2>/dev/null || echo "  未发现IPv6独立链"
        fi
    elif [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        firewall-cmd --zone=drop --list-rich-rules 2>/dev/null || echo "  未能读取firewalld rich rules"
    else
        echo "  防火墙类型未知,无法读取运行规则"
    fi
    echo ""
}

save_runtime_config() {
    mkdir -p "$(dirname "${CONFIG_FILE}")"
    cat > "${CONFIG_FILE}" <<EOF
# CN Firewall 配置文件,由 ${VERSION} 自动生成/更新
UPDATE_INTERVAL=${UPDATE_INTERVAL}
UPDATE_TIME="${UPDATE_TIME}"
ENABLE_IPV6="${ENABLE_IPV6}"
ALLOW_SSH="${ALLOW_SSH}"
SSH_PORT=${SSH_PORT}
WHITELIST_MODE="${WHITELIST_MODE}"
CUSTOM_DNS="${CUSTOM_DNS}"

CUSTOM_LAN_CIDRS=(
EOF
    for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
        echo "  \"${cidr}\"" >> "${CONFIG_FILE}"
    done
    cat >> "${CONFIG_FILE}" <<EOF
)

CUSTOM_ALLOW_IPS=(
EOF
    for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
        echo "  \"${cidr}\"" >> "${CONFIG_FILE}"
    done
    cat >> "${CONFIG_FILE}" <<EOF
)
EOF
    chmod 600 "${CONFIG_FILE}"
    log_info "配置已保存到:${CONFIG_FILE}"
}

custom_allow_menu() {
    if [ "${SILENT_MODE}" = "true" ]; then
        # 静默模式按配置文件内容应用自定义放行
        configure_lan_allow
        configure_custom_allow_ips
        return 0
    fi

    while true; do
        blue "=== 自定义放行管理 ==="
        print_allow_lists
        echo "请选择二级功能:"
        echo "  1) 发现局域网网段放行"
        echo "  2) 自定义IP/CIDR放行"
        echo "  3) 查看当前运行规则"
        echo "  4) 清空自定义放行并仅保留中国IP集合"
        echo "  0) 完成并应用规则"
        echo ""
        read -p "请输入选择(0-4):" custom_choice
        case "${custom_choice}" in
            1)
                manage_lan_allow_menu
                ;;
            2)
                manage_custom_ip_allow_menu
                ;;
            3)
                show_runtime_allow_rules
                read -p "按回车继续..." _
                ;;
            4)
                read -p "确认清空局域网和自定义IP放行?(y/N):" confirm_clear
                confirm_clear=${confirm_clear:-N}
                if [ "${confirm_clear}" = "y" ] || [ "${confirm_clear}" = "Y" ]; then
                    LAN_CIDRS=("127.0.0.1/32")
                    [ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
                    CUSTOM_LAN_CIDRS=()
                    CUSTOM_ALLOW_CIDRS=()
                    CUSTOM_ALLOW_IPS=()
                    WHITELIST_MODE="cn_only"
                    log_info "已清空自定义放行,保留中国IP集合、回环与SSH"
                fi
                ;;
            0)
                WHITELIST_MODE="custom"
                save_runtime_config
                log_info "自定义放行配置完成,准备应用防火墙规则"
                break
                ;;
            *)
                yellow "输入无效,请输入 0-4"
                ;;
        esac
    done
}

manage_lan_allow_menu() {
    detect_lan_cidrs
    blue "=== 发现局域网网段放行 ==="
    echo "当前内网/回环放行:${LAN_CIDRS[*]:-无}"
    echo ""
    if [ ${#DETECTED_LAN_CIDRS[@]} -gt 0 ]; then
        echo "自动发现的局域网网段:"
        for cidr in "${DETECTED_LAN_CIDRS[@]}"; do
            echo "   - ${cidr}"
        done
    else
        yellow "未自动发现RFC1918局域网网段。"
    fi
    echo ""
    echo "操作:"
    echo "  1) 追加自动发现的局域网网段"
    echo "  2) 覆盖为自动发现的局域网网段"
    echo "  3) 手动增加局域网CIDR"
    echo "  4) 清空局域网放行,仅保留回环"
    echo "  0) 返回"
    read -p "请输入选择(0-4):" lan_action
    case "${lan_action}" in
        1)
            LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
            CUSTOM_LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
            ;;
        2)
            LAN_CIDRS=("127.0.0.1/32")
            [ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
            LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
            CUSTOM_LAN_CIDRS=("${DETECTED_LAN_CIDRS[@]}")
            ;;
        3)
            while true; do
                read -p "请输入局域网CIDR(示例:192.168.1.0/24,留空结束):" custom_cidr
                [ -z "${custom_cidr}" ] && break
                if validate_cidr "${custom_cidr}"; then
                    LAN_CIDRS+=("${custom_cidr}")
                    CUSTOM_LAN_CIDRS+=("${custom_cidr}")
                    log_info "已添加局域网CIDR:${custom_cidr}"
                else
                    yellow "CIDR格式无效,已跳过:${custom_cidr}"
                fi
            done
            ;;
        4)
            LAN_CIDRS=("127.0.0.1/32")
            [ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
            CUSTOM_LAN_CIDRS=()
            ;;
        0)
            return 0
            ;;
        *)
            yellow "输入无效"
            ;;
    esac
    LAN_CIDRS=($(printf "%s\n" "${LAN_CIDRS[@]}" | sed '/^$/d' | sort -u))
    CUSTOM_LAN_CIDRS=($(printf "%s\n" "${CUSTOM_LAN_CIDRS[@]}" | sed '/^$/d' | sort -u))
    log_info "当前内网放行列表:${LAN_CIDRS[*]:-无}"
}

manage_custom_ip_allow_menu() {
    blue "=== 自定义IP/CIDR放行 ==="
    echo "当前自定义IP/CIDR放行:${CUSTOM_ALLOW_CIDRS[*]:-无}"
    echo ""
    echo "操作:"
    echo "  1) 追加自定义IP/CIDR"
    echo "  2) 覆盖修改自定义IP/CIDR列表"
    echo "  3) 清空自定义IP/CIDR列表"
    echo "  0) 返回"
    read -p "请输入选择(0-3):" ip_action
    case "${ip_action}" in
        1)
            add_custom_ip_items "append"
            ;;
        2)
            CUSTOM_ALLOW_CIDRS=()
            CUSTOM_ALLOW_IPS=()
            add_custom_ip_items "replace"
            ;;
        3)
            CUSTOM_ALLOW_CIDRS=()
            CUSTOM_ALLOW_IPS=()
            log_info "已清空自定义IP/CIDR放行列表"
            ;;
        0)
            return 0
            ;;
        *)
            yellow "输入无效"
            ;;
    esac
    if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
        CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | sed '/^$/d' | sort -u))
    fi
    CUSTOM_ALLOW_IPS=("${CUSTOM_ALLOW_CIDRS[@]}")
    log_info "当前自定义IP/CIDR放行列表:${CUSTOM_ALLOW_CIDRS[*]:-无}"
}

add_custom_ip_items() {
    local mode="${1:-append}"
    while true; do
        read -p "请输入要放行的IP/CIDR(示例:8.8.8.8、1.2.3.0/24,留空结束):" allow_item
        [ -z "${allow_item}" ] && break
        local normalized=""
        normalized=$(normalize_ip_or_cidr "${allow_item}" 2>/dev/null || true)
        if [ -n "${normalized}" ]; then
            if printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | grep -Fxq "${normalized}" 2>/dev/null; then
                log_warn "自定义放行地址 ${normalized} 已存在,无需重复添加"
            else
                CUSTOM_ALLOW_CIDRS+=("${normalized}")
                CUSTOM_ALLOW_IPS+=("${normalized}")
                log_info "已添加自定义放行地址:${normalized}"
            fi
        else
            yellow "地址格式无效或IPv6未启用,已跳过:${allow_item}"
        fi
    done
    [ "${mode}" = "replace" ] && log_info "自定义IP/CIDR列表已覆盖修改"
}

apply_selected_action() {
    case "${START_ACTION}" in
        install)
            reset_to_cn_only_allow
            ;;
        custom_allow)
            custom_allow_menu
            ;;
        *)
            log_warn "未知开局动作 ${START_ACTION},默认执行自动安装"
            reset_to_cn_only_allow
            ;;
    esac
}

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_custom_allow_ips() {
    if [ "${SILENT_MODE}" = "false" ]; then
        blue "=== 配置自定义放行IP/CIDR ==="
        if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
            echo "配置文件中已有自定义放行地址:"
            for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
                echo "   - ${cidr}"
            done
            echo ""
        fi
        read -p "是否需要添加自定义放行IP或CIDR?(y/n,默认n):" add_custom_allow
        add_custom_allow=${add_custom_allow:-n}
        if [ "${add_custom_allow}" = "y" ] || [ "${add_custom_allow}" = "Y" ]; then
            while true; do
                read -p "请输入要放行的IP/CIDR(示例:8.8.8.8、1.2.3.0/24、240e::1):" allow_item
                local normalized=""
                normalized=$(normalize_ip_or_cidr "${allow_item}" 2>/dev/null || true)
                if [ -n "${normalized}" ]; then
                    if printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | grep -Fxq "${normalized}" 2>/dev/null; then
                        log_warn "自定义放行地址 ${normalized} 已存在,无需重复添加"
                    else
                        CUSTOM_ALLOW_CIDRS+=("${normalized}")
                        log_info "已添加自定义放行地址:${normalized}"
                    fi
                    read -p "是否继续添加?(y/n,默认n):" continue_add_allow
                    continue_add_allow=${continue_add_allow:-n}
                    [ "${continue_add_allow}" != "y" ] && [ "${continue_add_allow}" != "Y" ] && break
                else
                    log_warn "地址格式无效或IPv6未启用,请重新输入"
                fi
            done
        fi
    fi

    if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
        CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | sort -u))
        log_info "最终自定义放行地址:${CUSTOM_ALLOW_CIDRS[*]}"
    else
        log_info "未配置自定义放行IP/CIDR"
    fi
}

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_warn "主包${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 | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
ipv6_count=$([ "${ENABLE_IPV6}" = "true" ] && ipset list "${IPSET_IPV6}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || 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 2>&1 || true
        else
            firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source address=${cidr} accept" >/dev/null 2>&1 || true
        fi
    done

    # 放行自定义IP/CIDR
    for cidr in "${CUSTOM_ALLOW_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 2>&1 || true
        else
            if [ "${ENABLE_IPV6}" = "true" ]; then
                firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source address=${cidr} accept" >/dev/null 2>&1 || true
            fi
        fi
    done
    [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ] && log_info "已放行自定义IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]}"

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

    # IPv6规则
    if [ "${ENABLE_IPV6}" = "true" ]; then
        firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source ipset=${IPSET_IPV6} accept" >/dev/null 2>&1 || true
    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 2>&1 || true
    log_info "firewalld规则配置完成"
}

# iptables规则配置:使用独立链,避免重复规则堆到 INPUT,便于后续扩展和卸载
configure_iptables_rules() {
    log_info "开始配置iptables规则(独立链:${IPTABLES_CHAIN})"

    # 创建并刷新IPv4独立链
    iptables -N "${IPTABLES_CHAIN}" 2>/dev/null || true
    iptables -F "${IPTABLES_CHAIN}"
    iptables -C INPUT -j "${IPTABLES_CHAIN}" 2>/dev/null || iptables -I INPUT 1 -j "${IPTABLES_CHAIN}"

    # 设置默认策略
    iptables -P INPUT DROP

    # 独立链规则顺序:已建立连接 -> 内网 -> 自定义放行 -> CN IP -> SSH -> RETURN
    iptables -A "${IPTABLES_CHAIN}" -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 "${IPTABLES_CHAIN}" -s "${cidr}" -j ACCEPT
        fi
    done
    for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
        if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
            iptables -A "${IPTABLES_CHAIN}" -s "${cidr}" -j ACCEPT
        fi
    done
    iptables -A "${IPTABLES_CHAIN}" -m set --match-set "${IPSET_IPV4}" src -j ACCEPT
    if [ "${ALLOW_SSH}" = "true" ]; then
        local ssh_port=${SSH_PORT:-22}
        iptables -A "${IPTABLES_CHAIN}" -p tcp --dport "${ssh_port}" -j ACCEPT
        log_info "已放行SSH端口:${ssh_port}"
    fi
    iptables -A "${IPTABLES_CHAIN}" -j RETURN

    # IPv6规则
    if [ "${ENABLE_IPV6}" = "true" ]; then
        log_info "开始配置ip6tables规则(独立链:${IP6TABLES_CHAIN})"
        ip6tables -N "${IP6TABLES_CHAIN}" 2>/dev/null || true
        ip6tables -F "${IP6TABLES_CHAIN}"
        ip6tables -C INPUT -j "${IP6TABLES_CHAIN}" 2>/dev/null || ip6tables -I INPUT 1 -j "${IP6TABLES_CHAIN}"
        ip6tables -P INPUT DROP
        ip6tables -A "${IP6TABLES_CHAIN}" -m state --state ESTABLISHED,RELATED -j ACCEPT
        for cidr in "${LAN_CIDRS[@]}"; do
            if ! echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
                ip6tables -A "${IP6TABLES_CHAIN}" -s "${cidr}" -j ACCEPT
            fi
        done
        for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
            if ! echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
                ip6tables -A "${IP6TABLES_CHAIN}" -s "${cidr}" -j ACCEPT
            fi
        done
        ip6tables -A "${IP6TABLES_CHAIN}" -m set --match-set "${IPSET_IPV6}" src -j ACCEPT
        [ "${ALLOW_SSH}" = "true" ] && ip6tables -A "${IP6TABLES_CHAIN}" -p tcp --dport "${SSH_PORT:-22}" -j ACCEPT
        ip6tables -A "${IP6TABLES_CHAIN}" -j RETURN
    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
                yellow "输入无效!请输入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
                yellow "输入无效!请按格式输入(如03:00、18:30)"
            fi
        done
    fi
    log_info "更新策略确认:每${UPDATE_INTERVAL}天的${UPDATE_TIME}自动更新IP段"
    update_cron_job
}

# 安装/应用规则时只确保定时任务存在,不再交互询问更新频率
ensure_update_cron_job() {
    if ! validate_interval "${UPDATE_INTERVAL:-0}"; then
        UPDATE_INTERVAL=1
    fi
    if ! validate_time "${UPDATE_TIME:-00:00}"; then
        UPDATE_TIME="03:00"
    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快速修复操作完成"
}

# ===================== 状态查看功能 =====================
show_status() {
    check_root
    init_env
    load_config
    validate_config
    detect_firewall

    blue "================================================"
    green "CN Firewall 当前状态"
    blue "================================================"
    echo "版本:${VERSION}"
    echo "防火墙类型:${FIREWALL_TYPE:-unknown}"
    echo "IPv6启用:${ENABLE_IPV6}"
    echo "白名单模式:${WHITELIST_MODE}(cn_only=自动安装仅CN IP集,custom=自定义放行管理,lan=兼容旧配置)"
    echo "SSH放行:${ALLOW_SSH},端口:${SSH_PORT}"
    echo "配置文件:${CONFIG_FILE}"
    echo "日志文件:${LOG_FILE}"
    echo ""

    green "规则状态:"
    if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
        echo "firewalld 默认区域:$(firewall-cmd --get-default-zone 2>/dev/null || echo unknown)"
        echo "firewalld 是否运行:$(systemctl is-active firewalld 2>/dev/null || echo unknown)"
    else
        echo "iptables INPUT默认策略:$(iptables -S INPUT 2>/dev/null | head -1 | awk '{print $3}')"
        if iptables -S "${IPTABLES_CHAIN}" >/dev/null 2>&1; then
            echo "IPv4独立链:${IPTABLES_CHAIN} 已存在"
        else
            echo "IPv4独立链:${IPTABLES_CHAIN} 不存在"
        fi
        if [ "${ENABLE_IPV6}" = "true" ]; then
            if ip6tables -S "${IP6TABLES_CHAIN}" >/dev/null 2>&1; then
                echo "IPv6独立链:${IP6TABLES_CHAIN} 已存在"
            else
                echo "IPv6独立链:${IP6TABLES_CHAIN} 不存在"
            fi
        fi
    fi
    echo ""

    green "IPset状态:"
    local ipv4_count=0
    local ipv6_count=0
    ipv4_count=$(ipset list "${IPSET_IPV4}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
    ipv6_count=$(ipset list "${IPSET_IPV6}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
    echo "${IPSET_IPV4}:${ipv4_count:-0} 条"
    [ "${ENABLE_IPV6}" = "true" ] && echo "${IPSET_IPV6}:${ipv6_count:-0} 条"
    echo "IPset配置文件:$([ -s "${IPSET_CONF}" ] && echo "存在" || echo "缺失或为空") (${IPSET_CONF})"
    echo ""

    green "放行列表:"
    echo "内网/回环放行:${LAN_CIDRS[*]:-无}"
    echo "自定义放行IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]:-无}"
    echo ""

    green "定时任务:"
    if [ -f "${CRON_FILE}" ]; then
        cat "${CRON_FILE}"
    else
        echo "未发现 ${CRON_FILE}"
    fi
    echo ""
    check_persistence_status
    blue "================================================"
}

# ===================== 主流程入口 =====================
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
                ;;
            --status)
                show_status
                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_ipv6_switch
    select_start_action
    apply_selected_action
    create_ipset_script
    init_ipset

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

    ensure_update_cron_job
    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 "    白名单模式:${WHITELIST_MODE}"
    echo "    放行网段:${LAN_CIDRS[*]:-默认回环网段}"
    echo "    自定义放行IP/CIDR:${CUSTOM_ALLOW_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 "    进入开局菜单/自定义放行管理:$0"
    echo ""
    blue "================================================"
    log_info "所有操作执行完毕,服务器重启后IPset规则将自动加载"
}

# 启动主流程
main "$@"