#!/bin/bash

#########################################
# 中国IP白名单端口访问控制脚本
# 功能：限制指定端口仅允许中国IP访问
# 作者：Claude
# 日期：2025-11-12
# 版本：2.1 (出向中国流量限制改为独立可选功能，默认关闭)
#########################################

# 配置变量
IPSET_NAME="china_ip"
IPSET_NAME_V6="china_ip_v6"
# 独立的自定义白名单 ipset（最高优先级，不受任何限制）
CUSTOM_IPSET_NAME="custom_whitelist"
CUSTOM_IP_FILE="/etc/ipset/custom_ips.txt"
# PROTECTED_PORTS 将在运行时根据用户选择动态设置
PROTECTED_PORTS=()
# PROTECTED_PORTS_IP: 与 PROTECTED_PORTS 同长的关联数组，key=port/proto，value=目标IP（空=所有IP）
declare -A PROTECTED_PORTS_IP=()

# 是否限制ICMP（ping）- 将在运行时设置
PROTECT_ICMP=false

# 是否限制所有UDP流量（仅允许中国IP的UDP）- 将在运行时设置
PROTECT_UDP_ALL=false

# 是否配置IPv6防火墙规则- 将在运行时设置
PROTECT_IPV6=true

# 针对非中国IP流量的统一标记值
BLOCK_MARK="0x1"

# 反选模式：用户选择一个豁免端口，其余所有端口（含未配置端口）限制仅China访问
REVERSE_MODE=false
EXEMPT_PORT=""
EXEMPT_PROTO="tcp"
EXEMPT_PORTS=()  # 反选模式的全部豁免项，格式：port/proto
# 反选模式对未配置端口的全局限制上限；1023 以下是传统系统服务端口范围。
REVERSE_LOW_PORT_MAX="1023"

# WireGuard 流量豁免（完全不拦截 WireGuard 相关流量）
WIREGUARD_EXEMPT=false
WIREGUARD_PORT="51820"  # WireGuard 默认 UDP 端口，可逗号分隔多个

# 是否限制出向中国流量（阻止服务器主动连接中国IP + 阻止转发到中国IP）
# 独立可选功能，默认关闭；仅通过菜单 6 / 命令行 outbound、outbound-on 启用
BLOCK_CN_OUTBOUND=false
# 出向限制的处理方式：REJECT 或 DROP
CN_OUTBOUND_ACTION="REJECT"


# GeoIP数据源（按优先级）
GEOIP_SOURCES=(
    "https://ftp.apnic.net/stats/apnic/delegated-apnic-latest"
    "https://raw.githubusercontent.com/ipip-net/china-ip-list/master/china_ip_list.txt"
    "https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt"
)

LOG_FILE="/var/log/china_ip_firewall.log"
TEMP_IPLIST="/tmp/china_ip_temp.txt"
TEMP_IPLIST_V6="/tmp/china_ip_v6_temp.txt"
SYSTEMD_SERVICE="/etc/systemd/system/china-ip-firewall.service"
INSTALL_SCRIPT_PATH="/usr/local/bin/china_ip_firewall.sh"
# 如需从其他URL下载脚本，可在运行前设置 SCRIPT_SOURCE_URL 环境变量
SCRIPT_SOURCE_URL="${SCRIPT_SOURCE_URL:-https://dl.erdaye.shop/BAK/china_ip_firewall.sh}"
SCRIPT_SELF_PATH="$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")"
EFFECTIVE_SCRIPT_PATH="$SCRIPT_SELF_PATH"
DOCKER_CHAIN_WARNED=false
IPV6_WARNED=false

# 日志函数
log() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$msg" | tee -a "$LOG_FILE" >&2
}

get_current_protected_ports_v6() {
    if ! ipv6_firewall_available; then
        return 1
    fi
    ip6tables -t mangle -S PREROUTING 2>/dev/null | awk -v setname="$IPSET_NAME_V6" '
        $0 ~ "--match-set " setname " src" && /--dport/ {
            proto=""
            port=""
            for (i = 1; i <= NF; i++) {
                if ($i == "-p" && (i+1) <= NF) proto=$(i+1)
                if ($i == "--dport" && (i+1) <= NF) port=$(i+1)
            }
            if (port != "" && proto != "") printf "%s/%s\n", port, proto
        }
    '
}

update_ipset_v6() {
    if ! ipv6_firewall_available; then
        rm -f "$TEMP_IPLIST_V6"
        return 0
    fi
    if [[ ! -s "$TEMP_IPLIST_V6" ]]; then
        log "提示：未获取IPv6中国IP列表，跳过IPv6 ipset"
        return 0
    fi
    log "开始更新IPv6 ipset..."
    if ipset list "${IPSET_NAME_V6}_temp" &>/dev/null; then
        ipset destroy "${IPSET_NAME_V6}_temp"
    fi
    if ! ipset create "${IPSET_NAME_V6}_temp" hash:net family inet6 hashsize 4096 maxelem 100000; then
        log "错误：创建IPv6临时ipset失败"
        return 1
    fi
    local count=0
    local error_count=0
    while IFS= read -r ip; do
        [[ -z "$ip" || "$ip" =~ ^# ]] && continue
        if ipset add "${IPSET_NAME_V6}_temp" "$ip" 2>/dev/null; then
            ((count++))
            if (( count % 1000 == 0 )); then
                log "IPv6 已处理 $count 条IP规则..."
            fi
        else
            ((error_count++))
        fi
    done < "$TEMP_IPLIST_V6"
    log "IPv6 IP导入完成：成功 $count 条，失败 $error_count 条"
    if [[ $count -lt 50 ]]; then
        log "警告：IPv6 IP数量过少，跳过更新"
        ipset destroy "${IPSET_NAME_V6}_temp" 2>/dev/null
        return 0
    fi
    if ipset list "$IPSET_NAME_V6" &>/dev/null; then
        ipset swap "${IPSET_NAME_V6}_temp" "$IPSET_NAME_V6"
        ipset destroy "${IPSET_NAME_V6}_temp"
        log "已更新 IPv6 ipset"
    else
        ipset rename "${IPSET_NAME_V6}_temp" "$IPSET_NAME_V6"
        log "已创建 IPv6 ipset"
    fi
    rm -f "$TEMP_IPLIST_V6"
    return 0
}

ipv6_firewall_available() {
    if command -v ip6tables &>/dev/null; then
        return 0
    fi
    if [[ "$IPV6_WARNED" != true ]]; then
        log "提示：系统未启用 ip6tables，IPv6 流量将无法受控"
        IPV6_WARNED=true
    fi
    return 1
}

ensure_docker_chain_v6() {
    if ! command -v ip6tables &>/dev/null; then
        return 1
    fi
    if ip6tables -nL DOCKER-USER &>/dev/null; then
        if ! ip6tables -S DOCKER-USER 2>/dev/null | grep -q -- "-j RETURN"; then
            ip6tables -A DOCKER-USER -j RETURN 2>/dev/null || true
        fi
        if ! ip6tables -S FORWARD 2>/dev/null | grep -q -- "-j DOCKER-USER"; then
            ip6tables -I FORWARD 1 -j DOCKER-USER 2>/dev/null || true
        fi
        return 0
    fi
    if ip6tables -N DOCKER-USER 2>/dev/null; then
        ip6tables -I DOCKER-USER -j RETURN 2>/dev/null || true
        ip6tables -I FORWARD 1 -j DOCKER-USER 2>/dev/null || true
        log "已创建 IPv6 DOCKER-USER 链并接入 FORWARD"
        return 0
    fi
    if [[ "$DOCKER_CHAIN_WARNED" != true ]]; then
        log "警告：无法创建 IPv6 DOCKER-USER 链"
        DOCKER_CHAIN_WARNED=true
    fi
    return 1
}

get_effective_script_path() {
    ensure_script_persisted
    echo "$EFFECTIVE_SCRIPT_PATH"
}

ensure_docker_chain() {
    if ! command -v iptables &>/dev/null; then
        return 1
    fi
    if iptables -nL DOCKER-USER &>/dev/null; then
        if ! iptables -S DOCKER-USER 2>/dev/null | grep -q -- "-j RETURN"; then
            iptables -A DOCKER-USER -j RETURN 2>/dev/null || true
        fi
        if ! iptables -S FORWARD 2>/dev/null | grep -q -- "-j DOCKER-USER"; then
            iptables -I FORWARD 1 -j DOCKER-USER 2>/dev/null || true
        fi
        return 0
    fi
    if iptables -N DOCKER-USER 2>/dev/null; then
        iptables -I DOCKER-USER -j RETURN 2>/dev/null || true
        iptables -I FORWARD 1 -j DOCKER-USER 2>/dev/null || true
        log "已创建 DOCKER-USER 链并接入 FORWARD，用于控制容器端口"
        return 0
    fi
    if [[ "$DOCKER_CHAIN_WARNED" != true ]]; then
        log "警告：无法创建 DOCKER-USER 链，Docker容器端口将无法自动限制"
        DOCKER_CHAIN_WARNED=true
    fi
    return 1
}

# 确保脚本存在于本地路径（便于cron调用）
ensure_script_persisted() {
    local current="$SCRIPT_SELF_PATH"
    if [[ -f "$current" && "$current" != /dev/fd/* && "$current" != /proc/* ]]; then
        EFFECTIVE_SCRIPT_PATH="$current"
        return 0
    fi

    local target="$INSTALL_SCRIPT_PATH"
    mkdir -p "$(dirname "$target")"

    # 当前脚本可读时，优先复制它。不能让远程旧版本覆盖本地已验证的修复。
    if [[ -f "$current" ]]; then
        if cp "$current" "$target" 2>/dev/null; then
            chmod +x "$target"
            EFFECTIVE_SCRIPT_PATH="$target"
            log "已将脚本复制到 $target 用于后续任务"
            return 0
        fi
    fi

    # 只有当前脚本不可读取时，才将远程地址作为最后兜底。
    if [[ -n "$SCRIPT_SOURCE_URL" ]]; then
        if curl -fsSL "$SCRIPT_SOURCE_URL" -o "$target"; then
            chmod +x "$target"
            EFFECTIVE_SCRIPT_PATH="$target"
            log "脚本运行自临时路径，已下载副本到 $target"
            return 0
        else
            log "警告：无法从 $SCRIPT_SOURCE_URL 下载脚本副本"
        fi
    fi

    EFFECTIVE_SCRIPT_PATH="$current"
    return 1
}

# 提取端口号（兼容IPv4和IPv6）
extract_port() {
    local addr="$1"
    addr="${addr#[}"
    addr="${addr%]}"
    addr="${addr##*:}"
    [[ "$addr" =~ ^[0-9]+$ ]] && echo "$addr" || echo ""
}

# 判断是否为对外监听地址
is_public_listening() {
    local addr="$1"
    local host="${addr%:*}"
    host="${host#[}"
    host="${host%]}"
    case "$host" in
        ''|0.0.0.0|::|:::|*|[::]|::ffff:0.0.0.0|::ffff:0000:0000)
            return 0
            ;;
    esac
    return 1
}

# 从ss/netstat输出中提取进程名
extract_process_name() {
    local info="$1"
    if [[ -z "$info" || "$info" == "-" ]]; then
        echo "未知"
        return
    fi
    if [[ "$info" =~ \"([^\"]+)\" ]]; then
        echo "${BASH_REMATCH[1]}"
        return
    fi
    if [[ "$info" =~ ([^,]+)/?[0-9]*$ ]]; then
        local name="${BASH_REMATCH[1]}"
        name="${name##*/}"
        [[ -n "$name" ]] && echo "$name" && return
    fi
    echo "$info"
}

# 扫描当前监听端口
scan_listening_ports() {
    log "正在扫描对外监听的端口..."
    
    local temp_result="/tmp/listening_ports_$$.txt"
    > "$temp_result"
    local gathered=false
    
    if command -v ss &> /dev/null; then
        gathered=true
        ss -H -lntp 2>/dev/null | while read -r state recv_q send_q local_addr peer_addr process_info; do
            [[ -z "$local_addr" ]] && continue
            if ! is_public_listening "$local_addr"; then
                continue
            fi
            local port="$(extract_port "$local_addr")"
            [[ -z "$port" ]] && continue
            local proc_name="$(extract_process_name "$process_info")"
            echo "${port}/tcp|${proc_name}"
        done >> "$temp_result"
        
        ss -H -lnup 2>/dev/null | while read -r state recv_q send_q local_addr peer_addr process_info; do
            [[ -z "$local_addr" ]] && continue
            if ! is_public_listening "$local_addr"; then
                continue
            fi
            local port="$(extract_port "$local_addr")"
            [[ -z "$port" ]] && continue
            local proc_name="$(extract_process_name "$process_info")"
            echo "${port}/udp|${proc_name}"
        done >> "$temp_result"
    fi
    
    if [[ ! -s "$temp_result" ]] && command -v netstat &> /dev/null; then
        gathered=true
        netstat -lntp 2>/dev/null | awk '/^tcp/ {
            local_addr = $4
            program = $7
            if (local_addr ~ /^0\.0\.0\.0:/ || local_addr ~ /^:::/) {
                port = local_addr
                sub(/.*:/, "", port)
                split(program, prog, "/")
                proc_name = prog[2]
                if (proc_name == "" || proc_name == "-") proc_name = "未知"
                if (port ~ /^[0-9]+$/) {
                    print port "/tcp|" proc_name
                }
            }
        }' >> "$temp_result"
        
        netstat -lnup 2>/dev/null | awk '/^udp/ {
            local_addr = $4
            program = $6
            if (local_addr ~ /^0\.0\.0\.0:/ || local_addr ~ /^:::/) {
                port = local_addr
                sub(/.*:/, "", port)
                split(program, prog, "/")
                proc_name = prog[2]
                if (proc_name == "" || proc_name == "-") proc_name = "未知"
                if (port ~ /^[0-9]+$/) {
                    print port "/udp|" proc_name
                }
            }
        }' >> "$temp_result"
    fi
    
    if [[ "$gathered" == false ]]; then
        log "错误：未找到 ss 或 netstat 命令"
        return 1
    fi
    
    # 去重并按端口号排序
    if [[ -s "$temp_result" ]]; then
        sort -u "$temp_result" | sort -t'/' -k1 -n -o "$temp_result"
        local count=$(wc -l < "$temp_result" | tr -d ' ')
        log "扫描到 $count 个监听端口"
    else
        log "未扫描到任何监听端口"
        rm -f "$temp_result"
        return 1
    fi
    
    echo "$temp_result"
    return 0
}

# 获取当前受保护端口列表
get_current_protected_ports() {
    if ! command -v iptables &>/dev/null; then
        return 1
    fi
    iptables -t mangle -S PREROUTING 2>/dev/null | awk -v setname="$IPSET_NAME" '
        $0 ~ "--match-set " setname " src" && /--dport/ {
            proto=""
            port=""
            for (i = 1; i <= NF; i++) {
                if ($i == "-p" && (i+1) <= NF) {
                    proto = $(i+1)
                }
                if ($i == "--dport" && (i+1) <= NF) {
                    port = $(i+1)
                }
            }
            if (port != "" && proto != "") {
                printf "%s/%s\n", port, proto
            }
        }
    '
}

# 判断是否为fail2ban相关链或规则
is_fail2ban_chain() {
    local chain="$1"
    [[ "$chain" =~ ^f2b- || "$chain" =~ ^fail2ban- ]]
}

is_fail2ban_rule() {
    local rule="$1"
    [[ "$rule" =~ f2b- || "$rule" =~ fail2ban ]]
}

# 判断是否为inition.sh脚本添加的规则（需要保留）
is_inition_rule() {
    local rule="$1"
    # 识别inition.sh添加的规则特征：
    # 1. ddos_protect 链相关
    # 2. 特定端口开放规则（12748, 5222, 7777, 8007等）
    # 3. 高端口范围规则（10000-65535）
    # 4. FORWARD链的规则
    [[ "$rule" =~ ddos_protect ]] || \
    [[ "$rule" =~ --dport\ (12748|5222|7777|8007|30000:31000|10000:65535) ]] || \
    [[ "$rule" =~ -j\ ddos_protect ]] || \
    [[ "$rule" =~ ^-A\ FORWARD ]]
}

is_inition_chain() {
    local chain="$1"
    [[ "$chain" == "ddos_protect" ]]
}

# 判断是否为出向中国流量限制链（独立可选功能，重装时默认保留）
is_outbound_chain() {
    local chain="$1"
    [[ "$chain" == "china_outbound" || "$chain" == "china_outbound_v6" ]]
}

# 清空iptables规则时保留fail2ban和inition.sh相关内容
flush_iptables_preserve_fail2ban() {
    log "开始清理iptables规则（保留fail2ban和inition.sh规则）..."
    
    # 检查是否有fail2ban封禁的IP
    local has_fail2ban_banned=false
    if command -v fail2ban-client &>/dev/null && systemctl is-active --quiet fail2ban 2>/dev/null; then
        if fail2ban-client status sshd 2>/dev/null | grep -q "Currently banned: [1-9]"; then
            has_fail2ban_banned=true
            log "检测到fail2ban有被封禁的IP，将完全保留fail2ban规则和链"
        fi
    fi
    
    local chains=(INPUT FORWARD OUTPUT)
    for chain in "${chains[@]}"; do
        while read -r rule; do
            [[ -z "$rule" ]] && continue
            [[ "$rule" != -A* ]] && continue
            # 保留fail2ban规则
            if is_fail2ban_rule "$rule" || is_fail2ban_iptables_rule "$rule"; then
                log "保留fail2ban规则: ${rule#-A }"
                continue
            fi
            # 保留inition.sh规则（重要！）
            if is_inition_rule "$rule"; then
                log "保留inition.sh规则: ${rule#-A }"
                continue
            fi
            local delete_rule="${rule/-A /-D }"
            iptables $delete_rule 2>/dev/null || true
        done < <(iptables -S "$chain" 2>/dev/null)
    done
    # 清理自定义链（保留fail2ban和inition.sh的链）
    while read -r custom_chain; do
        [[ -z "$custom_chain" ]] && continue
        if is_fail2ban_chain "$custom_chain"; then
            log "保留fail2ban链: $custom_chain"
            continue
        fi
        if is_inition_chain "$custom_chain"; then
            log "保留inition.sh链: $custom_chain"
            continue
        fi
        if is_outbound_chain "$custom_chain"; then
            log "保留出向中国流量限制链: $custom_chain"
            continue
        fi
        iptables -F "$custom_chain" 2>/dev/null || true
        iptables -X "$custom_chain" 2>/dev/null || true
    done < <(iptables -S 2>/dev/null | awk '/^-N / {print $2}')
    # 注意：不重置INPUT链默认策略，保留inition.sh设置的DROP策略
    # 只确保FORWARD和OUTPUT是ACCEPT
    # iptables -P INPUT ACCEPT 2>/dev/null || true  # 注释掉，保留原有策略
    iptables -P FORWARD ACCEPT 2>/dev/null || true
    iptables -P OUTPUT ACCEPT 2>/dev/null || true
    log "iptables规则已清理（已保留fail2ban和inition.sh规则）"
}

# 获取服务器所有非 loopback 的 IP 地址（IPv4）
get_server_ips() {
    local ips=()
    # 优先使用 ip addr
    if command -v ip &>/dev/null; then
        while IFS= read -r line; do
            ips+=("$line")
        done < <(ip -4 addr show scope global 2>/dev/null | awk '/inet / {split($2,a,"/"); print a[1]}')
    fi
    # 备用 hostname -I
    if [[ ${#ips[@]} -eq 0 ]] && command -v hostname &>/dev/null; then
        local raw
        raw=$(hostname -I 2>/dev/null)
        for ip in $raw; do
            [[ "$ip" =~ ^127\. ]] && continue
            [[ "$ip" =~ ^::1$ ]] && continue
            ips+=("$ip")
        done
    fi
    printf '%s\n' "${ips[@]}"
}

# 交互式自定义端口输入（支持单端口、范围、指定IP）
# 结果追加到全局 PROTECTED_PORTS / PROTECTED_PORTS_IP
select_custom_ports() {
    echo ""
    echo "=========================================="
    echo "  自定义端口配置"
    echo "=========================================="

    # ---- 步骤1：选择目标IP ----
    echo ""
    echo "【步骤1】选择规则作用的目标 IP（即服务器监听 IP）"
    echo ""

    local server_ips=()
    while IFS= read -r ip; do
        [[ -n "$ip" ]] && server_ips+=("$ip")
    done < <(get_server_ips)

    local target_ip=""  # 空 = 所有IP

    if [[ ${#server_ips[@]} -eq 0 ]]; then
        echo "  未检测到服务器IP，将作用于所有IP"
    else
        echo "  检测到以下服务器 IP："
        echo "  0. 所有IP（默认）"
        local idx=1
        for sip in "${server_ips[@]}"; do
            printf "  %d. %s\n" "$idx" "$sip"
            ((idx++))
        done
        echo ""
        read -p "请选择目标IP编号（默认0=所有IP，或直接输入IP）: " ip_sel
        ip_sel="${ip_sel:-0}"

        if [[ "$ip_sel" == "0" || -z "$ip_sel" ]]; then
            target_ip=""
            echo "✓ 将作用于所有IP"
        elif [[ "$ip_sel" =~ ^[0-9]+$ ]] && (( ip_sel >= 1 && ip_sel < idx )); then
            target_ip="${server_ips[$((ip_sel-1))]}"
            echo "✓ 将作用于 IP: $target_ip"
        elif [[ "$ip_sel" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
            target_ip="$ip_sel"
            echo "✓ 将作用于 IP: $target_ip"
        else
            echo "  无效输入，将作用于所有IP"
            target_ip=""
        fi
    fi

    # ---- 步骤2：输入端口 ----
    echo ""
    echo "【步骤2】输入要保护的端口"
    echo "  格式示例："
    echo "    单端口:   8080"
    echo "    端口范围: 8000-9000"
    echo "    多个:     80 443 8080-8090（空格分隔）"
    echo "  输入空行完成，输入 'done' 完成"
    echo ""

    local raw_ports=()
    while true; do
        read -p "端口 (留空完成): " port_input
        [[ -z "$port_input" || "$port_input" == "done" ]] && break
        # 支持逗号分隔
        port_input=$(echo "$port_input" | tr ',' ' ')
        for token in $port_input; do
            raw_ports+=("$token")
        done
    done

    if [[ ${#raw_ports[@]} -eq 0 ]]; then
        echo "  未输入任何端口，跳过自定义端口配置"
        return 0
    fi

    # ---- 步骤3：选择协议 ----
    echo ""
    echo "【步骤3】选择协议"
    echo "  1. TCP（默认）"
    echo "  2. UDP"
    echo "  3. TCP 和 UDP 均保护"
    read -p "请选择 (1/2/3，默认1): " proto_sel
    proto_sel="${proto_sel:-1}"

    local protos=()
    case "$proto_sel" in
        2) protos=("udp") ;;
        3) protos=("tcp" "udp") ;;
        *) protos=("tcp") ;;
    esac

    # ---- 解析端口并存入全局数组 ----
    local added=0
    for token in "${raw_ports[@]}"; do
        if [[ "$token" =~ ^([0-9]+)-([0-9]+)$ ]]; then
            # 端口范围：iptables 使用 start:end 格式
            local start="${BASH_REMATCH[1]}"
            local end="${BASH_REMATCH[2]}"
            if (( start < 1 || end > 65535 || start > end )); then
                echo "  ✗ 无效端口范围: $token（端口须在 1-65535 内且 start <= end）"
                continue
            fi
            local port_expr="${start}:${end}"
            for proto in "${protos[@]}"; do
                local key="${port_expr}/${proto}"
                # 避免重复
                local dup=false
                for existing in "${PROTECTED_PORTS[@]}"; do
                    [[ "$existing" == "$key" ]] && dup=true && break
                done
                if [[ "$dup" == false ]]; then
                    PROTECTED_PORTS+=("$key")
                    PROTECTED_PORTS_IP["$key"]="$target_ip"
                    local ip_desc="${target_ip:-所有IP}"
                    echo "  ✓ 已添加: $key（作用IP: $ip_desc）"
                    ((added++))
                fi
            done
        elif [[ "$token" =~ ^([0-9]+)$ ]]; then
            local port="${BASH_REMATCH[1]}"
            if (( port < 1 || port > 65535 )); then
                echo "  ✗ 无效端口: $token（端口须在 1-65535 内）"
                continue
            fi
            for proto in "${protos[@]}"; do
                local key="${port}/${proto}"
                local dup=false
                for existing in "${PROTECTED_PORTS[@]}"; do
                    [[ "$existing" == "$key" ]] && dup=true && break
                done
                if [[ "$dup" == false ]]; then
                    PROTECTED_PORTS+=("$key")
                    PROTECTED_PORTS_IP["$key"]="$target_ip"
                    local ip_desc="${target_ip:-所有IP}"
                    echo "  ✓ 已添加: $key（作用IP: $ip_desc）"
                    ((added++))
                fi
            done
        else
            echo "  ✗ 无效输入: $token（请输入单端口如 8080 或范围如 8000-9000）"
        fi
    done

    echo ""
    echo "✓ 共添加 $added 个自定义端口规则"
    echo ""
    return 0
}

# 反选模式：从端口列表中选择一个豁免端口
# 用法: select_exempt_port <port_file>
# 设置全局变量 REVERSE_MODE=true, EXEMPT_PORT, EXEMPT_PROTO
select_exempt_port() {
    local port_file="$1"

    echo ""
    echo "=========================================="
    echo "  反选模式：选择【不限制】的端口（可多选）"
    echo "=========================================="
    echo ""
    echo "说明：您选择的端口将对所有IP开放（无限制）。"
    echo "      其余所有端口（包括未监听/未配置的端口）"
    echo "      将限制为仅允许中国大陆IP访问。"
    echo ""

    declare -a exempt_ports_array
    local index=1

    if [[ -f "$port_file" ]] && [[ -s "$port_file" ]]; then
        echo "当前监听的端口："
        while IFS='|' read -r port_proto process; do
            [[ -z "$port_proto" ]] && continue
            exempt_ports_array+=("$port_proto")
            printf "  %3d. %-16s - %s\n" "$index" "$port_proto" "${process:-未知服务}"
            ((index++))
        done < "$port_file"
        echo ""
    fi

    echo "  0. 手动输入端口号"
    echo ""
    echo "支持多选，用空格分隔编号（如: 1 3 5）"
    echo "手动输入时也支持多个，空格分隔（如: 8080/tcp 443 1234/udp）"
    echo ""
    read -p "请选择豁免端口编号: " exempt_sel_raw

    # 收集所有豁免的 port/proto
    declare -a exempt_list=()

    # 解析输入：可能是数字编号、0、或手动 port/proto 混合
    for token in $exempt_sel_raw; do
        if [[ "$token" == "0" ]]; then
            # 0 表示手动输入，但在多选场景下跳过（避免歧义），提示用直接输入格式
            echo "  提示：多选时请直接输入端口格式（如 8080/tcp），无需输入0"
            continue
        elif [[ "$token" =~ ^[0-9]+$ ]]; then
            # 数字编号
            if (( token >= 1 && token < index )); then
                exempt_list+=("${exempt_ports_array[$((token-1))]}")
            else
                echo "  ✗ 编号 $token 超出范围，已跳过"
            fi
        elif [[ "$token" =~ ^([0-9]+)/(tcp|udp)$ ]]; then
            # 手动 port/proto 格式
            local p="${BASH_REMATCH[1]}"
            local proto="${BASH_REMATCH[2]}"
            if (( p >= 1 && p <= 65535 )); then
                exempt_list+=("${p}/${proto}")
            else
                echo "  ✗ 端口 $p 超出范围(1-65535)，已跳过"
            fi
        elif [[ "$token" =~ ^([0-9]+)$ ]]; then
            # 纯数字但不在编号范围内，当作手动端口号，默认 tcp
            local p="${BASH_REMATCH[1]}"
            if (( p >= 1 && p <= 65535 )); then
                exempt_list+=("${p}/tcp")
                echo "  提示：$p 未指定协议，默认为 tcp"
            else
                echo "  ✗ 端口 $p 超出范围(1-65535)，已跳过"
            fi
        else
            echo "  ✗ 无效输入: $token（请输入编号或 端口/协议 格式），已跳过"
        fi
    done

    if [[ ${#exempt_list[@]} -eq 0 ]]; then
        echo "  ✗ 未选择任何有效豁免端口，取消反选模式"
        return 1
    fi

    # 去重
    declare -a exempt_dedup=()
    declare -A exempt_seen=()
    for item in "${exempt_list[@]}"; do
        if [[ -z "${exempt_seen[$item]:-}" ]]; then
            exempt_dedup+=("$item")
            exempt_seen["$item"]=1
        fi
    done

    # 设置全局变量（兼容旧逻辑：EXEMPT_PORT/EXEMPT_PROTO 取第一个豁免端口）
    EXEMPT_PORTS=("${exempt_dedup[@]}")
    EXEMPT_PORT="${EXEMPT_PORTS[0]%/*}"
    EXEMPT_PROTO="${EXEMPT_PORTS[0]#*/}"
    REVERSE_MODE=true

    # 从已监听端口中排除所有豁免端口，其余加入保护列表
    PROTECTED_PORTS=()
    for port_proto in "${ports_array[@]}"; do
        local skip=false
        for ex in "${exempt_dedup[@]}"; do
            [[ "$port_proto" == "$ex" ]] && skip=true && break
        done
        [[ "$skip" == false ]] && PROTECTED_PORTS+=("$port_proto")
    done

    echo ""
    echo "✓ 豁免端口（所有IP均可访问）："
    for ex in "${exempt_dedup[@]}"; do
        echo "    - $ex"
    done
    echo "✓ 其余已监听端口将限制为仅允许中国大陆IP访问"
    echo "✓ 未配置/未监听的低位端口（1-1023）也将限制为仅允许中国大陆IP访问"
    echo "  （高位端口 1024-65535 不添加全局限制）"
    echo ""
    log "反选模式：豁免端口=${exempt_dedup[*]}"
    return 0
}

# 统一询问附加协议保护选项，避免普通模式和反选模式走不同流程。
ask_protocol_options() {
    echo ""
    read -p "是否限制 ICMP (ping)? (y/N): " protect_ping
    if [[ "$protect_ping" =~ ^[Yy]$ ]]; then
        PROTECT_ICMP=true
        log "用户选择限制ICMP"
        echo "✓ 将限制 ICMP (ping)"
    else
        PROTECT_ICMP=false
        log "用户选择不限制ICMP"
        echo "✓ 不限制 ICMP (ping)"
    fi

    echo ""
    read -p "是否限制所有 UDP 流量（仅允许中国IP的UDP）? (y/N): " protect_udp_all
    if [[ "$protect_udp_all" =~ ^[Yy]$ ]]; then
        PROTECT_UDP_ALL=true
        log "用户选择限制所有UDP流量"
        echo "✓ 将限制所有 UDP 流量（仅中国IP可访问）"
    else
        PROTECT_UDP_ALL=false
        log "用户选择不限制所有UDP"
        echo "✓ 不限制所有 UDP"
    fi

    # ---- WireGuard 豁免询问 ----
    echo ""
    read -p "此服务器是否运行 WireGuard VPN 服务？(y/N): " wg_enabled
    if [[ "$wg_enabled" =~ ^[Yy]$ ]]; then
        WIREGUARD_EXEMPT=true
        log "用户启用 WireGuard 豁免"
        read -p "WireGuard 监听端口（默认 51820，多个用逗号分隔）: " wg_port_input
        wg_port_input="${wg_port_input:-51820}"
        # 去除空格
        wg_port_input="${wg_port_input// /}"
        WIREGUARD_PORT="$wg_port_input"
        echo "✓ 已启用 WireGuard 豁免（端口: $WIREGUARD_PORT），WireGuard 流量不受任何限制"
    else
        WIREGUARD_EXEMPT=false
        log "用户未启用 WireGuard 豁免"
        echo "✓ 未启用 WireGuard 豁免"
    fi

    echo ""
    if ipv6_firewall_available; then
        read -p "是否配置 IPv6 防火墙规则? (Y/n): " protect_ipv6
        if [[ "$protect_ipv6" =~ ^[Nn]$ ]]; then
            PROTECT_IPV6=false
            log "用户选择不配置IPv6防火墙"
            echo "✓ 不配置 IPv6 防火墙规则"
        else
            PROTECT_IPV6=true
            log "用户选择配置IPv6防火墙"
            echo "✓ 将配置 IPv6 防火墙规则"
        fi
    else
        PROTECT_IPV6=false
        echo "✓ 未检测到可用的 IPv6 防火墙，跳过 IPv6 规则"
    fi

    # 注意：出向中国流量限制已改为独立可选功能（菜单 6 / 命令行 outbound），
    # 不再在安装流程中询问。
}

# ============================================================
# 出向中国流量限制（独立可选功能，默认关闭）
# 入口：菜单 6 / 命令行 outbound | outbound-on | outbound-off
# ============================================================
detect_wireguard() {
    command -v ip &>/dev/null && [[ -n "$(ip -o link show type wireguard 2>/dev/null)" ]]
}

show_cn_outbound_status() {
    echo ""
    echo "出向中国流量限制（OUTPUT + FORWARD）："
    if iptables -nL china_outbound >/dev/null 2>&1 && iptables -C OUTPUT -j china_outbound 2>/dev/null; then
        local ob_action="DROP"
        iptables -S china_outbound 2>/dev/null | grep -q '\-j REJECT' && ob_action="REJECT"
        echo "✓ 已启用（$ob_action）"
        iptables -S china_outbound 2>/dev/null | grep -v "^-N" | sed 's/^/    /'
        if ipv6_firewall_available && ip6tables -nL china_outbound_v6 >/dev/null 2>&1; then
            echo "✓ IPv6 出向限制 - 已启用"
        fi
    else
        echo "✗ 未启用"
    fi
    echo ""
}

enable_cn_outbound_interactive() {
    echo ""
    echo "=========================================="
    echo "  启用出向中国流量限制"
    echo "=========================================="
    echo ""
    echo "说明：阻止服务器【主动访问】中国IP（OUTPUT），"
    echo "      以及阻止【转发】到中国IP的流量（FORWARD，如 Docker/LXC/PVE 虚拟机）。"
    echo "      入方向限制与本功能互相独立。"
    echo ""
    echo "始终放行："
    echo "  • 已建立连接的返回包（ESTABLISHED/RELATED）"
    echo "  • 内网私有IP段、loopback、DNS(53)"
    echo "  • 自定义白名单 IP（custom_whitelist）"
    echo "  • WireGuard 隧道流量（下面可选）"
    echo ""

    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        echo "错误：ipset $IPSET_NAME 不存在，请先执行 安装(1) 或 更新(2) 导入中国IP列表"
        return 1
    fi

    read -p "确认启用出向中国流量限制？(y/N): " ans
    if [[ ! "$ans" =~ ^[Yy]$ ]]; then
        echo "已取消"
        return 0
    fi

    echo ""
    echo "处理方式："
    echo "  1. REJECT（立即返回拒绝，应用报错更快）← 默认"
    echo "  2. DROP（静默丢弃，对端超时才感知）"
    read -p "请选择 (1/2，默认1): " sel
    if [[ "${sel:-1}" == "2" ]]; then
        CN_OUTBOUND_ACTION="DROP"
    else
        CN_OUTBOUND_ACTION="REJECT"
    fi

    # WireGuard 豁免：独立运行时没有安装流程里的变量，这里单独询问（自动检测作为默认值）
    local wg_hint="y/N"
    detect_wireguard && wg_hint="Y/n（已检测到 WireGuard 接口）"
    read -p "是否放行 WireGuard 隧道流量？($wg_hint): " wg_ans
    if [[ -z "$wg_ans" ]]; then
        detect_wireguard && wg_ans=y || wg_ans=n
    fi
    if [[ "$wg_ans" =~ ^[Yy]$ ]]; then
        WIREGUARD_EXEMPT=true
        read -p "WireGuard 监听端口（默认 51820，多个逗号分隔）: " wg_port_input
        WIREGUARD_PORT="${wg_port_input:-51820}"
        WIREGUARD_PORT="${WIREGUARD_PORT// /}"
    else
        WIREGUARD_EXEMPT=false
    fi

    BLOCK_CN_OUTBOUND=true
    log "用户启用出向中国流量限制（动作=$CN_OUTBOUND_ACTION，WireGuard豁免=$WIREGUARD_EXEMPT）"
    if setup_cn_outbound_block; then
        save_iptables
        echo "✓ 出向中国流量限制已启用（$CN_OUTBOUND_ACTION）"
    else
        BLOCK_CN_OUTBOUND=false
        echo "错误：出向限制配置失败，详见 $LOG_FILE"
        return 1
    fi
}

disable_cn_outbound() {
    cleanup_cn_outbound_block
    save_iptables
    echo "✓ 出向中国流量限制已关闭"
}

manage_cn_outbound() {
    echo ""
    echo "=========================================="
    echo "  出向中国流量限制管理"
    echo "=========================================="
    show_cn_outbound_status
    echo "  1. 启用 / 重新配置"
    echo "  2. 关闭"
    echo "  0. 返回"
    read -p "请选择: " ob_choice
    case "$ob_choice" in
        1) enable_cn_outbound_interactive ;;
        2) disable_cn_outbound ;;
        *) ;;
    esac
}

# 清理出向中国流量限制规则（IPv4 + IPv6）
cleanup_cn_outbound_block() {
    log "清理出向中国流量限制规则..."
    local sets=("$IPSET_NAME")
    local actions=("DROP" "REJECT")

    # --- IPv4 OUTPUT 链 ---
    for action in "${actions[@]}"; do
        # 带 custom_whitelist 豁免的精确规则
        while iptables -C OUTPUT -m set ! --match-set "$IPSET_NAME" dst \
            ! -o lo -j "$action" 2>/dev/null; do
            iptables -D OUTPUT -m set ! --match-set "$IPSET_NAME" dst \
                ! -o lo -j "$action" 2>/dev/null || break
        done
        # 旧版本可能写的规则形式（不含 ! -o lo）
        while iptables -C OUTPUT -m set ! --match-set "$IPSET_NAME" dst \
            -j "$action" 2>/dev/null; do
            iptables -D OUTPUT -m set ! --match-set "$IPSET_NAME" dst \
                -j "$action" 2>/dev/null || break
        done
    done

    # --- IPv4 FORWARD 链 ---
    for action in "${actions[@]}"; do
        while iptables -C FORWARD -m set ! --match-set "$IPSET_NAME" dst \
            -j "$action" 2>/dev/null; do
            iptables -D FORWARD -m set ! --match-set "$IPSET_NAME" dst \
                -j "$action" 2>/dev/null || break
        done
    done

    # --- 清理 china_outbound 独立链 ---
    for chain in OUTPUT FORWARD; do
        while iptables -C "$chain" -j china_outbound 2>/dev/null; do
            iptables -D "$chain" -j china_outbound 2>/dev/null || break
        done
    done
    if iptables -nL china_outbound >/dev/null 2>&1; then
        iptables -F china_outbound 2>/dev/null || true
        iptables -X china_outbound 2>/dev/null || true
        log "已删除 china_outbound 链"
    fi

    # --- IPv6 ---
    if ipv6_firewall_available; then
        for action in "${actions[@]}"; do
            while ip6tables -C OUTPUT -m set ! --match-set "$IPSET_NAME_V6" dst \
                ! -o lo -j "$action" 2>/dev/null; do
                ip6tables -D OUTPUT -m set ! --match-set "$IPSET_NAME_V6" dst \
                    ! -o lo -j "$action" 2>/dev/null || break
            done
            while ip6tables -C OUTPUT -m set ! --match-set "$IPSET_NAME_V6" dst \
                -j "$action" 2>/dev/null; do
                ip6tables -D OUTPUT -m set ! --match-set "$IPSET_NAME_V6" dst \
                    -j "$action" 2>/dev/null || break
            done
            while ip6tables -C FORWARD -m set ! --match-set "$IPSET_NAME_V6" dst \
                -j "$action" 2>/dev/null; do
                ip6tables -D FORWARD -m set ! --match-set "$IPSET_NAME_V6" dst \
                    -j "$action" 2>/dev/null || break
            done
        done
        for chain in OUTPUT FORWARD; do
            while ip6tables -C "$chain" -j china_outbound_v6 2>/dev/null; do
                ip6tables -D "$chain" -j china_outbound_v6 2>/dev/null || break
            done
        done
        if ip6tables -nL china_outbound_v6 >/dev/null 2>&1; then
            ip6tables -F china_outbound_v6 2>/dev/null || true
            ip6tables -X china_outbound_v6 2>/dev/null || true
            log "已删除 IPv6 china_outbound_v6 链"
        fi
    fi

    log "出向中国流量限制规则清理完成"
}

# 配置出向中国流量限制规则（OUTPUT + FORWARD，IPv4 + IPv6）
# 使用独立链 china_outbound / china_outbound_v6，方便幂等更新
setup_cn_outbound_block() {
    if [[ "$BLOCK_CN_OUTBOUND" != true ]]; then
        return 0
    fi

    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        log "错误：ipset $IPSET_NAME 不存在，跳过出向限制配置"
        return 1
    fi

    log "开始配置出向中国流量限制（ACTION=${CN_OUTBOUND_ACTION}）..."

    # ---- 先清理旧规则，保证幂等 ----
    cleanup_cn_outbound_block

    # ====================================================
    # IPv4 独立链 china_outbound
    # ====================================================
    if ! iptables -N china_outbound 2>/dev/null; then
        iptables -F china_outbound 2>/dev/null || true
    fi

    # 1. 已建立/关联连接放行（最高优先级，确保服务器可以正常上网）
    iptables -A china_outbound -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
    iptables -A china_outbound -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true

    # 2. 本地回环放行
    iptables -A china_outbound -o lo -j ACCEPT

    # 3. 内网/私有 IP 段放行
    for subnet in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8; do
        iptables -A china_outbound -d "$subnet" -j ACCEPT
    done

    # 4. 自定义白名单放行（custom_whitelist ipset）
    if ipset list "$CUSTOM_IPSET_NAME" &>/dev/null; then
        iptables -A china_outbound -m set --match-set "$CUSTOM_IPSET_NAME" dst -j ACCEPT
        log "已在出向链放行自定义白名单 IP"
    fi

    # 5. WireGuard 隧道流量放行
    if [[ "$WIREGUARD_EXEMPT" == true ]]; then
        iptables -A china_outbound -o wg+ -j ACCEPT
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ -z "$wgp" ]] && continue
            iptables -A china_outbound -p udp --dport "$wgp" -j ACCEPT
        done
        log "已在出向链放行 WireGuard 流量"
    fi

    # 6. DNS 查询放行（避免限制后无法解析域名）
    iptables -A china_outbound -p udp --dport 53 -j ACCEPT
    iptables -A china_outbound -p tcp --dport 53 -j ACCEPT

    # 7. 核心规则：目标是中国 IP → REJECT/DROP
    local reject_args=()
    if [[ "$CN_OUTBOUND_ACTION" == "REJECT" ]]; then
        reject_args=(-j REJECT --reject-with icmp-net-prohibited)
    else
        reject_args=(-j DROP)
    fi
    iptables -A china_outbound -m set --match-set "$IPSET_NAME" dst "${reject_args[@]}"
    log "已添加出向限制规则：目标是中国IP → $CN_OUTBOUND_ACTION"

    # ---- 挂载到 OUTPUT 和 FORWARD 链头部 ----
    if ! iptables -C OUTPUT -j china_outbound 2>/dev/null; then
        iptables -I OUTPUT 1 -j china_outbound
        log "已将 china_outbound 链挂接到 OUTPUT"
    fi
    if ! iptables -C FORWARD -j china_outbound 2>/dev/null; then
        iptables -I FORWARD 1 -j china_outbound
        log "已将 china_outbound 链挂接到 FORWARD"
    fi

    # ====================================================
    # IPv6 独立链 china_outbound_v6（仅在启用 IPv6 且 ipset 存在时）
    # ====================================================
    if [[ "$PROTECT_IPV6" == true ]] && ipv6_firewall_available && ipset list "$IPSET_NAME_V6" &>/dev/null; then
        if ! ip6tables -N china_outbound_v6 2>/dev/null; then
            ip6tables -F china_outbound_v6 2>/dev/null || true
        fi

        ip6tables -A china_outbound_v6 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
        ip6tables -A china_outbound_v6 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
        ip6tables -A china_outbound_v6 -o lo -j ACCEPT
        # IPv6 ULA 内网段
        ip6tables -A china_outbound_v6 -d fc00::/7 -j ACCEPT
        ip6tables -A china_outbound_v6 -d ::1/128 -j ACCEPT

        local v6_custom="${CUSTOM_IPSET_NAME}_v6"
        if ipset list "$v6_custom" &>/dev/null; then
            ip6tables -A china_outbound_v6 -m set --match-set "$v6_custom" dst -j ACCEPT
        fi

        if [[ "$WIREGUARD_EXEMPT" == true ]]; then
            ip6tables -A china_outbound_v6 -o wg+ -j ACCEPT
        fi

        ip6tables -A china_outbound_v6 -p udp --dport 53 -j ACCEPT
        ip6tables -A china_outbound_v6 -p tcp --dport 53 -j ACCEPT

        local reject_args_v6=()
        if [[ "$CN_OUTBOUND_ACTION" == "REJECT" ]]; then
            reject_args_v6=(-j REJECT --reject-with icmp6-adm-prohibited)
        else
            reject_args_v6=(-j DROP)
        fi
        ip6tables -A china_outbound_v6 -m set --match-set "$IPSET_NAME_V6" dst "${reject_args_v6[@]}"
        log "已添加 IPv6 出向限制规则：目标是中国IP → $CN_OUTBOUND_ACTION"

        if ! ip6tables -C OUTPUT -j china_outbound_v6 2>/dev/null; then
            ip6tables -I OUTPUT 1 -j china_outbound_v6
            log "已将 china_outbound_v6 链挂接到 IPv6 OUTPUT"
        fi
        if ! ip6tables -C FORWARD -j china_outbound_v6 2>/dev/null; then
            ip6tables -I FORWARD 1 -j china_outbound_v6
            log "已将 china_outbound_v6 链挂接到 IPv6 FORWARD"
        fi
        log "IPv6 出向中国流量限制配置完成"
    fi

    log "出向中国流量限制规则配置完成（OUTPUT + FORWARD）"
    return 0
}

# 交互式选择要保护的端口
select_ports_to_protect() {
    echo ""
    echo "=========================================="
    echo "  扫描对外监听的端口和服务"
    echo "=========================================="
    echo ""
    
    local port_file=$(scan_listening_ports)
    
    if [[ ! -f "$port_file" ]] || [[ ! -s "$port_file" ]]; then
        log "未扫描到监听端口，使用默认配置"
        echo "未扫描到对外监听的端口，将使用以下默认端口："
        echo "  1. 53/tcp   - DNS"
        echo "  2. 53/udp   - DNS"
        echo "  3. 443/tcp  - HTTPS"
        echo "  4. 853/tcp  - DNS over TLS"
        echo ""
        read -p "是否使用默认配置? (Y/n): " use_default
        if [[ ! "$use_default" =~ ^[Nn]$ ]]; then
            PROTECTED_PORTS=("53/tcp" "53/udp" "443/tcp" "853/tcp")
        fi
        ask_protocol_options
        return 0
    fi
    
    # 显示扫描到的端口
    echo "扫描到以下对外监听的端口和服务："
    echo ""
    
    declare -a ports_array
    declare -a display_array
    local index=1
    
    while IFS='|' read -r port_proto process; do
        [[ -z "$port_proto" ]] && continue
        ports_array+=($port_proto)
        printf "  %d. %-15s - %s\n" "$index" "$port_proto" "${process:-未知服务}"
        ((index++))
    done < "$port_file"
    
    local total_ports=$((index - 1))
    
    if [[ $total_ports -eq 0 ]]; then
        log "未找到有效的监听端口"
        rm -f "$port_file"
        return 1
    fi
    
    echo ""
    echo "=========================================="
    echo "请选择要限制访问的端口："
    echo "  - 输入端口编号（多个用空格或逗号分隔，如: 1 2 3 或 1,2,3）"
    echo "  - 按回车键全选所有端口"
    echo "  - 输入 'n' 或 'none' 跳过端口限制"
    echo "  - 输入 'c' 或 'custom' 自定义端口（支持指定IP、端口范围）"
    echo "  - 输入 'r' 或 'reverse' 反选模式（选一个豁免端口，其余已监听端口及低位端口限制仅China访问）"
    echo "=========================================="
    read -p "您的选择: " selection
    
    # 处理选择
    if [[ -z "$selection" ]]; then
        # 全选
        PROTECTED_PORTS=("${ports_array[@]}")
        log "已选择所有 ${#PROTECTED_PORTS[@]} 个端口进行保护"
        echo "✓ 已选择所有端口"
    elif [[ "$selection" =~ ^[Nn]([Oo][Nn][Ee])?$ ]]; then
        # 不选择任何端口
        PROTECTED_PORTS=()
        log "用户选择不限制任何端口"
        echo "✓ 已跳过端口限制"
    elif [[ "$selection" =~ ^[Rr]([Ee][Vv][Ee][Rr][Ss][Ee])?$ ]]; then
        # 反选模式：选一个豁免端口，其余全部限制仅China访问（含未配置端口）
        PROTECTED_PORTS=()
        log "用户选择反选模式"
        if select_exempt_port "$port_file"; then
            rm -f "$port_file"
            ask_protocol_options
            return 0
        else
            # 选择失败，回退到普通模式重新提示
            REVERSE_MODE=false
            log "反选模式选择失败，回退"
            rm -f "$port_file"
            return 1
        fi
    elif [[ "$selection" =~ ^[Cc]([Uu][Ss][Tt][Oo][Mm])?$ ]]; then
        # 自定义端口模式（跳过扫描列表，直接进入自定义配置）
        PROTECTED_PORTS=()
        rm -f "$port_file"
        log "用户选择自定义端口模式"
        select_custom_ports
        ask_protocol_options
        echo ""
        if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]] || [[ "$PROTECT_ICMP" == true ]] || [[ "$PROTECT_UDP_ALL" == true ]]; then
            echo "将保护以下端口/协议："
            for p in "${PROTECTED_PORTS[@]}"; do
                local bound_ip="${PROTECTED_PORTS_IP[$p]:-所有IP}"
                echo "  - $p（目标IP: $bound_ip）"
            done
            [[ "$PROTECT_ICMP" == true ]] && echo "  - ICMP (ping)"
            [[ "$PROTECT_UDP_ALL" == true ]] && echo "  - 所有 UDP 流量"
        else
            echo "注意：未选择任何要保护的端口或协议"
        fi
        echo ""
        return 0
    else
        # 处理用户输入的编号
        selection=$(echo "$selection" | tr ',' ' ')
        PROTECTED_PORTS=()
        
        for num in $selection; do
            if [[ "$num" =~ ^[0-9]+$ ]] && [[ $num -ge 1 ]] && [[ $num -le $total_ports ]]; then
                PROTECTED_PORTS+=("${ports_array[$((num-1))]}")
            else
                echo "警告: 忽略无效的选择: $num"
            fi
        done
        
        log "用户选择了 ${#PROTECTED_PORTS[@]} 个端口进行保护"
        echo "✓ 已选择 ${#PROTECTED_PORTS[@]} 个端口"
    fi

    # 询问是否追加自定义端口
    echo ""
    read -p "是否还需要添加自定义端口（单端口/范围/指定IP）? (y/N): " add_custom_ports
    if [[ "$add_custom_ports" =~ ^[Yy]$ ]]; then
        select_custom_ports
    fi
    
    ask_protocol_options
    
    # 清理临时文件
    rm -f "$port_file"
    
    echo ""
    if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]] || [[ "$PROTECT_ICMP" == true ]] || [[ "$PROTECT_UDP_ALL" == true ]]; then
        echo "将保护以下端口/协议："
        for p in "${PROTECTED_PORTS[@]}"; do
            local bound_ip="${PROTECTED_PORTS_IP[$p]:-所有IP}"
            echo "  - $p（目标IP: $bound_ip）"
        done
        [[ "$PROTECT_ICMP" == true ]] && echo "  - ICMP (ping)"
        [[ "$PROTECT_UDP_ALL" == true ]] && echo "  - 所有 UDP 流量"
    else
        echo "注意：未选择任何要保护的端口或协议"
    fi
    
    echo ""
    return 0
}

# 询问是否清理所有iptables规则
ask_clean_iptables() {
    echo ""
    echo "=========================================="
    echo "  iptables 规则清理选项"
    echo "=========================================="
    echo ""
    echo "当前系统的 iptables INPUT 链规则数量："
    local rule_count=$(iptables -L INPUT -n --line-numbers 2>/dev/null | grep -c "^[0-9]")
    echo "  共 $rule_count 条规则"
    echo ""
    echo "选项："
    echo "  1. 清理所有现有 iptables 规则（慎重！）"
    echo "  2. 仅修改本脚本相关的规则（推荐）"
    echo ""
    read -p "请选择 (1/2，默认为2): " clean_choice
    
    if [[ "$clean_choice" == "1" ]]; then
        echo ""
        echo "警告：这将清除所有 iptables INPUT 链规则！"
        read -p "确认清理所有规则? (yes/no): " confirm_clean
        if [[ "$confirm_clean" == "yes" ]]; then
            log "用户选择清理所有iptables规则"
            echo "正在清理所有 iptables 规则..."
            flush_iptables_preserve_fail2ban
            echo "✓ 已清理所有规则"
        else
            log "用户取消清理所有规则"
            echo "已取消清理，将仅修改相关规则"
        fi
    else
        log "用户选择仅修改本脚本相关规则"
        echo "✓ 将仅修改本脚本相关的规则"
    fi
    
    echo ""
}

# 显示脚本介绍
show_intro() {
    clear
    echo "=========================================="
    echo "  中国IP白名单防火墙安装脚本"
    echo "=========================================="
    echo ""
    echo "【功能说明】"
    echo "  • 限制指定端口仅允许中国IP访问"
    echo "  • 支持自定义IP段白名单"
    echo "  • 自动下载并更新中国IP列表"
    echo "  • 配置开机自启动和定时更新"
    echo ""
    echo "【功能特性】"
    echo "  • 扫描当前监听端口并选择保护"
    echo "  • 可选择是否清理现有防火墙规则"
    echo "  • 灵活的端口保护配置"
    echo ""
    echo "【配置文件位置】"
    echo "  自定义IP段: $CUSTOM_IP_FILE"
    echo "  日志文件: $LOG_FILE"
    echo ""
    echo "=========================================="
    echo ""
}

# 检查是否以root运行
check_root() {
    if [[ $EUID -ne 0 ]]; then
        echo "错误：此脚本必须以root权限运行"
        exit 1
    fi
}

# 检查依赖
check_dependencies() {
    local deps=("iptables" "ipset" "curl")
    local missing=()
    
    for dep in "${deps[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            missing+=("$dep")
        fi
    done
    
    if [[ ${#missing[@]} -gt 0 ]]; then
        echo "错误：缺少以下依赖工具: ${missing[*]}"
        echo "请先安装这些工具后再运行脚本"
        exit 1
    fi
}

# 从APNIC获取中国IP
fetch_apnic() {
    log "尝试从APNIC获取中国IP列表..."
    local temp_raw="/tmp/apnic_raw_$$.txt"
    if curl -sL --connect-timeout 10 --max-time 60 "${GEOIP_SOURCES[0]}" -o "$temp_raw"; then
        awk -F'|' '/CN\|ipv4/ {printf("%s/%d\n", $4, 32-log($5)/log(2))}' "$temp_raw" > "$TEMP_IPLIST"
        awk -F'|' '/CN\|ipv6/ {printf("%s/%d\n", $4, 128-log($5)/log(2))}' "$temp_raw" > "$TEMP_IPLIST_V6"
        rm -f "$temp_raw"
        return 0
    else
        rm -f "$temp_raw"
        return 1
    fi
}

# 从GitHub ipip.net获取
fetch_ipip() {
    log "尝试从ipip.net获取中国IP列表..."
    if curl -sL --connect-timeout 10 --max-time 60 "${GEOIP_SOURCES[1]}" > "$TEMP_IPLIST"; then
        return 0
    else
        return 1
    fi
}

# 从GitHub misakaio获取
fetch_misakaio() {
    log "尝试从misakaio获取中国IP列表..."
    if curl -sL --connect-timeout 10 --max-time 60 "${GEOIP_SOURCES[2]}" > "$TEMP_IPLIST"; then
        return 0
    else
        return 1
    fi
}

fetch_apnic_v6_only() {
    log "尝试补充APNIC IPv6列表..."
    local temp_raw="/tmp/apnic_v6_$$.txt"
    if curl -sL --connect-timeout 10 --max-time 60 "${GEOIP_SOURCES[0]}" -o "$temp_raw"; then
        awk -F'|' '/CN\|ipv6/ {printf("%s/%d\n", $4, 128-log($5)/log(2))}' "$temp_raw" > "$TEMP_IPLIST_V6"
        rm -f "$temp_raw"
        return 0
    fi
    rm -f "$temp_raw"
    return 1
}

# 下载中国IP列表（带重试机制）
download_china_ip() {
    local success=false
    : > "$TEMP_IPLIST_V6"
    
    # 尝试所有数据源
    local source=""
    for attempt in fetch_apnic fetch_ipip fetch_misakaio; do
        if $attempt && [[ -s "$TEMP_IPLIST" ]]; then
            local line_count=$(wc -l < "$TEMP_IPLIST")
            if [[ $line_count -gt 1000 ]]; then
                log "成功下载中国IP列表，共 $line_count 条记录"
                success=true
                source="$attempt"
                break
            else
                log "警告：下载的IP列表条目过少 ($line_count)，尝试下一个源"
            fi
        else
            log "警告：从当前源下载失败，尝试下一个源"
        fi
        rm -f "$TEMP_IPLIST"
    done
    
    if [[ "$success" == false ]]; then
        log "错误：所有数据源均下载失败"
        return 1
    fi

    if [[ ! -s "$TEMP_IPLIST_V6" ]]; then
        if ! fetch_apnic_v6_only; then
            log "警告：无法获取IPv6中国IP列表，IPv6流量将不受限"
            : > "$TEMP_IPLIST_V6"
        fi
    fi
    
    return 0
}

# 交互式添加自定义IP段
add_custom_ips_interactive() {
    mkdir -p "$(dirname "$CUSTOM_IP_FILE")"
    
    echo ""
    echo "=========================================="
    echo "  添加/修改自定义IP段"
    echo "=========================================="
    echo "这些IP段获得最高优先级白名单，完全不受任何防火墙限制"
    echo "格式: IPv4 CIDR 如 103.143.14.0/24  或 IPv6 如 2001:db8::/32"
    echo ""

    # 显示当前已有的自定义IP段
    local existing_count=0
    if [[ -f "$CUSTOM_IP_FILE" ]]; then
        existing_count=$(grep -v "^#" "$CUSTOM_IP_FILE" 2>/dev/null | grep -c "^[0-9a-fA-F:]" || echo 0)
    fi

    local write_mode="1"  # 默认追加

    if [[ $existing_count -gt 0 ]]; then
        echo "当前已有 $existing_count 个自定义IP段："
        grep -v "^#" "$CUSTOM_IP_FILE" 2>/dev/null | grep "^[0-9a-fA-F:]" | head -10 | sed 's/^/  /'
        if [[ $existing_count -gt 10 ]]; then
            echo "  ... (共 $existing_count 个，完整列表见 $CUSTOM_IP_FILE)"
        fi
        echo ""
        echo "请选择操作："
        echo "  1. 追加到现有列表（保留原有IP段）← 默认"
        echo "  2. 覆盖现有列表（清空后重新写入）"
        echo "  3. 退出不修改"
        read -p "请选择 (1/2/3，默认1): " write_mode
        write_mode="${write_mode:-1}"
        case "$write_mode" in
            2)
                echo ""
                echo "警告：将清空现有 $existing_count 个IP段！"
                read -p "确认覆盖? (yes/no): " confirm_overwrite
                if [[ "$confirm_overwrite" != "yes" ]]; then
                    echo "已取消，保持原有列表不变"
                    return 0
                fi
                log "用户选择覆盖写入自定义IP段"
                ;;
            3)
                echo "已退出，未做任何修改"
                return 0
                ;;
            *)
                write_mode="1"
                log "用户选择追加模式"
                ;;
        esac
    else
        echo "当前无自定义IP段"
        echo ""
        read -p "是否现在添加自定义IP段? (y/N): " add_now
        if [[ ! "$add_now" =~ ^[Yy]$ ]]; then
            log "跳过添加自定义IP段"
            echo ""
            echo "提示: 您可以随时通过菜单选项4来添加IP段"
            echo "  配置文件位置: $CUSTOM_IP_FILE"
            return 0
        fi
    fi
    
    echo ""
    echo "请输入需要添加的IP段（CIDR格式），每行一个"
    echo "  支持IPv4: 103.143.14.0/24"
    echo "  支持IPv6: 2001:db8::/32"
    echo "输入空行完成，输入 'skip' 跳过"
    echo ""
    
    local custom_ips=()
    local line_num=1
    
    while true; do
        read -p "IP段 #$line_num (留空完成): " ip
        
        if [[ "$ip" == "skip" ]]; then
            log "跳过添加自定义IP段"
            break
        fi
        
        if [[ -z "$ip" ]]; then
            break
        fi
        
        # 验证IP格式（支持IPv4和IPv6 CIDR）
        if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]] || \
           [[ "$ip" =~ ^[0-9a-fA-F:]+/[0-9]{1,3}$ ]]; then
            custom_ips+=("$ip")
            echo "  ✓ 已添加: $ip"
            ((line_num++))
        else
            echo "  ✗ 格式错误，请使用CIDR格式（如：192.168.1.0/24 或 2001:db8::/32）"
        fi
    done
    
    # 保存到文件
    if [[ ${#custom_ips[@]} -gt 0 ]]; then
        if [[ "$write_mode" == "2" ]]; then
            printf "%s\n" "${custom_ips[@]}" > "$CUSTOM_IP_FILE"
            log "已覆盖写入 ${#custom_ips[@]} 个自定义IP段到 $CUSTOM_IP_FILE"
            echo ""
            echo "✓ 已覆盖写入 ${#custom_ips[@]} 个自定义IP段"
        else
            printf "%s\n" "${custom_ips[@]}" >> "$CUSTOM_IP_FILE"
            local new_total
            new_total=$(grep -v "^#" "$CUSTOM_IP_FILE" 2>/dev/null | grep -c "^[0-9a-fA-F:]" || echo 0)
            log "已追加 ${#custom_ips[@]} 个自定义IP段，共 $new_total 个"
            echo ""
            echo "✓ 已追加 ${#custom_ips[@]} 个自定义IP段（共 $new_total 个）"
        fi
    else
        echo ""
        echo "未添加任何IP段"
    fi
    
    return 0
}

# 初始化/更新独立的 custom_whitelist ipset（最高优先级白名单）
setup_custom_whitelist_ipset() {
    if ! ipset list "$CUSTOM_IPSET_NAME" &>/dev/null; then
        if ! ipset create "$CUSTOM_IPSET_NAME" hash:net family inet hashsize 256 maxelem 65536 2>/dev/null; then
            log "警告：创建 $CUSTOM_IPSET_NAME ipset 失败"
            return 1
        fi
        log "已创建 $CUSTOM_IPSET_NAME ipset"
    fi
    local v6_name="${CUSTOM_IPSET_NAME}_v6"
    if ipv6_firewall_available && ! ipset list "$v6_name" &>/dev/null; then
        if ! ipset create "$v6_name" hash:net family inet6 hashsize 256 maxelem 65536 2>/dev/null; then
            log "警告：创建 ${v6_name} ipset 失败"
        else
            log "已创建 ${v6_name} ipset"
        fi
    fi
    return 0
}

# 加载自定义IP段到ipset（同时维护 custom_whitelist，获得最高优先级豁免）
load_custom_ips() {
    if [[ ! -f "$CUSTOM_IP_FILE" ]]; then
        return 0
    fi

    # 确保独立白名单 ipset 存在
    setup_custom_whitelist_ipset

    local count=0
    local count_v6=0
    local v6_name="${CUSTOM_IPSET_NAME}_v6"
    while IFS= read -r ip; do
        [[ -z "$ip" || "$ip" =~ ^# ]] && continue
        
        if [[ "$ip" == *:* ]]; then
            # IPv6：加入 china_ip_v6 和 custom_whitelist_v6
            if ipv6_firewall_available; then
                ipset add "$IPSET_NAME_V6" "$ip" 2>/dev/null || true
                ipset add "$v6_name" "$ip" 2>/dev/null || true
                ((count_v6++))
            fi
        else
            # IPv4：同时加入 china_ip 和 custom_whitelist
            ipset add "$IPSET_NAME" "$ip" 2>/dev/null || true
            ipset add "$CUSTOM_IPSET_NAME" "$ip" 2>/dev/null || true
            ((count++))
        fi
    done < "$CUSTOM_IP_FILE"
    
    if [[ $count -gt 0 ]]; then
        log "已加载 $count 个自定义IPv4 IP段（china_ip + custom_whitelist）"
    fi
    if [[ $count_v6 -gt 0 ]]; then
        log "已加载 $count_v6 个自定义IPv6 IP段（china_ip_v6 + custom_whitelist_v6）"
    fi
}

# 创建或更新ipset
update_ipset() {
    log "开始更新ipset..."
    
    # 创建临时ipset
    if ipset list "${IPSET_NAME}_temp" &>/dev/null; then
        log "删除已存在的临时ipset..."
        ipset destroy "${IPSET_NAME}_temp"
    fi
    
    log "创建临时ipset..."
    if ! ipset create "${IPSET_NAME}_temp" hash:net family inet hashsize 4096 maxelem 100000; then
        log "错误：创建ipset失败"
        return 1
    fi
    
    # 导入IP列表
    log "开始导入IP列表..."
    local count=0
    local error_count=0
    
    while IFS= read -r ip; do
        # 跳过空行和注释
        [[ -z "$ip" || "$ip" =~ ^# ]] && continue
        
        # 验证IP格式
        if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
            if ipset add "${IPSET_NAME}_temp" "$ip" 2>/dev/null; then
                ((count++))
            else
                ((error_count++))
            fi
            
            # 每1000条显示进度
            if (( count % 1000 == 0 )); then
                log "已处理 $count 条IP规则..."
            fi
        fi
    done < "$TEMP_IPLIST"
    
    log "IP导入完成：成功 $count 条，失败 $error_count 条"
    
    if [[ $count -lt 100 ]]; then
        log "错误：导入的IP数量过少，可能存在问题"
        ipset destroy "${IPSET_NAME}_temp" 2>/dev/null
        return 1
    fi
    
    # 原子性替换
    if ipset list "$IPSET_NAME" &>/dev/null; then
        log "交换ipset..."
        ipset swap "${IPSET_NAME}_temp" "$IPSET_NAME"
        ipset destroy "${IPSET_NAME}_temp"
        log "已更新现有ipset"
    else
        log "重命名ipset..."
        ipset rename "${IPSET_NAME}_temp" "$IPSET_NAME"
        log "已创建新ipset"
    fi
    
    # 加载自定义IP段
    load_custom_ips
    
    # 清理临时文件
    rm -f "$TEMP_IPLIST"
    
    # 验证ipset
    local final_count=$(ipset list "$IPSET_NAME" 2>/dev/null | grep -c "^[0-9]" || echo 0)
    log "最终ipset包含 $final_count 条规则"
    
    return 0
}

# 检查规则是否是fail2ban规则（防止误删fail2ban的封禁规则）
is_fail2ban_iptables_rule() {
    local rule="$1"
    # fail2ban规则特征：
    # 1. 跳转到fail2ban链（-j f2b-* 或 -j fail2ban-*）
    # 2. 在fail2ban链中
    # 3. 包含fail2ban相关的注释或标记
    if [[ "$rule" =~ -j\ (f2b-|fail2ban-) ]] || \
       [[ "$rule" =~ ^-A\ (f2b-|fail2ban-) ]] || \
       [[ "$rule" =~ fail2ban ]]; then
        return 0
    fi
    return 1
}

# 清理旧的相关规则
cleanup_old_rules() {
    log "清理旧的相关iptables规则（保留fail2ban封禁规则）..."

    # 清理端口0阻断规则（重装时先移除，再由 setup_block_port_zero 重建到正确位置）
    cleanup_block_port_zero

    # ---- 清理最高优先级豁免规则（custom_whitelist / WireGuard / 游戏UDP）----

    # 清理自定义白名单豁免规则
    while iptables -t mangle -C PREROUTING -m set --match-set "$CUSTOM_IPSET_NAME" src -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -m set --match-set "$CUSTOM_IPSET_NAME" src -j RETURN 2>/dev/null || break
    done

    # 清理 WireGuard 接口豁免规则
    while iptables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -i wg+ -j RETURN 2>/dev/null || break
    done
    # 清理 WireGuard UDP 端口豁免（扫描并删除所有 wg 相关端口的 RETURN 规则）
    IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
    for wgp in "${wg_ports[@]}"; do
        wgp="${wgp// /}"
        [[ -z "$wgp" ]] && continue
        while iptables -t mangle -C PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null; do
            iptables -t mangle -D PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null || break
        done
        while iptables -C INPUT -p udp --dport "$wgp" -j ACCEPT 2>/dev/null; do
            iptables -D INPUT -p udp --dport "$wgp" -j ACCEPT 2>/dev/null || break
        done
    done

    # 清理旧版本遗留的高位 UDP 入站豁免。游戏的主动出站连接走 OUTPUT，
    # 不需要也不能通过 PREROUTING 放开所有高位 UDP 入站端口。
    while iptables -t mangle -C PREROUTING -p udp --dport 1024:65535 -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -p udp --dport 1024:65535 -j RETURN 2>/dev/null || break
    done

    # 清理内网 IP 段豁免规则（RFC1918 私有地址 + loopback + wg+ 接口）
    for subnet in "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" "127.0.0.0/8"; do
        while iptables -t mangle -C PREROUTING -s "$subnet" -j RETURN 2>/dev/null; do
            iptables -t mangle -D PREROUTING -s "$subnet" -j RETURN 2>/dev/null || break
        done
    done
    # 清理 loopback 接口豁免
    while iptables -t mangle -C PREROUTING -i lo -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -i lo -j RETURN 2>/dev/null || break
    done
    # 清理 wg+ 无条件豁免（内网网卡统配，不依赖 WIREGUARD_EXEMPT 开关）
    # 注意：此处只清理无条件的 -i wg+ RETURN，
    # WIREGUARD_EXEMPT 开关控制的同名规则由上面 WireGuard 清理段处理
    # 两者规则形式相同，while 循环会一并清理，无需额外处理

    # 清理 DOCKER-USER 链（flush 后由 setup_iptables 重建，消除累积的冗余 RETURN 规则）
    # 注意：DOCKER-USER 是用户扩展点，Docker 自身端口映射在 DOCKER 链，不受影响
    if iptables -nL DOCKER-USER &>/dev/null 2>&1; then
        iptables -F DOCKER-USER 2>/dev/null || true
        log "已清空 DOCKER-USER 链（消除冗余 RETURN 规则，将在 setup_iptables 中重建）"
    fi
    if command -v ip6tables &>/dev/null && ip6tables -nL DOCKER-USER &>/dev/null 2>&1; then
        ip6tables -F DOCKER-USER 2>/dev/null || true
        log "已清空 IPv6 DOCKER-USER 链"
    fi

    # 清理旧的端口保护规则
    ensure_docker_chain

    while read -r rule; do
        [[ -z "$rule" ]] && continue
        # 检查是否是fail2ban规则，如果是则跳过
        if is_fail2ban_iptables_rule "$rule"; then
            log "跳过fail2ban规则：${rule#-A }"
            continue
        fi
        local delete_rule="${rule/-A /-D }"
        if iptables -t mangle $delete_rule 2>/dev/null; then
            log "已批量删除历史mangle规则：${rule#-A }"
        fi
    done < <(iptables -t mangle -S PREROUTING 2>/dev/null | grep -- "--match-set $IPSET_NAME" | grep -- "MARK --set-mark $BLOCK_MARK" || true)

    # ========== 重要：删除inition.sh添加的端口开放规则（如果这些端口在保护列表中）==========
    # 定义inition.sh可能开放的端口列表
    local inition_ports=("12748" "80" "443" "5222" "7777" "8080" "8007" "21" "20" "8006")
    local inition_port_ranges=("30000:31000" "10000:65535")
    
    log "检查并删除inition.sh添加的端口开放规则（如果这些端口需要被限制）..."
    
    # 检查是否有保护端口配置
    if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]]; then
        # 提取所有受保护端口的端口号
        declare -A protected_ports_map=()
        for port_proto in "${PROTECTED_PORTS[@]}"; do
            local port="${port_proto%/*}"
            protected_ports_map["$port"]=1
        done
        
        # 检查是否选择了限制所有端口（通过检查端口数量来判断）
        # 如果受保护端口数量 >= 5，可能选择了限制所有端口，删除所有inition.sh的端口规则
        local protect_all_ports=false
        if [[ ${#PROTECTED_PORTS[@]} -ge 5 ]]; then
            protect_all_ports=true
            log "检测到选择了限制多个端口（${#PROTECTED_PORTS[@]}个端口），将删除所有inition.sh的端口ACCEPT规则以确保限制生效"
        fi
        
        # 删除inition.sh添加的特定端口ACCEPT规则
        # 如果选择了限制所有端口，或者这个端口在保护列表中
        for port in "${inition_ports[@]}"; do
            if [[ "$protect_all_ports" == true ]] || [[ -n "${protected_ports_map[$port]}" ]]; then
                log "端口 $port 需要被限制，删除inition.sh添加的ACCEPT规则（保留fail2ban规则）..."
                
                # 删除TCP端口的ACCEPT规则（可能有多个，需要全部删除）
                # 但需要检查是否是fail2ban规则
                while iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null; do
                    # 检查这条规则是否是fail2ban规则
                    local rule_line=$(iptables -L INPUT -n --line-numbers 2>/dev/null | grep -E "tcp.*dpt:$port.*ACCEPT" | head -1)
                    if echo "$rule_line" | grep -qE "f2b-|fail2ban-"; then
                        log "跳过fail2ban规则：tcp/$port ACCEPT"
                        break
                    fi
                    if iptables -D INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
                        log "已删除inition.sh规则：ACCEPT tcp/$port"
                    else
                        break
                    fi
                done
                
                # 删除UDP端口的ACCEPT规则（如果有）
                while iptables -C INPUT -p udp --dport "$port" -j ACCEPT 2>/dev/null; do
                    # 检查这条规则是否是fail2ban规则
                    local rule_line=$(iptables -L INPUT -n --line-numbers 2>/dev/null | grep -E "udp.*dpt:$port.*ACCEPT" | head -1)
                    if echo "$rule_line" | grep -qE "f2b-|fail2ban-"; then
                        log "跳过fail2ban规则：udp/$port ACCEPT"
                        break
                    fi
                    if iptables -D INPUT -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
                        log "已删除inition.sh规则：ACCEPT udp/$port"
                    else
                        break
                    fi
                done
                
                # 删除通过ddos_protect链的规则（这些不会是fail2ban规则）
                while iptables -C INPUT -p tcp --dport "$port" -m state --state NEW -j ddos_protect 2>/dev/null; do
                    if iptables -D INPUT -p tcp --dport "$port" -m state --state NEW -j ddos_protect 2>/dev/null; then
                        log "已删除inition.sh规则：ddos_protect tcp/$port"
                    else
                        break
                    fi
                done
            fi
        done
        
        # 额外处理：删除所有受保护端口的简单ACCEPT规则（无论是否在inition.sh的标准列表中）
        # 这确保所有受保护端口都不会被简单ACCEPT规则绕过
        log "删除所有受保护端口的简单ACCEPT规则（确保限制生效）..."
        for port_proto in "${PROTECTED_PORTS[@]}"; do
            local port="${port_proto%/*}"
            local proto="${port_proto#*/}"
            
        # 删除该端口的简单ACCEPT规则（不包含ipset匹配的，这些是china_ip_firewall.sh添加的）
        # 使用循环确保删除所有匹配的规则
        # 先按精确规则删除无条件 ACCEPT。此方式不会匹配带 ipset 的中国IP放行规则。
        while iptables -C INPUT -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; do
            if iptables -D INPUT -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; then
                log "已删除简单ACCEPT规则：$proto/$port"
            else
                break
            fi
        done
        while true; do
                # 查找该端口的ACCEPT规则（不包含ipset匹配的）
                local rule_num=$(iptables -L INPUT -n --line-numbers 2>/dev/null | \
                    awk -v proto="$proto" -v port="$port" \
                    '$0 ~ proto && $0 ~ "dpt:" port && $0 ~ "ACCEPT" && $0 !~ "match-set" && $0 !~ "f2b-" && $0 !~ "fail2ban-" {print $1; exit}')
                
                if [[ -n "$rule_num" ]]; then
                    # 检查这条规则是否真的是简单ACCEPT（不是fail2ban或china_ip规则）
                    local full_rule=$(iptables -L INPUT -n --line-numbers 2>/dev/null | awk "NR==$rule_num")
                    if echo "$full_rule" | grep -qE "f2b-|fail2ban-|match-set"; then
                        log "跳过fail2ban或china_ip规则：$proto/$port"
                        break
                    fi
                    # 删除这条规则
                    if iptables -D INPUT "$rule_num" 2>/dev/null; then
                        log "已删除简单ACCEPT规则：$proto/$port (位置: $rule_num)"
                    else
                        break
                    fi
                else
                    break
                fi
            done
        done
        
        # 删除高端口范围的ACCEPT规则
        # 如果选择了限制所有端口，或者有高端口在保护列表中，或者有端口在10000-65535范围内
        local has_high_port=false
        if [[ "$protect_all_ports" == true ]]; then
            has_high_port=true
        else
            for port_proto in "${PROTECTED_PORTS[@]}"; do
                local port="${port_proto%/*}"
                if [[ "$port" -ge 10000 ]] && [[ "$port" -le 65535 ]]; then
                    has_high_port=true
                    break
                fi
            done
        fi
        
        if [[ "$has_high_port" == true ]]; then
            log "删除inition.sh添加的高端口范围ACCEPT规则（10000-65535）..."
            # 删除TCP高端口范围
            while iptables -C INPUT -p tcp --dport 10000:65535 -j ACCEPT 2>/dev/null; do
                if iptables -D INPUT -p tcp --dport 10000:65535 -j ACCEPT 2>/dev/null; then
                    log "已删除inition.sh规则：ACCEPT tcp/10000:65535"
                else
                    break
                fi
            done
            # 删除UDP高端口范围
            while iptables -C INPUT -p udp --dport 10000:65535 -j ACCEPT 2>/dev/null; do
                if iptables -D INPUT -p udp --dport 10000:65535 -j ACCEPT 2>/dev/null; then
                    log "已删除inition.sh规则：ACCEPT udp/10000:65535"
                else
                    break
                fi
            done
        fi
        
        # 删除FTP被动模式端口范围（如果选择了限制所有端口，或者有端口在30000-31000范围内）
        local has_ftp_port=false
        if [[ "$protect_all_ports" == true ]]; then
            has_ftp_port=true
        else
            for port_proto in "${PROTECTED_PORTS[@]}"; do
                local port="${port_proto%/*}"
                if [[ "$port" -ge 30000 ]] && [[ "$port" -le 31000 ]]; then
                    has_ftp_port=true
                    break
                fi
            done
        fi
        
        if [[ "$has_ftp_port" == true ]]; then
            log "删除inition.sh添加的FTP被动模式端口范围ACCEPT规则（30000-31000）..."
            while iptables -C INPUT -p tcp --dport 30000:31000 -j ACCEPT 2>/dev/null; do
                if iptables -D INPUT -p tcp --dport 30000:31000 -j ACCEPT 2>/dev/null; then
                    log "已删除inition.sh规则：ACCEPT tcp/30000:31000"
                else
                    break
                fi
            done
        fi
    fi

    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        
        # 删除DROP规则（但跳过fail2ban规则）
        while iptables -C INPUT -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; do
            # 检查是否是fail2ban规则（fail2ban的DROP规则不会使用ipset匹配）
            # fail2ban的规则通常是直接DROP，不包含ipset匹配
            if iptables -D INPUT -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
                log "已删除旧规则：DROP $proto/$port（非中国IP）"
            else
                log "警告：删除旧规则失败：DROP $proto/$port"
                break
            fi
        done
        
        # 删除ACCEPT规则（但跳过fail2ban规则）
        while iptables -C INPUT -s 127.0.0.0/8 -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; do
            # 检查是否是fail2ban规则
            local rule_line=$(iptables -L INPUT -n --line-numbers 2>/dev/null | grep -E "$proto.*dpt:$port.*ACCEPT" | grep "127.0.0.0/8" | head -1)
            if echo "$rule_line" | grep -qE "f2b-|fail2ban-"; then
                log "跳过fail2ban规则：ACCEPT localhost $proto/$port"
                break
            fi
            if iptables -D INPUT -s 127.0.0.0/8 -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; then
                log "已删除旧规则：ACCEPT localhost $proto/$port"
            else
                log "警告：删除旧规则失败：ACCEPT localhost $proto/$port"
                break
            fi
        done

        if ensure_docker_chain; then
            while iptables -C DOCKER-USER -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; do
                if iptables -D DOCKER-USER -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
                    log "已删除DOCKER-USER链规则：DROP 非中国IP $proto/$port"
                else
                    log "警告：删除DOCKER-USER链规则失败：DROP 非中国IP $proto/$port"
                    break
                fi
            done
        fi
    done
    
    # 清理ICMP规则
    while iptables -C INPUT -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; do
        if iptables -D INPUT -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
            log "已删除旧规则：DROP ICMP"
        else
            log "警告：删除旧规则失败：DROP ICMP"
            break
        fi
    done

    # 删除 inition.sh 添加的无条件 ping 放行规则。该规则会短路后续的 china_ip ICMP 限制。
    while iptables -C INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; do
        if iptables -D INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; then
            log "已删除旧规则：无条件 ACCEPT ICMP ping"
        else
            log "警告：删除无条件 ACCEPT ICMP ping 失败"
            break
        fi
    done
    
    while iptables -C INPUT -s 127.0.0.0/8 -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; do
        if iptables -D INPUT -s 127.0.0.0/8 -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; then
            log "已删除旧规则：ACCEPT localhost ICMP"
        else
            log "警告：删除旧规则失败：ACCEPT localhost ICMP"
            break
        fi
    done

    if ensure_docker_chain && [[ "$PROTECT_ICMP" == true ]]; then
        while iptables -C DOCKER-USER -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; do
            if iptables -D DOCKER-USER -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
                log "已删除DOCKER-USER链 ICMP 限制规则"
            else
                log "警告：删除DOCKER-USER链 ICMP 限制规则失败"
                break
            fi
        done
    fi
    
    while iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        if iptables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            log "已删除通用标记规则：INPUT DROP mark $BLOCK_MARK"
        else
            log "警告：删除通用标记规则失败：INPUT DROP mark $BLOCK_MARK"
            break
        fi
    done

    if ensure_docker_chain; then
        while iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
            if iptables -D DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                log "已删除DOCKER-USER链标记规则：DROP mark $BLOCK_MARK"
            else
                log "警告：删除DOCKER-USER链标记规则失败：DROP mark $BLOCK_MARK"
                break
            fi
        done
    fi

    # 清理 UDP-all 限制规则（旧规则清理）
    while iptables -t mangle -C PREROUTING -p udp -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; do
        if iptables -t mangle -D PREROUTING -p udp -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
            log "已删除旧规则：UDP-all mangle 标记"
        else
            break
        fi
    done
    while iptables -C INPUT -p udp -m set --match-set "$IPSET_NAME" src -j ACCEPT 2>/dev/null; do
        if iptables -D INPUT -p udp -m set --match-set "$IPSET_NAME" src -j ACCEPT 2>/dev/null; then
            log "已删除旧规则：UDP-all ACCEPT 中国IP"
        else
            break
        fi
    done

    # 清理 INPUT 链中的裸 ACCEPT all 规则（无任何匹配条件，只有 -j ACCEPT 的规则）
    # 注意：必须精确匹配，不能用 -C INPUT -j ACCEPT，那会错误匹配所有带 ACCEPT 的规则
    # 正确方式：用 iptables-save 输出过滤，找到仅含 -A INPUT -j ACCEPT 的行
    if [[ "$DRY_RUN" != true ]]; then
        local bare_accept_count
        bare_accept_count=$(iptables-save 2>/dev/null | grep -cxF -- '-A INPUT -j ACCEPT' || true)
        if [[ "$bare_accept_count" -gt 0 ]]; then
            local i
            for ((i=0; i<bare_accept_count; i++)); do
                iptables -D INPUT -j ACCEPT 2>/dev/null || break
            done
            log "已清理 $bare_accept_count 条 INPUT 链裸 ACCEPT all 规则（将在规则配置末尾重新追加）"
        fi
    fi

    log "旧规则清理完成"
}

# 清理旧的IPv6相关规则
cleanup_old_rules_v6() {
    if ! ipv6_firewall_available; then
        return 0
    fi
    log "清理旧的IPv6 iptables规则..."

    # 清理 IPv6 自定义白名单豁免规则
    local v6_custom="${CUSTOM_IPSET_NAME}_v6"
    if ipset list "$v6_custom" &>/dev/null; then
        while ip6tables -t mangle -C PREROUTING -m set --match-set "$v6_custom" src -j RETURN 2>/dev/null; do
            ip6tables -t mangle -D PREROUTING -m set --match-set "$v6_custom" src -j RETURN 2>/dev/null || break
        done
    fi
    # 清理 IPv6 WireGuard 豁免规则
    while ip6tables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -i wg+ -j RETURN 2>/dev/null || break
    done
    IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
    for wgp in "${wg_ports[@]}"; do
        wgp="${wgp// /}"
        [[ -z "$wgp" ]] && continue
        while ip6tables -t mangle -C PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null; do
            ip6tables -t mangle -D PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null || break
        done
    done
    # 清理旧版本遗留的 IPv6 高位 UDP 入站豁免。
    while ip6tables -t mangle -C PREROUTING -p udp --dport 1024:65535 -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -p udp --dport 1024:65535 -j RETURN 2>/dev/null || break
    done

    # 清理 IPv6 内网地址豁免规则（ULA + loopback + wg+ 接口）
    for subnet in "fc00::/7" "::1/128"; do
        while ip6tables -t mangle -C PREROUTING -s "$subnet" -j RETURN 2>/dev/null; do
            ip6tables -t mangle -D PREROUTING -s "$subnet" -j RETURN 2>/dev/null || break
        done
    done
    while ip6tables -t mangle -C PREROUTING -i lo -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -i lo -j RETURN 2>/dev/null || break
    done

    ensure_docker_chain_v6

    while read -r rule; do
        [[ -z "$rule" ]] && continue
        local delete_rule="${rule/-A /-D }"
        if ip6tables -t mangle $delete_rule 2>/dev/null; then
            log "已批量删除历史IPv6 mangle规则：${rule#-A }"
        fi
    done < <(ip6tables -t mangle -S PREROUTING 2>/dev/null | grep -- "--match-set $IPSET_NAME_V6" | grep -- "MARK --set-mark $BLOCK_MARK" || true)

    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        
        while ip6tables -C INPUT -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; do
            if ip6tables -D INPUT -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; then
                log "已删除IPv6旧规则：DROP $proto/$port"
            else
                log "警告：删除IPv6旧规则失败：DROP $proto/$port"
                break
            fi
        done
        
        while ip6tables -C INPUT -s ::1 -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; do
            if ip6tables -D INPUT -s ::1 -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null; then
                log "已删除IPv6旧规则：ACCEPT localhost $proto/$port"
            else
                log "警告：删除IPv6旧规则失败：ACCEPT localhost $proto/$port"
                break
            fi
        done

        if ensure_docker_chain_v6; then
            while ip6tables -C DOCKER-USER -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; do
                if ip6tables -D DOCKER-USER -p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; then
                    log "已删除IPv6 DOCKER-USER链规则：DROP 非中国IP $proto/$port"
                else
                    log "警告：删除IPv6 DOCKER-USER链规则失败：DROP 非中国IP $proto/$port"
                    break
                fi
            done
        fi

    done
    
    while ip6tables -C INPUT -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; do
        if ip6tables -D INPUT -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; then
            log "已删除IPv6旧规则：DROP ICMPv6"
        else
            log "警告：删除IPv6旧规则失败：DROP ICMPv6"
            break
        fi
    done
    
    while ip6tables -C INPUT -s ::1 -p icmpv6 --icmpv6-type echo-request -j ACCEPT 2>/dev/null; do
        if ip6tables -D INPUT -s ::1 -p icmpv6 --icmpv6-type echo-request -j ACCEPT 2>/dev/null; then
            log "已删除IPv6旧规则：ACCEPT localhost ICMPv6"
        else
            log "警告：删除IPv6旧规则失败：ACCEPT localhost ICMPv6"
            break
        fi
    done

    if ensure_docker_chain_v6 && [[ "$PROTECT_ICMP" == true ]]; then
        while ip6tables -C DOCKER-USER -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; do
            if ip6tables -D DOCKER-USER -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; then
                log "已删除IPv6 DOCKER-USER链 ICMPv6 限制规则"
            else
                log "警告：删除IPv6 DOCKER-USER链 ICMPv6 限制规则失败"
                break
            fi
        done
    fi
    
    while ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        if ip6tables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            log "已删除IPv6通用标记规则：INPUT DROP mark $BLOCK_MARK"
        else
            log "警告：删除IPv6通用标记规则失败：INPUT DROP mark $BLOCK_MARK"
            break
        fi
    done

    if ensure_docker_chain_v6; then
        while ip6tables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
            if ip6tables -D DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                log "已删除IPv6 DOCKER-USER链标记规则：DROP mark $BLOCK_MARK"
            else
                log "警告：删除IPv6 DOCKER-USER链标记规则失败：DROP mark $BLOCK_MARK"
                break
            fi
        done
    fi

    log "IPv6旧规则清理完成"
}

# 端口0流量日志记录（写入 /var/log/port0_traffic.log）
# 幂等：检测 LOG 前缀 PORT0_DROP: 和 rsyslog 配置文件是否已存在
setup_port0_log() {
    local PORT0_LOG="/var/log/port0_traffic.log"
    local RSYSLOG_CONF="/etc/rsyslog.d/port0.conf"
    local LOGROTATE_CONF="/etc/logrotate.d/port0_traffic"

    # --- 幂等检测 ---
    local iptables_ok=false
    local rsyslog_ok=false
    if iptables -t raw -S PORT0_GUARD 2>/dev/null | grep -q 'PORT0_DROP:'; then
        iptables_ok=true
    fi
    if [[ -f "$RSYSLOG_CONF" ]]; then
        rsyslog_ok=true
    fi

    if [[ "$iptables_ok" == true && "$rsyslog_ok" == true ]]; then
        log "端口0日志功能已存在，检查并重排规则优先级"
    else
        log "配置端口0流量日志记录（raw表 LOG+DROP）..."
    fi

    # --- raw 表独立 PORT0_GUARD 链：LOG 在前，DROP 在后 ---
    # PREROUTING 第一条固定跳转此链，旧版带接口的端口0 DROP 无法抢在日志前面。
    while IFS= read -r old_rule; do
        [[ -z "$old_rule" ]] && continue
        iptables -t raw ${old_rule/-A /-D } 2>/dev/null || true
    done < <(iptables -t raw -S PREROUTING 2>/dev/null | \
        awk '/--(d|s)port 0/ && /-j (DROP|LOG)/')
    if iptables -t raw -nL PORT0_GUARD >/dev/null 2>&1; then
        iptables -t raw -F PORT0_GUARD
    else
        iptables -t raw -N PORT0_GUARD
    fi
    if ipv6_firewall_available; then
        while IFS= read -r old_rule; do
            [[ -z "$old_rule" ]] && continue
            ip6tables -t raw ${old_rule/-A /-D } 2>/dev/null || true
        done < <(ip6tables -t raw -S PREROUTING 2>/dev/null | \
            awk '/--(d|s)port 0/ && /-j (DROP|LOG)/')
        if ip6tables -t raw -nL PORT0_GUARD >/dev/null 2>&1; then
            ip6tables -t raw -F PORT0_GUARD
        else
            ip6tables -t raw -N PORT0_GUARD
        fi
    fi
    for proto in tcp udp; do
        for dir in --dport --sport; do
            iptables -t raw -A PORT0_GUARD -p "$proto" "$dir" 0 \
                -j LOG --log-prefix "PORT0_DROP: " --log-level 4
            iptables -t raw -A PORT0_GUARD -p "$proto" "$dir" 0 -j DROP
            # OUTPUT 链：阻止本机发出端口0包
            if ! iptables -t raw -C OUTPUT -p "$proto" "$dir" 0 -j DROP 2>/dev/null; then
                iptables -t raw -A OUTPUT -p "$proto" "$dir" 0 -j DROP
            fi
            # IPv6 同步
            if ipv6_firewall_available; then
                ip6tables -t raw -A PORT0_GUARD -p "$proto" "$dir" 0 \
                    -j LOG --log-prefix "PORT0_DROP: " --log-level 4
                ip6tables -t raw -A PORT0_GUARD -p "$proto" "$dir" 0 -j DROP
            fi
        done
    done
    while iptables -t raw -C PREROUTING -j PORT0_GUARD 2>/dev/null; do
        iptables -t raw -D PREROUTING -j PORT0_GUARD
    done
    iptables -t raw -I PREROUTING 1 -j PORT0_GUARD
    if ipv6_firewall_available; then
        while ip6tables -t raw -C PREROUTING -j PORT0_GUARD 2>/dev/null; do
            ip6tables -t raw -D PREROUTING -j PORT0_GUARD
        done
        ip6tables -t raw -I PREROUTING 1 -j PORT0_GUARD
    fi

    # --- rsyslog：过滤 PORT0_DROP 写入独立文件 ---
    if [[ ! -f "$RSYSLOG_CONF" ]]; then
        cat > "$RSYSLOG_CONF" << 'EOF'
# 捕获 iptables PORT0_DROP 端口0恶意流量日志
:msg, contains, "PORT0_DROP:" /var/log/port0_traffic.log
& stop
EOF
        systemctl reload rsyslog 2>/dev/null || service rsyslog reload 2>/dev/null || true
        log "rsyslog 过滤配置已写入 $RSYSLOG_CONF"
    fi

    # --- 创建日志文件 ---
    if [[ ! -f "$PORT0_LOG" ]]; then
        touch "$PORT0_LOG"
        chmod 640 "$PORT0_LOG"
    fi

    # --- logrotate：每日轮转，保留7天 ---
    if [[ ! -f "$LOGROTATE_CONF" ]]; then
        cat > "$LOGROTATE_CONF" << 'EOF'
/var/log/port0_traffic.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    postrotate
        systemctl reload rsyslog > /dev/null 2>&1 || true
    endscript
}
EOF
        log "logrotate 配置已写入 $LOGROTATE_CONF"
    fi

    log "端口0流量日志配置完成，日志路径：$PORT0_LOG"
}

# 阻断端口0流量（安全加固，默认启用）
# 端口0在正常通信中不使用，出现即代表扫描/指纹探测/DoS攻击
# 改进：迁移至 raw 表，在连接跟踪之前拦截，避免被 filter 层 ACCEPT 规则绕过
setup_block_port_zero() {
    # raw 表 LOG+DROP 由 setup_port0_log() 统一处理（含幂等检测）
    setup_port0_log

    # filter 表保留兜底规则（防止 raw 表模块未加载的极端情况）
    local added=0
    local filter_chain
    for filter_chain in INPUT OUTPUT FORWARD; do
        for proto in tcp udp; do
            for dir in --dport --sport; do
                if ! iptables -C "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null; then
                    iptables -I "$filter_chain" 1 -p "$proto" "$dir" 0 -j DROP 2>/dev/null && ((added++)) || true
                fi
                if ipv6_firewall_available; then
                    if ! ip6tables -C "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null; then
                        ip6tables -I "$filter_chain" 1 -p "$proto" "$dir" 0 -j DROP 2>/dev/null || true
                    fi
                fi
            done
        done
    done

    log "端口0阻断规则配置完成（raw表LOG+DROP + filter表兜底，共添加 $added 条 filter IPv4 规则）"
}

# 清理端口0阻断规则（raw 表 + filter 表均清理）
cleanup_block_port_zero() {
    log "清理端口0阻断规则（raw表 + filter表）..."

    # 清理 raw 表 LOG 和 DROP 规则
    while iptables -t raw -C PREROUTING -j PORT0_GUARD 2>/dev/null; do
        iptables -t raw -D PREROUTING -j PORT0_GUARD 2>/dev/null || break
    done
    if iptables -t raw -nL PORT0_GUARD >/dev/null 2>&1; then
        iptables -t raw -F PORT0_GUARD 2>/dev/null || true
        iptables -t raw -X PORT0_GUARD 2>/dev/null || true
    fi
    for proto in tcp udp; do
        for dir in --dport --sport; do
            while iptables -t raw -C PREROUTING -p "$proto" "$dir" 0 \
                -j LOG --log-prefix "PORT0_DROP: " --log-level 4 2>/dev/null; do
                iptables -t raw -D PREROUTING -p "$proto" "$dir" 0 \
                    -j LOG --log-prefix "PORT0_DROP: " --log-level 4 2>/dev/null || break
            done
            while iptables -t raw -C PREROUTING -p "$proto" "$dir" 0 -j DROP 2>/dev/null; do
                iptables -t raw -D PREROUTING -p "$proto" "$dir" 0 -j DROP 2>/dev/null || break
            done
            while iptables -t raw -C OUTPUT -p "$proto" "$dir" 0 -j DROP 2>/dev/null; do
                iptables -t raw -D OUTPUT -p "$proto" "$dir" 0 -j DROP 2>/dev/null || break
            done
            if ipv6_firewall_available; then
                while ip6tables -t raw -C PREROUTING -j PORT0_GUARD 2>/dev/null; do
                    ip6tables -t raw -D PREROUTING -j PORT0_GUARD 2>/dev/null || break
                done
                if ip6tables -t raw -nL PORT0_GUARD >/dev/null 2>&1; then
                    ip6tables -t raw -F PORT0_GUARD 2>/dev/null || true
                    ip6tables -t raw -X PORT0_GUARD 2>/dev/null || true
                fi
                while ip6tables -t raw -C PREROUTING -p "$proto" "$dir" 0 \
                    -j LOG --log-prefix "PORT0_DROP: " --log-level 4 2>/dev/null; do
                    ip6tables -t raw -D PREROUTING -p "$proto" "$dir" 0 \
                        -j LOG --log-prefix "PORT0_DROP: " --log-level 4 2>/dev/null || break
                done
                while ip6tables -t raw -C PREROUTING -p "$proto" "$dir" 0 -j DROP 2>/dev/null; do
                    ip6tables -t raw -D PREROUTING -p "$proto" "$dir" 0 -j DROP 2>/dev/null || break
                done
            fi
        done
    done

    # 清理 filter 表兜底规则
    local filter_chain
    for filter_chain in INPUT OUTPUT FORWARD; do
        for proto in tcp udp; do
            for dir in --dport --sport; do
                while iptables -C "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null; do
                    iptables -D "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null || break
                done
                if ipv6_firewall_available; then
                    while ip6tables -C "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null; do
                        ip6tables -D "$filter_chain" -p "$proto" "$dir" 0 -j DROP 2>/dev/null || break
                    done
                fi
            done
        done
    done

    log "端口0阻断规则已清理（raw表 + filter表）"
}

# 配置iptables规则
setup_iptables() {
    log "开始配置iptables规则..."
    
    # 验证ipset存在
    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        log "错误：ipset $IPSET_NAME 不存在，无法配置iptables"
        return 1
    fi

    # 先清理旧规则（端口0规则在末尾重新配置，确保位置最高）
    cleanup_old_rules

    # ========== 最高优先级豁免：内网IP段 / 自定义白名单 / WireGuard / 游戏UDP ==========
    # 这些规则在 mangle PREROUTING 最前面插入，使豁免流量跳过所有标记规则，永远不受限制。

    # 0. 内网 IP 段无条件豁免（最高优先级，不受任何限制）
    # 覆盖：loopback、RFC1918 私有地址、wg+ 隧道接口
    # 无论是否启用 PROTECT_UDP_ALL，内网流量始终不受 china_ip 过滤
    log "配置内网IP段和内网网卡豁免规则（mangle PREROUTING）..."
    # loopback 接口
    if ! iptables -t mangle -C PREROUTING -i lo -j RETURN 2>/dev/null; then
        iptables -t mangle -I PREROUTING 1 -i lo -j RETURN
        log "已添加豁免：loopback 接口（-i lo）"
    fi
    # wg+ 隧道接口（WireGuard，无论 WIREGUARD_EXEMPT 开关是否打开，内网侧流量均豁免）
    if ! iptables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; then
        iptables -t mangle -I PREROUTING 1 -i wg+ -j RETURN
        log "已添加豁免：WireGuard 接口（-i wg+，无条件）"
    fi
    # RFC1918 私有 IP 段（192.168/16、172.16/12、10/8）
    for _subnet in "192.168.0.0/16" "172.16.0.0/12" "10.0.0.0/8"; do
        if ! iptables -t mangle -C PREROUTING -s "$_subnet" -j RETURN 2>/dev/null; then
            iptables -t mangle -I PREROUTING 1 -s "$_subnet" -j RETURN
            log "已添加豁免：内网IP段 $_subnet"
        fi
    done
    log "内网IP段豁免规则配置完成（内网流量不受 china_ip 限制）"

    # 1. 自定义白名单（custom_whitelist ipset）- 最高优先级，所有协议豁免
    setup_custom_whitelist_ipset
    if ipset list "$CUSTOM_IPSET_NAME" &>/dev/null; then
        if ! iptables -t mangle -C PREROUTING -m set --match-set "$CUSTOM_IPSET_NAME" src -j RETURN 2>/dev/null; then
            iptables -t mangle -I PREROUTING 1 -m set --match-set "$CUSTOM_IPSET_NAME" src -j RETURN
            log "已在 mangle PREROUTING 顶部插入自定义白名单豁免规则（custom_whitelist）"
        fi
    fi

    # 2. WireGuard 接口流量豁免（如果启用）
    if [[ "$WIREGUARD_EXEMPT" == true ]]; then
        # 豁免 WireGuard 接口入站流量（wg0, wg1, ... 统配 wg+）
        if ! iptables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; then
            iptables -t mangle -I PREROUTING 1 -i wg+ -j RETURN
            log "已在 mangle PREROUTING 顶部插入 WireGuard 接口豁免规则（-i wg+）"
        fi
        # 豁免 WireGuard UDP 端口（支持多端口，逗号分隔）
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ -z "$wgp" ]] && continue
            if ! iptables -t mangle -C PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null; then
                iptables -t mangle -I PREROUTING 1 -p udp --dport "$wgp" -j RETURN
                log "已在 mangle PREROUTING 顶部插入 WireGuard UDP/$wgp 豁免规则"
            fi
        done
        # INPUT 链也放行 WireGuard 端口（确保 wg 握手包通过）
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ -z "$wgp" ]] && continue
            if ! iptables -C INPUT -p udp --dport "$wgp" -j ACCEPT 2>/dev/null; then
                iptables -I INPUT 3 -p udp --dport "$wgp" -j ACCEPT 2>/dev/null || true
                log "已在 INPUT 链放行 WireGuard UDP/$wgp"
            fi
        done
        log "WireGuard 豁免规则配置完成（端口: $WIREGUARD_PORT）"
    fi

    # ========== 确保业务正常运行的基础规则 ==========
    # 检测是否已有inition.sh的规则（通过检查ddos_protect链）
    local has_inition_rules=false
    if iptables -L ddos_protect &>/dev/null 2>&1; then
        has_inition_rules=true
        log "检测到inition.sh规则，将保持兼容性配置"
    fi
    
    # ========== 重要：INPUT链策略设置逻辑 ==========
    # 只有在选择了保护端口时，才设置INPUT链为DROP
    # 如果没有选择保护端口，保持ACCEPT策略，确保未保护的端口可以正常通信
    if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]] || [[ "$PROTECT_ICMP" == true ]] || [[ "$PROTECT_UDP_ALL" == true ]]; then
        # 选择了保护端口或ICMP或UDP-all，需要设置 INPUT 链为 DROP 以确保限制生效
        local current_policy=$(iptables -L INPUT -n 2>/dev/null | head -1 | grep -o "policy [A-Z]*" | awk '{print $2}')
        if [[ "$current_policy" != "DROP" ]]; then
            log "检测到已选择保护端口或ICMP，设置INPUT链默认策略为DROP，确保标记的流量被正确拦截"
            iptables -P INPUT DROP
        else
            log "INPUT链策略已是DROP，保持"
        fi
    else
        # 没有选择保护任何端口或ICMP，保持INPUT链为ACCEPT，确保未保护的端口可以正常通信
        local current_policy=$(iptables -L INPUT -n 2>/dev/null | head -1 | grep -o "policy [A-Z]*" | awk '{print $2}')
        if [[ "$current_policy" != "ACCEPT" ]]; then
            log "未选择保护任何端口或ICMP，设置INPUT链默认策略为ACCEPT，确保未保护的端口可以正常通信"
            iptables -P INPUT ACCEPT
        else
            log "INPUT链策略已是ACCEPT，保持（未保护的端口可以正常通信）"
        fi
    fi
    
    # 1. 确保 OUTPUT 链完全开放（代理、DNS等出站流量不受限制）
    if ! iptables -C OUTPUT -j ACCEPT 2>/dev/null; then
        iptables -A OUTPUT -j ACCEPT 2>/dev/null || true
        log "已确保 OUTPUT 链完全开放（允许所有出站流量）"
    fi
    
    # 2. 确保 FORWARD 链完全开放（PVE、Docker转发不受限制）
    if ! iptables -C FORWARD -j ACCEPT 2>/dev/null; then
        iptables -A FORWARD -j ACCEPT 2>/dev/null || true
        log "已确保 FORWARD 链完全开放（允许所有转发流量）"
    fi
    
    # 3. 允许本地回环接口（localhost）完全开放
    # 如果已有inition.sh规则，检查是否已存在lo规则，避免重复
    if ! iptables -C INPUT -i lo -j ACCEPT 2>/dev/null; then
        # 如果有inition.sh规则，使用-A追加（保持顺序）
        # 否则使用-I插入到最前面
        if [[ "$has_inition_rules" == true ]]; then
            iptables -A INPUT -i lo -j ACCEPT 2>/dev/null || true
            log "已添加规则：允许本地回环接口（localhost）- 兼容inition.sh"
        else
            iptables -I INPUT 1 -i lo -j ACCEPT
            log "已添加规则：允许本地回环接口（localhost）"
        fi
    fi
    
    # 4. 重要：允许已建立的连接通过，确保服务器可以正常访问海外服务器
    # 如果已有inition.sh规则，检查是否已存在ESTABLISHED规则
    if ! iptables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null && \
       ! iptables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
        if [[ "$has_inition_rules" == true ]]; then
            # 如果有inition.sh规则，使用-A追加（保持顺序）
            if iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                log "已添加规则：允许已建立的连接通过（兼容inition.sh，使用state模块）"
            fi
        else
            # 否则使用-I插入
            if iptables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; then
                log "已添加规则：允许已建立的连接通过（确保服务器可访问海外服务器）"
            else
                log "警告：添加连接跟踪规则失败，尝试使用状态模块"
                if iptables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT; then
                    log "已添加规则：允许已建立的连接通过（使用state模块）"
                else
                    log "错误：无法添加连接跟踪规则"
                    return 1
                fi
            fi
        fi
    else
        log "已存在ESTABLISHED规则，跳过添加（可能是inition.sh添加的）"
    fi
    
    # 配置端口保护规则
    # 双重防护：mangle MARK（标记后由 INPUT 的 mark DROP 规则拦截）+
    #           INPUT 链直接 DROP（兜底，防止 mangle 规则因 IPv6 socket 等原因未命中）
    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        local dest_ip="${PROTECTED_PORTS_IP[$port_proto]:-}"

        # --- mangle MARK 规则（原有逻辑）---
        local rule_args=(-p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK")
        [[ -n "$dest_ip" ]] && rule_args=(-d "$dest_ip" "${rule_args[@]}")
        if ! iptables -t mangle -C PREROUTING "${rule_args[@]}" 2>/dev/null; then
            if iptables -t mangle -A PREROUTING "${rule_args[@]}"; then
                log "已添加mangle标记规则：标记非中国IP访问 $proto/$port${dest_ip:+（→$dest_ip）}"
            else
                log "错误：添加mangle标记规则失败 $proto/$port"
                return 1
            fi
        else
            log "标记规则已存在：$proto/$port${dest_ip:+（→$dest_ip）}"
        fi

        # --- INPUT 链直接 DROP 兜底规则 ---
        # 防止程序以 IPv6 socket（:::port）监听时 mangle IPv4 规则未命中的穿透问题
        local drop_args=(-p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME" src -j DROP)
        [[ -n "$dest_ip" ]] && drop_args=(-d "$dest_ip" "${drop_args[@]}")
        if ! iptables -C INPUT "${drop_args[@]}" 2>/dev/null; then
            if iptables -A INPUT "${drop_args[@]}"; then
                log "已添加INPUT直接DROP兜底规则：非中国IP访问 $proto/$port${dest_ip:+（→$dest_ip）}"
            else
                log "警告：添加INPUT直接DROP兜底规则失败 $proto/$port"
            fi
        else
            log "INPUT直接DROP兜底规则已存在：$proto/$port"
        fi
    done

    if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]]; then
        # 计算插入位置：在lo和ESTABLISHED规则之后
        local insert_pos=3
        local base_rule_count=0
        
        # 计算基础规则的数量（lo、ESTABLISHED等）
        while IFS= read -r rule; do
            [[ "$rule" =~ ^-P ]] && continue
            [[ "$rule" =~ ^# ]] && continue
            [[ "$rule" =~ ^-N ]] && continue
            
            # 检查是否是基础规则
            if [[ "$rule" =~ -i\ lo ]] || \
               [[ "$rule" =~ state\ (ESTABLISHED|RELATED) ]] || \
               [[ "$rule" =~ conntrack.*ESTABLISHED ]]; then
                ((base_rule_count++))
            fi
        done < <(iptables -S INPUT 2>/dev/null)
        
        # 插入位置 = 基础规则数量 + 1
        if [[ $base_rule_count -gt 0 ]]; then
            insert_pos=$((base_rule_count + 1))
        fi
        
        # ========== 重要：先添加允许中国IP访问受保护端口的规则 ==========
        # 必须在标记DROP规则之前，确保中国IP流量在标记前就被ACCEPT
        # 同时，需要确保删除所有可能存在的简单ACCEPT规则（inition.sh添加的）
        log "添加允许中国IP访问受保护端口的规则..."
        local accept_insert_pos=$insert_pos
        for port_proto in "${PROTECTED_PORTS[@]}"; do
            local port="${port_proto%/*}"
            local proto="${port_proto#*/}"
            local dest_ip="${PROTECTED_PORTS_IP[$port_proto]:-}"
            
            # 再次确保删除该端口的简单ACCEPT规则（inition.sh可能添加的）
            # 使用更精确的方法：删除所有不包含ipset匹配的ACCEPT规则
            while true; do
                # 查找该端口的ACCEPT规则（不包含ipset匹配的）
                local rule_num=$(iptables -L INPUT -n --line-numbers 2>/dev/null | \
                    awk -v proto="$proto" -v port="$port" \
                    '$0 ~ proto && $0 ~ "dpt:" port && $0 ~ "ACCEPT" && $0 !~ "match-set" && $0 !~ "f2b-" && $0 !~ "fail2ban-" {print $1; exit}')
                
                if [[ -n "$rule_num" ]]; then
                    # 检查这条规则是否真的是简单ACCEPT（不是fail2ban或china_ip规则）
                    local full_rule=$(iptables -L INPUT -n --line-numbers 2>/dev/null | awk "NR==$rule_num")
                    if echo "$full_rule" | grep -qE "f2b-|fail2ban-|match-set"; then
                        log "跳过fail2ban或china_ip规则：$proto/$port"
                        break
                    fi
                    # 删除这条规则
                    if iptables -D INPUT "$rule_num" 2>/dev/null; then
                        log "已删除简单ACCEPT规则：$proto/$port (位置: $rule_num)"
                    else
                        break
                    fi
                else
                    break
                fi
            done
            
            # 构造包含目标IP（可选）的规则参数
            local accept_args=(-p "$proto" --dport "$port" -m set --match-set "$IPSET_NAME" src -j ACCEPT)
            [[ -n "$dest_ip" ]] && accept_args=(-d "$dest_ip" "${accept_args[@]}")

            # 检查是否已存在允许中国IP的规则
            if ! iptables -C INPUT "${accept_args[@]}" 2>/dev/null; then
                # 在基础规则之后、标记DROP规则之前添加允许规则
                if iptables -I INPUT "$accept_insert_pos" "${accept_args[@]}"; then
                    log "已添加规则：允许中国IP访问 $proto/$port${dest_ip:+（→$dest_ip）}（位置：$accept_insert_pos）"
                    ((accept_insert_pos++))  # 下一个规则插入位置递增
                else
                    log "警告：添加允许中国IP访问 $proto/$port 的规则失败"
                fi
            else
                log "允许中国IP访问 $proto/$port 的规则已存在"
            fi
        done
        
        # DROP 规则：在允许中国IP规则之后，确保标记的流量被正确拦截
        local mark_drop_pos=$accept_insert_pos
        if ! iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            if iptables -I INPUT "$mark_drop_pos" -m mark --mark "$BLOCK_MARK" -j DROP; then
                log "已添加通用规则：DROP 所有标记流量 (INPUT) - 插入到位置 $mark_drop_pos（允许中国IP规则之后）"
            else
                log "错误：添加 INPUT 标记规则失败"
                return 1
            fi
        else
            log "标记DROP规则已存在"
        fi
        
        # ========== 兜底 ACCEPT：始终追加到末尾 ==========
        # 让未受保护的端口（inition.sh 开放的端口等）可以正常通信。
        # 必须用 -A 追加到末尾，不能用 -I 插入到中间，否则会短路后面的
        # syn_protect / ddos_protect / ICMP 限制等规则。
        # 先精确删除裸 ACCEPT all（仅 -A INPUT -j ACCEPT，不误删有条件的 ACCEPT 规则）
        local _bare
        _bare=$(iptables-save 2>/dev/null | grep -cxF -- '-A INPUT -j ACCEPT' || true)
        for ((_i=0; _i<_bare; _i++)); do
            iptables -D INPUT -j ACCEPT 2>/dev/null || break
        done
        if iptables -A INPUT -j ACCEPT; then
            log "已追加兜底 ACCEPT 规则到 INPUT 链末尾（未保护端口可正常通信）"
        else
            log "警告：添加兜底 ACCEPT 规则失败，未保护的端口可能无法访问"
        fi

        if ensure_docker_chain; then
            # 在 DOCKER-USER 链中，先允许已建立的连接通过（确保Docker容器通信正常）
            if ! iptables -C DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                if iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                    log "已在DOCKER-USER链允许已建立的连接通过（确保Docker容器通信）"
                else
                    # 如果 conntrack 不可用，尝试使用 state 模块
                    if ! iptables -C DOCKER-USER -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                        iptables -I DOCKER-USER 1 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
                    fi
                fi
            fi
            
            # 然后添加标记阻断规则（位置2，在ESTABLISHED规则之后）
            if ! iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                if iptables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP; then
                    log "已在DOCKER-USER链阻断所有标记流量"
                else
                    log "警告：无法在DOCKER-USER链添加标记阻断规则"
                fi
            fi
        fi
    fi
    
    # 配置 UDP-all 限制（如果启用）
    if [[ "$PROTECT_UDP_ALL" == true ]]; then
        log "开始配置 UDP 全量限制规则..."

        # mangle PREROUTING：标记所有非中国IP的UDP流量
        if ! iptables -t mangle -C PREROUTING -p udp -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
            if iptables -t mangle -A PREROUTING -p udp -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK"; then
                log "已添加规则：标记非中国IP的所有UDP (mangle)"
            else
                log "错误：添加 UDP-all mangle 规则失败"
                return 1
            fi
        else
            log "UDP-all mangle 规则已存在"
        fi

        # INPUT：允许中国IP的UDP（必须在 DROP mark 规则之前）
        if ! iptables -C INPUT -p udp -m set --match-set "$IPSET_NAME" src -j ACCEPT 2>/dev/null; then
            # 插入到最前面（lo 和 ESTABLISHED 之后）
            local udp_accept_pos=3
            local br=0
            while IFS= read -r rule; do
                [[ "$rule" =~ ^-P ]] && continue
                [[ "$rule" =~ ^-N ]] && continue
                if [[ "$rule" =~ -i\ lo ]] || [[ "$rule" =~ state\ (ESTABLISHED|RELATED) ]] || [[ "$rule" =~ conntrack.*ESTABLISHED ]]; then
                    ((br++))
                fi
            done < <(iptables -S INPUT 2>/dev/null)
            [[ $br -gt 0 ]] && udp_accept_pos=$((br + 1))

            if iptables -I INPUT "$udp_accept_pos" -p udp -m set --match-set "$IPSET_NAME" src -j ACCEPT; then
                log "已添加规则：允许中国IP UDP 访问（位置：$udp_accept_pos）"
            else
                log "错误：添加 UDP-all ACCEPT 规则失败"
                return 1
            fi
        else
            log "UDP-all ACCEPT 规则已存在"
        fi

        # 确保 mark DROP 规则存在
        if ! iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            local drop_pos
            drop_pos=$(iptables -L INPUT -n --line-numbers 2>/dev/null | awk '/match-set.*china_ip.*ACCEPT/ {last=$1} END{print last+0+1}')
            [[ -z "$drop_pos" || "$drop_pos" -le 0 ]] && drop_pos=4
            if iptables -I INPUT "$drop_pos" -m mark --mark "$BLOCK_MARK" -j DROP; then
                log "已添加规则：DROP 标记流量（UDP-all模式，位置：$drop_pos）"
            fi
        fi

        # 确保兜底 ACCEPT 存在（追加到末尾，精确清理旧的裸 ACCEPT all）
        local _bare2
        _bare2=$(iptables-save 2>/dev/null | grep -cxF -- '-A INPUT -j ACCEPT' || true)
        for ((_i=0; _i<_bare2; _i++)); do
            iptables -D INPUT -j ACCEPT 2>/dev/null || break
        done
        iptables -A INPUT -j ACCEPT
        log "已追加兜底 ACCEPT 规则到 INPUT 链末尾（UDP-all 模式）"

        if ensure_docker_chain; then
            if ! iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                iptables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || true
                log "已在 DOCKER-USER 链添加 UDP-all 限制"
            fi
        fi

        log "UDP 全量限制规则配置完成"
    fi

    # 配置ICMP限制（如果启用）
    if [[ "$PROTECT_ICMP" == true ]]; then
        # 检查是否已有ICMP DROP规则（可能是inition.sh添加的）
        local has_icmp_drop=false
        if iptables -C INPUT -p icmp --icmp-type echo-request -j DROP 2>/dev/null; then
            has_icmp_drop=true
            log "检测到已有ICMP DROP规则（可能是inition.sh添加的），将替换为中国IP限制规则"
            # 删除原有的ICMP DROP规则
            iptables -D INPUT -p icmp --icmp-type echo-request -j DROP 2>/dev/null || true
        fi
        
        # 无条件 ACCEPT echo-request 来自 inition.sh，会短路此处的中国IP限制，必须移除。
        while iptables -C INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; do
            iptables -D INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null || break
        done

        # 允许本地 ping。端口0 filter 兜底规则占用前四条，因此固定插入位置5。
        if ! iptables -C INPUT -s 127.0.0.0/8 -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; then
            if ! iptables -I INPUT 5 -s 127.0.0.0/8 -p icmp --icmp-type echo-request -j ACCEPT; then
                log "错误：添加本地ICMP规则失败"
                return 1
            fi
            log "已添加规则：ACCEPT 本地 ICMP"
        fi
        
        # 限制非中国IP的 ping。插入在唯一的兜底 ACCEPT all 之前，避免被短路。
        if ! iptables -C INPUT -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
            if ! iptables -I INPUT 6 -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP; then
                log "错误：添加ICMP限制规则失败"
                return 1
            fi
            log "已添加规则：DROP 非中国IP ICMP ping"
        fi

        if ensure_docker_chain; then
            # 在 DOCKER-USER 链中，ICMP 限制放在 ESTABLISHED 规则之后
            if ! iptables -C DOCKER-USER -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
                if iptables -I DOCKER-USER 2 -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP; then
                    log "已在DOCKER-USER链限制 ICMP"
                fi
            fi
        fi
    else
        # 即使不启用ICMP限制，也检查是否有inition.sh的ICMP DROP规则
        # 如果有，保留它（inition.sh永久禁用ICMP）
        if iptables -C INPUT -p icmp --icmp-type echo-request -j DROP 2>/dev/null; then
            log "检测到inition.sh的ICMP DROP规则，已保留（永久禁用ICMP）"
        fi
    fi
    
    # ========== 如果没有选择保护任何端口，确保未保护的端口可以正常通信 ==========
    if [[ ${#PROTECTED_PORTS[@]} -eq 0 ]] && [[ "$PROTECT_ICMP" != true ]] && [[ "$PROTECT_UDP_ALL" != true ]]; then
        local current_policy=$(iptables -L INPUT -n 2>/dev/null | head -1 | grep -o "policy [A-Z]*" | awk '{print $2}')
        if [[ "$current_policy" == "DROP" ]]; then
            local _bare3
            _bare3=$(iptables-save 2>/dev/null | grep -cxF -- '-A INPUT -j ACCEPT' || true)
            for ((_i=0; _i<_bare3; _i++)); do
                iptables -D INPUT -j ACCEPT 2>/dev/null || break
            done
            iptables -A INPUT -j ACCEPT
            log "已追加兜底 ACCEPT 规则到 INPUT 链末尾（无保护端口模式）"
        else
            log "INPUT链策略为ACCEPT，未保护的端口可以正常通信"
        fi
    fi
    
    log "iptables规则配置完成"

    # ========== 端口0阻断（放在所有规则之后，确保 raw 表规则在最终状态下生效）==========
    setup_block_port_zero

    # ========== 恢复 ddos_protect / syn_protect 链挂载 ==========
    # china_ip_firewall.sh 的 cleanup_old_rules 会删除这两条链的挂载规则，
    # 此处在所有规则（包括兜底 ACCEPT）配置完成后追加，
    # 追加到末尾确保在兜底 ACCEPT 之后，流量按顺序经过防护链。
    # 注意：inition.sh 已在链头部挂载了 syn_protect，此处只补充 ddos_protect 缺失的挂载。
    if iptables -L syn_protect &>/dev/null 2>&1 && iptables -L ddos_protect &>/dev/null 2>&1; then
        local _ssh_port
        _ssh_port=$(ss -tlnp 2>/dev/null | grep sshd | grep -oE '[0-9]+$' | head -1)
        _ssh_port="${_ssh_port:-22}"
        # syn_protect：如果不存在则追加（inition.sh 通常已在头部加了，这里补漏）
        if ! iptables -C INPUT -p tcp --dport "$_ssh_port" -m state --state NEW -j syn_protect 2>/dev/null; then
            iptables -A INPUT -p tcp --dport "$_ssh_port" -m state --state NEW -j syn_protect 2>/dev/null || true
            log "已追加 syn_protect 链挂载（SSH 端口：$_ssh_port）"
        fi
        # ddos_protect：如果不存在则追加
        if ! iptables -C INPUT -p tcp --dport "$_ssh_port" -m state --state NEW -j ddos_protect 2>/dev/null; then
            iptables -A INPUT -p tcp --dport "$_ssh_port" -m state --state NEW -j ddos_protect 2>/dev/null || true
            log "已追加 ddos_protect 链挂载（SSH 端口：$_ssh_port）"
        fi
        log "syn_protect / ddos_protect 链挂载检查完成（SSH 端口：$_ssh_port）"
    else
        log "未检测到 syn_protect / ddos_protect 链，跳过恢复（仅 inition.sh 创建这两条链）"
    fi

    return 0
}

# 配置IPv6 iptables规则
setup_iptables_v6() {
    if ! ipv6_firewall_available; then
        return 0
    fi
    
    if ! ipset list "$IPSET_NAME_V6" &>/dev/null; then
        log "警告：IPv6 ipset 不存在，跳过IPv6防火墙配置"
        return 0
    fi
    
    log "开始配置IPv6 iptables规则..."
    cleanup_old_rules_v6
    ensure_docker_chain_v6

    # ========== 最高优先级豁免：内网IP段 / 自定义白名单 / WireGuard / 游戏UDP（IPv6）==========

    # 0. IPv6 内网地址无条件豁免（ULA fc00::/7、loopback ::1、wg+ 接口）
    log "配置 IPv6 内网地址豁免规则（mangle PREROUTING）..."
    if ! ip6tables -t mangle -C PREROUTING -i lo -j RETURN 2>/dev/null; then
        ip6tables -t mangle -I PREROUTING 1 -i lo -j RETURN
        log "已添加 IPv6 豁免：loopback 接口（-i lo）"
    fi
    if ! ip6tables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; then
        ip6tables -t mangle -I PREROUTING 1 -i wg+ -j RETURN
        log "已添加 IPv6 豁免：WireGuard 接口（-i wg+，无条件）"
    fi
    for _v6subnet in "fc00::/7" "::1/128"; do
        if ! ip6tables -t mangle -C PREROUTING -s "$_v6subnet" -j RETURN 2>/dev/null; then
            ip6tables -t mangle -I PREROUTING 1 -s "$_v6subnet" -j RETURN
            log "已添加 IPv6 豁免：内网地址段 $_v6subnet"
        fi
    done
    log "IPv6 内网地址豁免规则配置完成"

    # 1. 自定义白名单（custom_whitelist_v6 ipset）
    local v6_custom="${CUSTOM_IPSET_NAME}_v6"
    if ipset list "$v6_custom" &>/dev/null; then
        if ! ip6tables -t mangle -C PREROUTING -m set --match-set "$v6_custom" src -j RETURN 2>/dev/null; then
            ip6tables -t mangle -I PREROUTING 1 -m set --match-set "$v6_custom" src -j RETURN
            log "已在 IPv6 mangle PREROUTING 顶部插入自定义白名单豁免规则"
        fi
    fi

    # 2. WireGuard 接口流量豁免（IPv6）
    if [[ "$WIREGUARD_EXEMPT" == true ]]; then
        if ! ip6tables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; then
            ip6tables -t mangle -I PREROUTING 1 -i wg+ -j RETURN
            log "已在 IPv6 mangle PREROUTING 顶部插入 WireGuard 接口豁免规则"
        fi
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ -z "$wgp" ]] && continue
            if ! ip6tables -t mangle -C PREROUTING -p udp --dport "$wgp" -j RETURN 2>/dev/null; then
                ip6tables -t mangle -I PREROUTING 1 -p udp --dport "$wgp" -j RETURN
                log "已在 IPv6 mangle PREROUTING 顶部插入 WireGuard UDP/$wgp 豁免规则"
            fi
        done
    fi

    # ========== 确保业务正常运行的基础规则（IPv6）==========
    # 1. 确保 OUTPUT 链完全开放（代理、DNS等出站流量不受限制）
    if ! ip6tables -C OUTPUT -j ACCEPT 2>/dev/null; then
        ip6tables -A OUTPUT -j ACCEPT 2>/dev/null || true
        log "已确保 IPv6 OUTPUT 链完全开放（允许所有出站流量）"
    fi
    
    # 2. 确保 FORWARD 链完全开放（PVE、Docker转发不受限制）
    if ! ip6tables -C FORWARD -j ACCEPT 2>/dev/null; then
        ip6tables -A FORWARD -j ACCEPT 2>/dev/null || true
        log "已确保 IPv6 FORWARD 链完全开放（允许所有转发流量）"
    fi
    
    # 3. 允许本地回环接口（localhost）完全开放
    if ! ip6tables -C INPUT -i lo -j ACCEPT 2>/dev/null; then
        ip6tables -I INPUT 1 -i lo -j ACCEPT
        log "已添加IPv6规则：允许本地回环接口（localhost）"
    fi
    
    # 4. 重要：允许已建立的连接通过，确保服务器可以正常访问海外服务器
    if ! ip6tables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
        if ip6tables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; then
            log "已添加IPv6规则：允许已建立的连接通过（确保服务器可访问海外服务器）"
        else
            log "警告：添加IPv6连接跟踪规则失败，尝试使用状态模块"
            # 如果 conntrack 不可用，尝试使用 state 模块（旧版内核）
            if ! ip6tables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                if ip6tables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT; then
                    log "已添加IPv6规则：允许已建立的连接通过（使用state模块）"
                else
                    log "警告：无法添加IPv6连接跟踪规则"
                fi
            fi
        fi
    fi
    
    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        # IPv6 暂不支持目标IP绑定（IPv6地址格式复杂，跳过）
        local rule_args=(-p "$proto" --dport "$port" -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK")

        if ip6tables -t mangle -C PREROUTING "${rule_args[@]}" 2>/dev/null; then
            log "IPv6标记规则已存在：$proto/$port"
            continue
        fi

        if ip6tables -t mangle -A PREROUTING "${rule_args[@]}"; then
            log "已添加IPv6 mangle标记规则：标记非中国IP访问 $proto/$port"
        else
            log "警告：添加IPv6 mangle标记规则失败 $proto/$port"
        fi
    done

    if [[ ${#PROTECTED_PORTS[@]} -gt 0 ]]; then
        # ========== 重要：先添加允许中国IP访问受保护端口的规则（IPv6）==========
        # 必须在标记DROP规则之前，确保中国IP流量在标记前就被ACCEPT
        log "添加允许中国IPv6访问受保护端口的规则..."
        local accept_insert_pos=3  # 在lo和ESTABLISHED之后
        for port_proto in "${PROTECTED_PORTS[@]}"; do
            local port="${port_proto%/*}"
            local proto="${port_proto#*/}"
            
            # 检查是否已存在允许中国IPv6的规则
            if ! ip6tables -C INPUT -p "$proto" --dport "$port" -m set --match-set "$IPSET_NAME_V6" src -j ACCEPT 2>/dev/null; then
                # 在基础规则之后、标记DROP规则之前添加允许规则
                if ip6tables -I INPUT "$accept_insert_pos" -p "$proto" --dport "$port" -m set --match-set "$IPSET_NAME_V6" src -j ACCEPT; then
                    log "已添加IPv6规则：允许中国IP访问 $proto/$port（位置：$accept_insert_pos）"
                    ((accept_insert_pos++))  # 下一个规则插入位置递增
                else
                    log "警告：添加允许中国IPv6访问 $proto/$port 的规则失败"
                fi
            else
                log "允许中国IPv6访问 $proto/$port 的规则已存在"
            fi
        done
        
        # DROP 规则放在允许中国IP规则之后
        local mark_drop_pos=$accept_insert_pos
        if ! ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            if ip6tables -I INPUT "$mark_drop_pos" -m mark --mark "$BLOCK_MARK" -j DROP; then
                log "已添加IPv6通用规则：DROP 所有标记流量 (INPUT) - 位置：$mark_drop_pos（允许中国IP规则之后）"
            else
                log "警告：添加 IPv6 INPUT 标记规则失败"
            fi
        fi

        if ensure_docker_chain_v6; then
            # 在 DOCKER-USER 链中，先允许已建立的连接通过（确保Docker容器通信正常）
            if ! ip6tables -C DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                if ip6tables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                    log "已在IPv6 DOCKER-USER链允许已建立的连接通过（确保Docker容器通信）"
                else
                    # 如果 conntrack 不可用，尝试使用 state 模块
                    if ! ip6tables -C DOCKER-USER -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
                        ip6tables -I DOCKER-USER 1 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
                    fi
                fi
            fi
            
            # 然后添加标记阻断规则（位置2，在ESTABLISHED规则之后）
            if ! ip6tables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                if ip6tables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP; then
                    log "已在IPv6 DOCKER-USER链阻断所有标记流量"
                fi
            fi
        fi
    fi
    
    if [[ "$PROTECT_ICMP" == true ]]; then
        # 允许本地ping（插入到 DROP 规则之后，位置4）
        if ip6tables -I INPUT 4 -s ::1 -p icmpv6 --icmpv6-type echo-request -j ACCEPT; then
            log "已添加IPv6规则：ACCEPT 本地 ICMPv6"
        fi
        
        # 限制非中国IP的ping（插入到本地ICMP规则之后，位置5）
        if ip6tables -I INPUT 5 -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP; then
            log "已添加IPv6规则：DROP 非中国IP ICMPv6 ping"
        fi
        
        if ensure_docker_chain_v6; then
            # 在 DOCKER-USER 链中，ICMP 限制放在 ESTABLISHED 规则之后
            if ! ip6tables -C DOCKER-USER -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP 2>/dev/null; then
                if ip6tables -I DOCKER-USER 2 -p icmpv6 --icmpv6-type echo-request -m set ! --match-set "$IPSET_NAME_V6" src -j DROP; then
                    log "已在IPv6 DOCKER-USER链限制 ICMPv6"
                fi
            fi
        fi
    fi
    
    # 配置 IPv6 UDP-all 限制（如果启用）
    if [[ "$PROTECT_UDP_ALL" == true ]]; then
        log "开始配置 IPv6 UDP 全量限制规则..."
        if ! ip6tables -t mangle -C PREROUTING -p udp -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
            if ip6tables -t mangle -A PREROUTING -p udp -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK"; then
                log "已添加 IPv6 UDP-all mangle 规则"
            fi
        fi
        if ! ip6tables -C INPUT -p udp -m set --match-set "$IPSET_NAME_V6" src -j ACCEPT 2>/dev/null; then
            if ip6tables -I INPUT 3 -p udp -m set --match-set "$IPSET_NAME_V6" src -j ACCEPT; then
                log "已添加 IPv6 UDP-all ACCEPT 规则"
            fi
        fi
        if ! ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            ip6tables -I INPUT 4 -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || true
        fi
        if ensure_docker_chain_v6; then
            if ! ip6tables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
                ip6tables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || true
            fi
        fi
        log "IPv6 UDP 全量限制规则配置完成"
    fi

    log "IPv6 iptables规则配置完成"
    return 0
}

# 反选模式 iptables 配置
# 逻辑：对豁免端口 RETURN（不标记），对其余所有非China流量打标记并DROP
# 效果：豁免端口对所有IP开放，其余端口（含未配置端口）仅China可访
setup_iptables_reverse() {
    log "开始配置反选模式 iptables 规则（豁免端口: ${EXEMPT_PORT}/${EXEMPT_PROTO}）..."

    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        log "错误：ipset $IPSET_NAME 不存在，无法配置 iptables"
        return 1
    fi

    # ---- 清理反选模式可能遗留的旧规则 ----
    # 清理 mangle PREROUTING 豁免规则
    while iptables -t mangle -C PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    while iptables -t mangle -C PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    # 清理全局标记规则（无协议/端口过滤的通用版本）
    while iptables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; do
        iptables -t mangle -D PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null || break
    done
    # 清理 INPUT mark DROP 规则和兜底 ACCEPT 规则
    while iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        iptables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done

    # 清理旧版或上一次配置遗留的端口级 mangle 规则。
    # setup_iptables 会根据本次反选结果重新添加当前需要的端口规则。
    while IFS= read -r old_rule; do
        [[ -z "$old_rule" ]] && continue
        delete_rule="${old_rule/-A /-D }"
        iptables -t mangle $delete_rule 2>/dev/null || true
    done < <(iptables -t mangle -S PREROUTING 2>/dev/null | \
        grep -- "--match-set $IPSET_NAME" | grep -- "MARK --set-mark $BLOCK_MARK" || true)

    # ---- 基础规则 ----
    # 1. 本地回环完全开放
    if ! iptables -C INPUT -i lo -j ACCEPT 2>/dev/null; then
        iptables -I INPUT 1 -i lo -j ACCEPT
        log "已添加规则：允许本地回环接口"
    fi
    # 2. 已建立/关联的连接放行（保证 SSH 等现有连接不断）
    if ! iptables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null && \
       ! iptables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
        if iptables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
            log "已添加规则：允许已建立的连接（conntrack）"
        elif iptables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
            log "已添加规则：允许已建立的连接（state）"
        else
            log "警告：无法添加连接跟踪规则"
        fi
    fi
    # 3. OUTPUT / FORWARD 完全开放
    if ! iptables -C OUTPUT -j ACCEPT 2>/dev/null; then
        iptables -A OUTPUT -j ACCEPT 2>/dev/null || true
    fi
    if ! iptables -C FORWARD -j ACCEPT 2>/dev/null; then
        iptables -A FORWARD -j ACCEPT 2>/dev/null || true
    fi

    # ---- mangle PREROUTING（顺序关键：先豁免，再全局标记）----
    # 规则1：豁免端口 RETURN，跳过后续标记，使其流量永远不会被 DROP
    if ! iptables -t mangle -C PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; then
        iptables -t mangle -I PREROUTING 1 -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN
        log "已添加 mangle 豁免规则：${EXEMPT_PROTO}/${EXEMPT_PORT} RETURN（不标记）"
    fi

    # 规则2：全局标记所有非China IP 的流量（放在豁免规则之后）
    if ! iptables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
        iptables -t mangle -A PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK"
        log "已添加全局 mangle 标记规则：所有非中国IP流量打标记 ${BLOCK_MARK}"
    fi

    # ---- INPUT 链 ----
    # 在 lo/ESTABLISHED 规则之后插入 mark DROP 规则
    local insert_pos=3
    local base_count=0
    while IFS= read -r rule; do
        [[ "$rule" =~ ^-P ]] && continue
        [[ "$rule" =~ ^-N ]] && continue
        if [[ "$rule" =~ -i\ lo ]] || \
           [[ "$rule" =~ state.*ESTABLISHED ]] || \
           [[ "$rule" =~ conntrack.*ESTABLISHED ]]; then
            ((base_count++))
        fi
    done < <(iptables -S INPUT 2>/dev/null)
    [[ $base_count -gt 0 ]] && insert_pos=$((base_count + 1))

    if ! iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
        iptables -I INPUT "$insert_pos" -m mark --mark "$BLOCK_MARK" -j DROP
        log "已添加 INPUT 规则：DROP 所有标记流量（位置 ${insert_pos}）"
    fi

    # 兜底 ACCEPT（China IP 和豁免端口流量均不携带标记，会到达此规则被放行）
    if ! iptables -C INPUT -j ACCEPT 2>/dev/null; then
        iptables -A INPUT -j ACCEPT
        log "已添加兜底 ACCEPT 规则（China IP 及豁免端口流量放行）"
    fi

    # ---- DOCKER-USER 链同步 ----
    if ensure_docker_chain; then
        if ! iptables -C DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
            iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
            iptables -I DOCKER-USER 1 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
        fi
        if ! iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            iptables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || true
            log "已在 DOCKER-USER 链阻断标记流量"
        fi
    fi

    log "反选模式 iptables 规则配置完成"
    return 0
}

# 反选模式 IPv6 iptables 配置
setup_iptables_v6_reverse() {
    if ! ipv6_firewall_available; then
        return 0
    fi
    if ! ipset list "$IPSET_NAME_V6" &>/dev/null; then
        log "警告：IPv6 ipset 不存在，跳过 IPv6 反选模式配置"
        return 0
    fi

    log "开始配置反选模式 IPv6 iptables 规则（豁免端口: ${EXEMPT_PORT}/${EXEMPT_PROTO}）..."
    cleanup_old_rules_v6
    ensure_docker_chain_v6

    # 基础规则
    if ! ip6tables -C INPUT -i lo -j ACCEPT 2>/dev/null; then
        ip6tables -I INPUT 1 -i lo -j ACCEPT
    fi
    if ! ip6tables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null && \
       ! ip6tables -C INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
        ip6tables -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
        ip6tables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
    fi
    if ! ip6tables -C OUTPUT -j ACCEPT 2>/dev/null; then
        ip6tables -A OUTPUT -j ACCEPT 2>/dev/null || true
    fi
    if ! ip6tables -C FORWARD -j ACCEPT 2>/dev/null; then
        ip6tables -A FORWARD -j ACCEPT 2>/dev/null || true
    fi

    # mangle：先豁免端口 RETURN，再全局标记非China IPv6 流量
    while ip6tables -t mangle -C PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    if ! ip6tables -t mangle -C PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; then
        ip6tables -t mangle -I PREROUTING 1 -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN
        log "已添加 IPv6 mangle 豁免规则：${EXEMPT_PROTO}/${EXEMPT_PORT}"
    fi

    while ip6tables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null || break
    done
    if ! ip6tables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
        ip6tables -t mangle -A PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK"
        log "已添加全局 IPv6 mangle 标记规则：所有非中国IP流量打标记"
    fi

    # INPUT：mark DROP + 兜底 ACCEPT
    if ! ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
        ip6tables -I INPUT 3 -m mark --mark "$BLOCK_MARK" -j DROP
        log "已添加 IPv6 INPUT DROP 标记流量规则"
    fi
    if ! ip6tables -C INPUT -j ACCEPT 2>/dev/null; then
        ip6tables -A INPUT -j ACCEPT
        log "已添加 IPv6 兜底 ACCEPT 规则"
    fi

    if ensure_docker_chain_v6; then
        if ! ip6tables -C DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null; then
            ip6tables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
        fi
        if ! ip6tables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
            ip6tables -I DOCKER-USER 2 -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || true
        fi
    fi

    log "反选模式 IPv6 iptables 规则配置完成"
    return 0
}

# 反选模式的最终实现：
# 1. 已监听且未豁免的端口，复用普通模式的精确端口规则；
# 2. 未配置端口只覆盖低位端口 1-1023；
# 3. 不对 1024-65535 添加全局 mangle 规则，避免影响出站连接的临时端口。
#
# 该函数故意放在旧版实现之后，以便保留旧安装脚本的代码兼容性；Bash 会使用最后一次定义。
setup_iptables_reverse() {
    log "开始配置优化后的反选模式（豁免端口: ${EXEMPT_PORT}/${EXEMPT_PROTO}）..."

    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        log "错误：ipset $IPSET_NAME 不存在，无法配置 iptables"
        return 1
    fi

    # 删除旧版反选模式的全局标记规则，避免继续影响高位临时端口。
    while iptables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; do
        iptables -t mangle -D PREROUTING -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null || break
    done
    while iptables -t mangle -C PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    while iptables -t mangle -C PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        iptables -t mangle -D PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done

    # 先清理旧版反选的 mark DROP；普通模式函数会按当前端口重新建立需要的规则。
    while iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        iptables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done

    # 已监听端口使用原有精确规则，避免把高位临时端口纳入全局规则。
    if ! setup_iptables; then
        log "配置反选模式的已监听端口规则失败"
        return 1
    fi

    # 豁免端口必须在低位全局规则之前 RETURN；仅豁免用户选择的协议。
    if ! iptables -t mangle -C PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; then
        iptables -t mangle -I PREROUTING 1 -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN
    fi

    # 低位端口兜底：覆盖未配置/未监听端口 1-REVERSE_LOW_PORT_MAX。
    # 不覆盖更高端口，避免服务器访问公网时使用的临时端口受到全局规则影响。
    for proto in tcp udp; do
        if ! iptables -t mangle -C PREROUTING -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
            -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
            iptables -t mangle -A PREROUTING -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
                -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK"
            log "已添加低位端口全局限制：非中国IP $proto/1-${REVERSE_LOW_PORT_MAX}"
        fi
    done

    # 只有在低位兜底规则存在时才需要 mark DROP；放在基础规则之后。
    if ! iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
        iptables -I INPUT 3 -m mark --mark "$BLOCK_MARK" -j DROP
        log "已添加低位端口 mark DROP 规则"
    fi

    log "优化后的 IPv4 反选模式配置完成：已监听端口精确限制，未配置低位端口限制，高位端口放行"
    return 0
}

# IPv6 对应的优化反选实现。
setup_iptables_v6_reverse() {
    if ! ipv6_firewall_available; then
        return 0
    fi
    if ! ipset list "$IPSET_NAME_V6" &>/dev/null; then
        log "警告：IPv6 ipset 不存在，跳过 IPv6 反选模式配置"
        return 0
    fi

    # 清理旧版 IPv6 全局标记和豁免规则。
    while ip6tables -t mangle -C PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null || break
    done
    while ip6tables -t mangle -C PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -p tcp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    while ip6tables -t mangle -C PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; do
        ip6tables -t mangle -D PREROUTING -p udp --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null || break
    done
    while ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        ip6tables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done

    # 清理旧版或上一次配置遗留的 IPv6 端口级 mangle 规则。
    while IFS= read -r old_rule; do
        [[ -z "$old_rule" ]] && continue
        delete_rule="${old_rule/-A /-D }"
        ip6tables -t mangle $delete_rule 2>/dev/null || true
    done < <(ip6tables -t mangle -S PREROUTING 2>/dev/null | \
        grep -- "--match-set $IPSET_NAME_V6" | grep -- "MARK --set-mark $BLOCK_MARK" || true)

    if ! setup_iptables_v6; then
        log "配置反选模式的已监听 IPv6 端口规则失败"
        return 1
    fi

    if ! ip6tables -t mangle -C PREROUTING -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN 2>/dev/null; then
        ip6tables -t mangle -I PREROUTING 1 -p "$EXEMPT_PROTO" --dport "$EXEMPT_PORT" -j RETURN
    fi

    for proto in tcp udp; do
        if ! ip6tables -t mangle -C PREROUTING -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
            -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
            ip6tables -t mangle -A PREROUTING -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
                -m set ! --match-set "$IPSET_NAME_V6" src -j MARK --set-mark "$BLOCK_MARK"
            log "已添加 IPv6 低位端口全局限制：非中国IP $proto/1-${REVERSE_LOW_PORT_MAX}"
        fi
    done

    if ! ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; then
        ip6tables -I INPUT 3 -m mark --mark "$BLOCK_MARK" -j DROP
    fi

    log "优化后的 IPv6 反选模式配置完成"
    return 0
}

# 最终反选实现：使用独立 filter 链，不再依赖 mangle MARK。
# 这样不会给 DNS/HTTPS 返回包或其它高位临时端口打标记。
setup_iptables_reverse() {
    local chain="china_reverse"

    log "配置反选防火墙（独立 filter 链，豁免端口：${EXEMPT_PORTS[*]}）..."

    if ! ipset list "$IPSET_NAME" &>/dev/null; then
        log "错误：ipset $IPSET_NAME 不存在"
        return 1
    fi

    # 先复用普通模式处理已监听端口、ICMP 和 UDP 选项。
    setup_iptables || return 1

    # 删除旧版反选和普通端口模式产生的 mangle 标记规则，避免继续影响出站连接返回包。
    # 同时兼容 iptables -S 输出中的 --set-mark 和 --set-xmark。
    while IFS= read -r old_rule; do
        [[ -z "$old_rule" ]] && continue
        local delete_rule="${old_rule/-A /-D }"
        iptables -t mangle $delete_rule 2>/dev/null || true
    done < <(iptables -t mangle -S PREROUTING 2>/dev/null | \
        grep -- "--match-set $IPSET_NAME" | grep -- "MARK" || true)

    # 删除旧的 mark DROP，包括 INPUT 和 DOCKER-USER 中的版本。
    while iptables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        iptables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done
    while iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        iptables -D DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done

    # 重建独立链，避免旧的端口范围规则残留。
    if iptables -nL "$chain" >/dev/null 2>&1; then
        iptables -F "$chain" || {
            log "错误：无法清空反选链 $chain"
            return 1
        }
    elif ! iptables -N "$chain"; then
        log "错误：无法创建反选链 $chain"
        return 1
    fi

    # 已建立连接必须直接放行，保证本机访问 Google、DNS、软件源等正常。
    iptables -A "$chain" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
        iptables -A "$chain" -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true

    # NAT/LXC/WireGuard 内网设备的出站流量不按公网 China IP 判断，避免游戏 UDP 被拦截。
    for subnet in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16; do
        iptables -A "$chain" -s "$subnet" -j ACCEPT
    done

    # 兼容部分系统 conntrack 状态尚未建立的 DNS 返回包。
    iptables -A "$chain" -p udp --sport 53 -j ACCEPT
    iptables -A "$chain" -p tcp --sport 53 -j ACCEPT

    # WireGuard 监听端口必须在反选链中终结 ACCEPT。
    # 使用 RETURN 会回到 INPUT/DOCKER-USER 后续规则，仍可能被全 UDP 限制拦截。
    if [[ "$WIREGUARD_EXEMPT" == true ]]; then
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ "$wgp" =~ ^[0-9]+$ ]] && (( wgp >= 1 && wgp <= 65535 )) || continue
            iptables -A "$chain" -p udp --dport "$wgp" -j ACCEPT
        done
    fi

    # 所有用户选择的端口均终结 ACCEPT，确保反选豁免端口在 INPUT 默认 DROP 下可访问。
    # SSH 端口若已有 f2b-sshd 跳转，先进入 fail2ban 再 ACCEPT，保留封禁效果。
    for exempt in "${EXEMPT_PORTS[@]}"; do
        local exempt_port="${exempt%/*}"
        local exempt_proto="${exempt#*/}"
        if [[ "$exempt_proto" == "tcp" ]] && \
           iptables -S INPUT 2>/dev/null | grep -q -- "--dports $exempt_port -j f2b-sshd"; then
            iptables -A "$chain" -p tcp --dport "$exempt_port" -j f2b-sshd
        fi
        iptables -A "$chain" -p "$exempt_proto" --dport "$exempt_port" -j ACCEPT
    done

    # 已监听但未豁免的端口：非中国 IP 丢弃。
    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        iptables -A "$chain" -p "$proto" --dport "$port" \
            -m set ! --match-set "$IPSET_NAME" src -j DROP
    done

    # 未配置/未监听的低位端口：非中国 IP 丢弃。
    for proto in tcp udp; do
        iptables -A "$chain" -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
            -m set ! --match-set "$IPSET_NAME" src -j DROP
    done

    # 用户明确选择限制所有 UDP 时，按原意限制所有新建的非中国 UDP。
    # 已建立/关联连接已在链首 RETURN，不影响本机主动发起的 DNS 等请求。
    if [[ "$PROTECT_UDP_ALL" == true ]]; then
        iptables -A "$chain" -p udp -m set ! --match-set "$IPSET_NAME" src -j DROP
    fi

    # 用户选择限制 ICMP 时，限制非中国 IPv4 ping。
    if [[ "$PROTECT_ICMP" == true ]]; then
        iptables -A "$chain" -p icmp --icmp-type echo-request \
            -m set ! --match-set "$IPSET_NAME" src -j DROP
    fi

    # 其它端口/协议 RETURN，由 INPUT 原有规则继续处理；高位端口不会被本链限制。
    if ! iptables -C INPUT -j "$chain" 2>/dev/null; then
        if ! iptables -I INPUT 1 -j "$chain"; then
            log "错误：无法将反选链 $chain 挂接到 INPUT"
            return 1
        fi
    fi

    # Docker 转发也经过同一套端口限制，但允许链返回给 Docker 自己的规则。
    if ensure_docker_chain; then
        while iptables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
            iptables -D DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
        done
        if ! iptables -C DOCKER-USER -j "$chain" 2>/dev/null; then
            if ! iptables -I DOCKER-USER 1 -j "$chain"; then
                log "错误：无法将反选链 $chain 挂接到 DOCKER-USER"
                return 1
            fi
        fi
    fi

    if ! iptables -nL "$chain" >/dev/null 2>&1 || ! iptables -C INPUT -j "$chain" 2>/dev/null; then
        log "错误：反选链 $chain 未能生效"
        return 1
    fi

    log "反选防火墙配置完成：高位端口不做全局限制，已建立连接正常放行"
    return 0
}

setup_iptables_v6_reverse() {
    if ! ipv6_firewall_available; then
        return 0
    fi
    if ! ipset list "$IPSET_NAME_V6" &>/dev/null; then
        log "警告：IPv6 ipset 不存在，跳过 IPv6 反选规则"
        return 0
    fi

    local chain="china_reverse_v6"
    log "配置 IPv6 反选防火墙（独立 filter 链，豁免端口：${EXEMPT_PORTS[*]}）..."
    setup_iptables_v6 || return 1

    while IFS= read -r old_rule; do
        [[ -z "$old_rule" ]] && continue
        local delete_rule="${old_rule/-A /-D }"
        ip6tables -t mangle $delete_rule 2>/dev/null || true
    done < <(ip6tables -t mangle -S PREROUTING 2>/dev/null | \
        grep -- "--match-set $IPSET_NAME_V6" | grep -- "MARK" || true)

    while ip6tables -C INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        ip6tables -D INPUT -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done
    while ip6tables -C DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null; do
        ip6tables -D DOCKER-USER -m mark --mark "$BLOCK_MARK" -j DROP 2>/dev/null || break
    done

    if ip6tables -nL "$chain" >/dev/null 2>&1; then
        ip6tables -F "$chain" || {
            log "错误：无法清空 IPv6 反选链 $chain"
            return 1
        }
    elif ! ip6tables -N "$chain"; then
        log "错误：无法创建 IPv6 反选链 $chain"
        return 1
    fi

    ip6tables -A "$chain" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
        ip6tables -A "$chain" -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true

    # IPv6 ULA 内网设备的出站流量不按公网 China IP 判断。
    ip6tables -A "$chain" -s fc00::/7 -j ACCEPT

    ip6tables -A "$chain" -p udp --sport 53 -j ACCEPT
    ip6tables -A "$chain" -p tcp --sport 53 -j ACCEPT
    if [[ "$WIREGUARD_EXEMPT" == true ]]; then
        IFS=',' read -ra wg_ports <<< "$WIREGUARD_PORT"
        for wgp in "${wg_ports[@]}"; do
            wgp="${wgp// /}"
            [[ "$wgp" =~ ^[0-9]+$ ]] && (( wgp >= 1 && wgp <= 65535 )) || continue
            ip6tables -A "$chain" -p udp --dport "$wgp" -j ACCEPT
        done
    fi
    for exempt in "${EXEMPT_PORTS[@]}"; do
        local exempt_port="${exempt%/*}"
        local exempt_proto="${exempt#*/}"
        ip6tables -A "$chain" -p "$exempt_proto" --dport "$exempt_port" -j ACCEPT
    done

    for port_proto in "${PROTECTED_PORTS[@]}"; do
        local port="${port_proto%/*}"
        local proto="${port_proto#*/}"
        ip6tables -A "$chain" -p "$proto" --dport "$port" \
            -m set ! --match-set "$IPSET_NAME_V6" src -j DROP
    done
    for proto in tcp udp; do
        ip6tables -A "$chain" -p "$proto" --dport "1:${REVERSE_LOW_PORT_MAX}" \
            -m set ! --match-set "$IPSET_NAME_V6" src -j DROP
    done
    if [[ "$PROTECT_UDP_ALL" == true ]]; then
        ip6tables -A "$chain" -p udp -m set ! --match-set "$IPSET_NAME_V6" src -j DROP
    fi
    if [[ "$PROTECT_ICMP" == true ]]; then
        ip6tables -A "$chain" -p ipv6-icmp --icmpv6-type echo-request \
            -m set ! --match-set "$IPSET_NAME_V6" src -j DROP
    fi

    if ! ip6tables -C INPUT -j "$chain" 2>/dev/null; then
        if ! ip6tables -I INPUT 1 -j "$chain"; then
            log "错误：无法将 IPv6 反选链 $chain 挂接到 INPUT"
            return 1
        fi
    fi
    if ensure_docker_chain_v6 && ! ip6tables -C DOCKER-USER -j "$chain" 2>/dev/null; then
        if ! ip6tables -I DOCKER-USER 1 -j "$chain"; then
            log "错误：无法将 IPv6 反选链 $chain 挂接到 DOCKER-USER"
            return 1
        fi
    fi
    if ! ip6tables -nL "$chain" >/dev/null 2>&1 || ! ip6tables -C INPUT -j "$chain" 2>/dev/null; then
        log "错误：IPv6 反选链 $chain 未能生效"
        return 1
    fi

    log "IPv6 反选防火墙配置完成"
    return 0
}

# 保存iptables规则
save_iptables() {
    log "保存iptables规则..."
    
    if command -v netfilter-persistent &> /dev/null; then
        netfilter-persistent save
        log "使用netfilter-persistent保存规则"
    elif [[ -d /etc/iptables ]]; then
        iptables-save > /etc/iptables/rules.v4
        log "保存规则到 /etc/iptables/rules.v4"
        if ipv6_firewall_available; then
            ip6tables-save > /etc/iptables/rules.v6 2>/dev/null || true
            log "保存IPv6规则到 /etc/iptables/rules.v6"
        fi
    elif [[ -d /etc/sysconfig ]]; then
        iptables-save > /etc/sysconfig/iptables
        log "保存规则到 /etc/sysconfig/iptables"
        if ipv6_firewall_available; then
            ip6tables-save > /etc/sysconfig/ip6tables 2>/dev/null || true
            log "保存IPv6规则到 /etc/sysconfig/ip6tables"
        fi
    else
        log "警告：无法自动保存iptables规则，请手动保存"
    fi
    
    # 保存ipset
    if command -v ipset &> /dev/null; then
        mkdir -p /etc/ipset
        ipset save > /etc/ipset/ipset.conf 2>/dev/null || true
        log "保存ipset到 /etc/ipset/ipset.conf"
    fi
}

# 设置开机自启动
setup_autostart() {
    log "配置开机自启动..."
    
    # 创建systemd服务
    cat > "$SYSTEMD_SERVICE" <<'EOF'
[Unit]
Description=China IP Firewall - Load ipset and iptables rules
After=network.target
Before=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'ipset restore -f /etc/ipset/ipset.conf 2>/dev/null || true'
ExecStart=/bin/bash -c 'iptables-restore -n < /etc/iptables/rules.v4 2>/dev/null || iptables-restore -n < /etc/sysconfig/iptables 2>/dev/null || true'
ExecStart=/bin/bash -c 'ip6tables-restore -n < /etc/iptables/rules.v6 2>/dev/null || ip6tables-restore -n < /etc/sysconfig/ip6tables 2>/dev/null || true'

[Install]
WantedBy=multi-user.target
EOF

    # 重载systemd并启用服务
    systemctl daemon-reload
    systemctl enable china-ip-firewall.service
    
    log "已配置开机自启动服务"
}

# 设置定时任务
setup_cron() {
    ensure_script_persisted
    local script_path="$EFFECTIVE_SCRIPT_PATH"
    local quoted_script=$(printf '%q' "$script_path")
    local quoted_log=$(printf '%q' "$LOG_FILE")
    local cron_cmd="0 3 * * 0 $quoted_script update >> $quoted_log 2>&1"
    
    # 检查cron任务是否已存在
    if crontab -l 2>/dev/null | grep -F "$script_path" | grep -v "^#" >/dev/null; then
        log "定时任务已存在，跳过"
    else
        (crontab -l 2>/dev/null; echo "$cron_cmd") | crontab -
        log "已添加定时任务：每周日凌晨3点更新中国IP列表"
    fi
}

# 显示状态
show_status() {
    echo "======================================"
    echo "中国IP白名单防火墙状态"
    echo "======================================"
    
    if ipset list "$IPSET_NAME" &>/dev/null; then
        local count=$(ipset list "$IPSET_NAME" | grep -c "^[0-9]" || echo 0)
        echo "✓ IPv4 IPSet: $IPSET_NAME ($count 条规则)"
    else
        echo "✗ IPv4 IPSet: 未创建"
    fi
    if ipset list "$IPSET_NAME_V6" &>/dev/null; then
        local count6=$(ipset list "$IPSET_NAME_V6" | grep -c "^[0-9a-fA-F]" || echo 0)
        echo "✓ IPv6 IPSet: $IPSET_NAME_V6 ($count6 条规则)"
    else
        echo "✗ IPv6 IPSet: 未创建"
    fi
    
    echo ""
    echo "受保护端口的iptables规则 (IPv4)："
    local -a protected_ports=()
    if iptables -nL china_reverse >/dev/null 2>&1; then
        echo "✓ 反选模式 - 已启用（未豁免的已监听端口及低位端口仅允许中国IP）"
        echo "  豁免端口："
        iptables -S china_reverse 2>/dev/null | awk '/-j (RETURN|ACCEPT)/ && /--dport/ {
            proto=""; port=""
            for (i=1; i<=NF; i++) {
                if ($i == "-p") proto=$(i+1)
                if ($i == "--dport") port=$(i+1)
            }
            if (proto != "" && port != "") print "    - " port "/" proto
        }'
    elif mapfile -t protected_ports < <(get_current_protected_ports); then
        declare -A seen_ports=()
        local -a unique_ports=()
        for entry in "${protected_ports[@]}"; do
            [[ -z "$entry" ]] && continue
            if [[ -z "${seen_ports[$entry]}" ]]; then
                seen_ports[$entry]=1
                unique_ports+=("$entry")
            fi
        done
        if [[ ${#unique_ports[@]} -gt 0 ]]; then
            for entry in "${unique_ports[@]}"; do
                echo "✓ $entry - 已保护"
            done
        else
            echo "✗ 未检测到受保护的端口规则"
        fi
    else
        echo "✗ 无法检测当前iptables规则"
    fi

    if ipv6_firewall_available; then
        echo ""
        echo "受保护端口的ip6tables规则 (IPv6)："
        local -a protected_ports_v6=()
        if ip6tables -nL china_reverse_v6 >/dev/null 2>&1; then
            echo "✓ IPv6 反选模式 - 已启用"
        elif mapfile -t protected_ports_v6 < <(get_current_protected_ports_v6); then
            declare -A seen_ports_v6=()
            local -a unique_ports_v6=()
            for entry in "${protected_ports_v6[@]}"; do
                [[ -z "$entry" ]] && continue
                if [[ -z "${seen_ports_v6[$entry]}" ]]; then
                    seen_ports_v6[$entry]=1
                    unique_ports_v6+=("$entry")
                fi
            done
            if [[ ${#unique_ports_v6[@]} -gt 0 ]]; then
                for entry in "${unique_ports_v6[@]}"; do
                    echo "✓ $entry - 已保护"
                done
            else
                echo "✗ 未检测到受保护的IPv6端口规则"
            fi
        else
            echo "✗ 无法检测当前ip6tables规则"
        fi
    fi
    
    # 检查ICMP保护
    if iptables -C INPUT -p icmp --icmp-type echo-request -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
        echo "✓ ICMP (ping) - 已保护"
    else
        echo "✗ ICMP (ping) - 未保护"
    fi

    # 检查 UDP-all 保护
    if iptables -nL china_reverse >/dev/null 2>&1 && \
       iptables -C china_reverse -p udp -m set ! --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
        echo "✓ 所有 UDP 流量 - 已保护（反选链；显示的显式豁免端口除外）"
    elif iptables -t mangle -C PREROUTING -p udp -m set ! --match-set "$IPSET_NAME" src -j MARK --set-mark "$BLOCK_MARK" 2>/dev/null; then
        echo "✓ 所有 UDP 流量 - 已保护（仅中国IP可访）"
    else
        echo "✗ 所有 UDP 流量 - 未保护"
    fi

    # 检查出向中国流量限制（独立可选功能）
    show_cn_outbound_status

    echo "自定义IP段（最高优先级白名单）："
    if [[ -f "$CUSTOM_IP_FILE" ]]; then
        local custom_count=$(grep -v "^#" "$CUSTOM_IP_FILE" 2>/dev/null | grep -c "^[0-9a-fA-F:]" || echo 0)
        echo "✓ 已配置 $custom_count 个自定义IP段（完全绕过所有防火墙限制）"
        if [[ $custom_count -gt 0 ]]; then
            echo "  文件位置: $CUSTOM_IP_FILE"
            echo "  内容预览:"
            grep -v "^#" "$CUSTOM_IP_FILE" 2>/dev/null | grep "^[0-9a-fA-F:]" | head -5 | sed 's/^/    /'
            if [[ $custom_count -gt 5 ]]; then
                echo "    ... (共 $custom_count 个)"
            fi
        fi
    else
        echo "✗ 未配置自定义IP段"
    fi
    if ipset list "$CUSTOM_IPSET_NAME" &>/dev/null; then
        local wl_count=$(ipset list "$CUSTOM_IPSET_NAME" 2>/dev/null | grep -c "^[0-9]" || echo 0)
        echo "  custom_whitelist ipset: $wl_count 条（mangle 顶部豁免）"
    fi

    echo ""
    echo "安全加固规则："
    if iptables -t raw -C PREROUTING -j PORT0_GUARD 2>/dev/null && \
       iptables -t raw -S PORT0_GUARD 2>/dev/null | grep -q 'PORT0_DROP:'; then
        echo "✓ 端口0阻断与日志 - 已启用（raw PORT0_GUARD：LOG 后 DROP）"
    else
        echo "✗ 端口0阻断 - 未启用"
    fi

    echo ""
    echo "特殊豁免配置："
    if iptables -t mangle -C PREROUTING -i wg+ -j RETURN 2>/dev/null; then
        echo "✓ WireGuard/内网接口豁免 - 已启用（-i wg+ RETURN）"
    else
        echo "✗ WireGuard 流量豁免 - 未启用"
    fi
    
    echo ""
    echo "开机自启动："
    if systemctl is-enabled china-ip-firewall.service &>/dev/null; then
        echo "✓ 已启用"
    else
        echo "✗ 未启用"
    fi
    
    echo ""
    echo "定时任务："
    local script_path=$(get_effective_script_path)
    if crontab -l 2>/dev/null | grep -F "$script_path" | grep -v "^#" >/dev/null; then
        echo "✓ 已设置（每周日凌晨3点更新）"
        crontab -l 2>/dev/null | grep -F "$script_path" | grep -v "^#"
    else
        echo "✗ 未设置"
    fi
    
    echo "======================================"
}

# 显示安装完成信息
show_install_complete() {
    echo ""
    echo "=========================================="
    echo "  ✓ 安装完成！"
    echo "=========================================="
    echo ""
    echo "【重要配置文件位置】"
    echo "  自定义IP段配置文件: $CUSTOM_IP_FILE"
    echo "  日志文件: $LOG_FILE"
    echo ""
    echo "【如何添加更多自定义IP段】"
    echo "  编辑文件: $CUSTOM_IP_FILE"
    echo "  每行一个IP段（CIDR格式），例如:"
    echo "    103.143.14.0/24"
    echo "    192.168.1.0/24"
    echo ""
    local script_path=$(get_effective_script_path)
    echo "  添加后运行以下命令重新加载:"
    echo "    $script_path add-custom"
    echo ""
    echo "【常用命令】"
    echo "  查看状态: $script_path status"
    echo "  手动更新: $script_path update"
    echo "  添加IP段: $script_path add-custom"
    echo "  出向限制: $script_path outbound   （可选，默认关闭）"
    echo "  卸载脚本: $script_path uninstall"
    echo ""
    echo "=========================================="
}

# 卸载功能
uninstall() {
    log "开始卸载..."

    # ============================================================
    # 核心清理逻辑：直接扫描 iptables 规则，不依赖运行时 PROTECTED_PORTS
    # 删除所有包含本脚本 ipset 名称或 BLOCK_MARK 的规则
    # ============================================================
    local max_rounds=30
    local round

    # --- 端口0阻断规则清理 ---
    cleanup_block_port_zero

    # --- 出向中国流量限制规则清理 ---
    cleanup_cn_outbound_block

    # --- IPv4 mangle 表 PREROUTING ---
    log "清理 IPv4 mangle PREROUTING 规则..."
    local mangle_rules=()
    while IFS= read -r rule; do
        [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
        echo "$rule" | grep -qE "(--match-set ${IPSET_NAME}[^_]|--match-set ${IPSET_NAME}$|MARK --set-mark ${BLOCK_MARK})" && mangle_rules+=("$rule")
    done < <(iptables -t mangle -S PREROUTING 2>/dev/null)
    for rule in "${mangle_rules[@]}"; do
        local del="${rule/-A /-D }"
        iptables -t mangle $del 2>/dev/null && log "已删除 mangle 规则：${rule#-A }" || true
    done

    # --- IPv4 INPUT 链：删除所有含 china_ip ipset 引用 或 mark 0x1 的规则 ---
    log "清理 IPv4 INPUT 链规则..."
    round=0
    while (( round < max_rounds )); do
        local found=false
        while IFS= read -r rule; do
            [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
            if echo "$rule" | grep -qE "(--match-set ${IPSET_NAME}[^_]|--match-set ${IPSET_NAME} |--match-set ${IPSET_NAME}$|mark --mark ${BLOCK_MARK})"; then
                local del="${rule/-A /-D }"
                if iptables $del 2>/dev/null; then
                    log "已删除 INPUT 规则：${rule#-A }"
                    found=true
                    break
                fi
            fi
        done < <(iptables -S INPUT 2>/dev/null)
        [[ "$found" == false ]] && break
        ((round++))
    done

    # --- IPv4 DOCKER-USER 链 ---
    if iptables -nL DOCKER-USER &>/dev/null; then
        log "清理 IPv4 DOCKER-USER 链规则..."
        round=0
        while (( round < max_rounds )); do
            local found=false
            while IFS= read -r rule; do
                [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
                if echo "$rule" | grep -qE "(--match-set ${IPSET_NAME}[^_]|--match-set ${IPSET_NAME} |--match-set ${IPSET_NAME}$|mark --mark ${BLOCK_MARK})"; then
                    local del="${rule/-A /-D }"
                    if iptables $del 2>/dev/null; then
                        log "已删除 DOCKER-USER 规则：${rule#-A }"
                        found=true
                        break
                    fi
                fi
            done < <(iptables -S DOCKER-USER 2>/dev/null)
            [[ "$found" == false ]] && break
            ((round++))
        done
    fi

    # --- IPv6 ---
    if ipv6_firewall_available; then
        log "清理 IPv6 mangle PREROUTING 规则..."
        local mangle_rules_v6=()
        while IFS= read -r rule; do
            [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
            echo "$rule" | grep -qE "(--match-set ${IPSET_NAME_V6}|MARK --set-mark ${BLOCK_MARK})" && mangle_rules_v6+=("$rule")
        done < <(ip6tables -t mangle -S PREROUTING 2>/dev/null)
        for rule in "${mangle_rules_v6[@]}"; do
            local del="${rule/-A /-D }"
            ip6tables -t mangle $del 2>/dev/null && log "已删除 IPv6 mangle 规则：${rule#-A }" || true
        done

        log "清理 IPv6 INPUT 链规则..."
        round=0
        while (( round < max_rounds )); do
            local found=false
            while IFS= read -r rule; do
                [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
                if echo "$rule" | grep -qE "(--match-set ${IPSET_NAME_V6}|mark --mark ${BLOCK_MARK})"; then
                    local del="${rule/-A /-D }"
                    if ip6tables $del 2>/dev/null; then
                        log "已删除 IPv6 INPUT 规则：${rule#-A }"
                        found=true
                        break
                    fi
                fi
            done < <(ip6tables -S INPUT 2>/dev/null)
            [[ "$found" == false ]] && break
            ((round++))
        done

        if ip6tables -nL DOCKER-USER &>/dev/null; then
            log "清理 IPv6 DOCKER-USER 链规则..."
            round=0
            while (( round < max_rounds )); do
                local found=false
                while IFS= read -r rule; do
                    [[ -z "$rule" || "$rule" == -P* || "$rule" == -N* ]] && continue
                    if echo "$rule" | grep -qE "(--match-set ${IPSET_NAME_V6}|mark --mark ${BLOCK_MARK})"; then
                        local del="${rule/-A /-D }"
                        if ip6tables $del 2>/dev/null; then
                            log "已删除 IPv6 DOCKER-USER 规则：${rule#-A }"
                            found=true
                            break
                        fi
                    fi
                done < <(ip6tables -S DOCKER-USER 2>/dev/null)
                [[ "$found" == false ]] && break
                ((round++))
            done
        fi
    fi

    # --- 删除 ipset ---
    for set_name in "$IPSET_NAME" "${IPSET_NAME}_temp" "$IPSET_NAME_V6" "${IPSET_NAME_V6}_temp" \
                    "$CUSTOM_IPSET_NAME" "${CUSTOM_IPSET_NAME}_v6"; do
        if ipset list "$set_name" &>/dev/null; then
            ipset destroy "$set_name" 2>/dev/null && log "已删除ipset: $set_name" || true
        fi
    done

    # --- 删除开机自启动 ---
    if [[ -f "$SYSTEMD_SERVICE" ]]; then
        systemctl disable china-ip-firewall.service 2>/dev/null || true
        systemctl stop china-ip-firewall.service 2>/dev/null || true
        rm -f "$SYSTEMD_SERVICE"
        systemctl daemon-reload
        log "已删除开机自启动服务"
    fi

    # --- 删除cron任务 ---
    local script_path=$(realpath "$0" 2>/dev/null || echo "$0")
    crontab -l 2>/dev/null | grep -v "$script_path" | crontab - 2>/dev/null || true
    log "已删除定时任务"

    save_iptables
    log "卸载完成"

    echo ""
    read -p "是否删除自定义IP配置文件? (y/N): " del_custom
    if [[ "$del_custom" =~ ^[Yy]$ ]]; then
        rm -f "$CUSTOM_IP_FILE"
        log "已删除自定义IP配置文件"
    fi
}

# 显示交互式菜单
show_menu() {
    clear
    echo "=========================================="
    echo "  中国IP白名单防火墙管理工具"
    echo "=========================================="
    echo ""
    echo "请选择要执行的操作："
    echo ""
    echo "  1. 安装/配置防火墙规则"
    echo "  2. 更新中国IP列表"
    echo "  3. 查看当前状态"
    echo "  4. 添加/修改自定义IP段"
    echo "  5. 卸载防火墙规则"
    echo "  6. 出向中国流量限制（OUTPUT/FORWARD，可选，默认关闭）"
    echo "  7. 退出"
    echo ""
    echo "=========================================="
    read -p "请输入选项 (1-7): " menu_choice
    echo ""
    
    case "$menu_choice" in
        1|2|3|4|5|6)
            # 有效选择，继续处理
            ;;
        7|q|Q)
            echo "退出程序"
            exit 0
            ;;
        *)
            echo "无效的选项，请重新选择"
            sleep 2
            show_menu
            ;;
    esac
}

# 主函数
main() {
    local action="${1}"
    
    # 如果没有参数，显示交互式菜单
    if [[ -z "$action" ]]; then
        show_menu
        action="$menu_choice"
    fi
    
    case "$action" in
        1|install)
            # 显示介绍
            show_intro
            
            # 检查权限
            check_root
            
            # 检查依赖
            check_dependencies
            
            # 确认安装
            echo ""
            read -p "是否继续安装? (y/N): " confirm
            if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
                echo ""
                echo "已取消安装"
                exit 0
            fi
            
            log "========== 开始安装 =========="
            
            # 询问是否清理iptables
            ask_clean_iptables
            
            # 扫描并选择要保护的端口
            select_ports_to_protect
            
            # 检查是否有要保护的内容（反选模式下 PROTECTED_PORTS 可以为空，但 REVERSE_MODE=true）
            if [[ "$REVERSE_MODE" != true ]] && \
               [[ ${#PROTECTED_PORTS[@]} -eq 0 ]] && \
               [[ "$PROTECT_ICMP" != true ]] && \
               [[ "$PROTECT_UDP_ALL" != true ]]; then
                echo ""
                echo "警告：未选择任何要保护的端口或协议"
                read -p "是否继续安装（仅配置IP集合）? (y/N): " continue_anyway
                if [[ ! "$continue_anyway" =~ ^[Yy]$ ]]; then
                    log "用户取消安装"
                    echo "已取消安装"
                    exit 0
                fi
            fi
            
            # 下载中国IP列表
            echo ""
            echo "正在下载中国IP列表..."
            if ! download_china_ip; then
                log "下载失败，退出"
                echo ""
                echo "错误: 无法下载中国IP列表，请检查网络连接"
                exit 1
            fi
            
            # 更新ipset
            echo "正在更新IP规则集..."
            if ! update_ipset; then
                log "更新ipset失败，退出"
                echo ""
                echo "错误: 更新IP规则集失败"
                exit 1
            fi

            if ! update_ipset_v6; then
                log "警告：IPv6 ipset 更新失败，将仅限制IPv4流量"
            fi
            
            # 交互式添加自定义IP段
            add_custom_ips_interactive
            
            # 重新加载自定义IP段（如果已添加）
            load_custom_ips
            
            # 配置iptables
            echo ""
            echo "正在配置防火墙规则..."
            if [[ "$REVERSE_MODE" == true ]]; then
                # 反选模式：已监听端口精确限制，低位未配置端口兜底限制
                if ! setup_iptables_reverse; then
                    log "配置反选模式 iptables 失败，退出"
                    echo ""
                    echo "错误: 配置防火墙规则失败"
                    exit 1
                fi
                # 配置IPv6反选模式
                if [[ "$PROTECT_IPV6" == true ]]; then
                    if ! setup_iptables_v6_reverse; then
                        log "警告：IPv6 反选模式防火墙配置失败或跳过"
                    fi
                fi
            else
                if ! setup_iptables; then
                    log "配置iptables失败，退出"
                    echo ""
                    echo "错误: 配置防火墙规则失败"
                    exit 1
                fi
                # 配置IPv6 iptables
                if [[ "$PROTECT_IPV6" == true ]]; then
                    if ! setup_iptables_v6; then
                        log "警告：IPv6防火墙配置失败或跳过"
                    fi
                fi
            fi

            # 保存规则
            echo "正在保存配置..."
            save_iptables
            
            # 设置自启动
            echo "正在配置开机自启动..."
            setup_autostart
            
            # 设置定时任务
            echo "正在配置定时更新..."
            setup_cron
            
            log "========== 安装完成 =========="
            
            # 显示完成信息和状态
            show_install_complete
            echo ""
            show_status
            
            # 如果是交互式安装，询问是否继续
            if [[ -z "${1}" ]]; then
                echo ""
                read -p "按任意键返回菜单..." -n 1
                show_menu
                main "$menu_choice"
            fi
            ;;
            
        2|update)
            check_root
            check_dependencies
            log "========== 开始更新 =========="
            
            if ! download_china_ip; then
                log "下载失败，退出"
                exit 1
            fi
            
            if ! update_ipset; then
                log "更新ipset失败，退出"
                exit 1
            fi

            if ! update_ipset_v6; then
                log "警告：IPv6 ipset 更新失败，将仅限制IPv4流量"
            fi
            
            # 更新后重新加载自定义IP段
            load_custom_ips
            
            save_iptables
            log "========== 更新完成 =========="
            
            # 如果是交互式模式，返回菜单
            if [[ -z "${1}" ]]; then
                echo ""
                read -p "按任意键返回菜单..." -n 1
                show_menu
                main "$menu_choice"
            fi
            ;;
            
        3|status)
            show_status
            
            # 如果是交互式模式，返回菜单
            if [[ -z "${1}" ]]; then
                echo ""
                read -p "按任意键返回菜单..." -n 1
                show_menu
                main "$menu_choice"
            fi
            ;;
            
        4|add-custom)
            check_root
            add_custom_ips_interactive
            if ipset list "$IPSET_NAME" &>/dev/null; then
                log "重新加载自定义IP段..."
                load_custom_ips
                save_iptables
                log "已加载自定义IP段"
                echo ""
                echo "✓ 自定义IP段已重新加载"
            else
                echo ""
                echo "错误: ipset 不存在，请先运行安装命令"
            fi
            
            # 如果是交互式模式，返回菜单
            if [[ -z "${1}" ]]; then
                echo ""
                read -p "按任意键返回菜单..." -n 1
                show_menu
                main "$menu_choice"
            fi
            ;;
            
        5|uninstall)
            check_root
            echo ""
            echo "警告: 此操作将删除所有防火墙规则和配置"
            read -p "确认卸载? (y/N): " confirm
            if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
                echo "已取消卸载"
                
                # 如果是交互式模式，返回菜单；否则退出
                if [[ -z "${1}" ]]; then
                    echo ""
                    read -p "按任意键返回菜单..." -n 1
                    show_menu
                    main "$menu_choice"
                else
                    exit 0
                fi
            else
                uninstall
                echo ""
                echo "卸载完成"
                
                # 交互式或命令行模式都退出
                if [[ -z "${1}" ]]; then
                    read -p "按任意键退出..." -n 1
                fi
                exit 0
            fi
            ;;

        6|outbound)
            # 出向中国流量限制：独立可选功能，默认关闭
            check_root
            manage_cn_outbound

            # 如果是交互式模式，返回菜单
            if [[ -z "${1}" ]]; then
                echo ""
                read -p "按任意键返回菜单..." -n 1
                show_menu
                main "$menu_choice"
            fi
            ;;

        outbound-on)
            check_root
            enable_cn_outbound_interactive
            ;;

        outbound-off)
            check_root
            disable_cn_outbound
            ;;
            
        *)
            # 如果是通过命令行参数调用（用于cron等）
            echo "用法: $0 [install|update|status|add-custom|outbound|outbound-on|outbound-off|uninstall]"
            echo ""
            echo "  不带参数      - 显示交互式菜单（推荐）"
            echo "  install       - 初次安装（交互式）"
            echo "  update        - 手动更新中国IP列表（保留自定义IP段）"
            echo "  status        - 查看当前状态"
            echo "  add-custom    - 添加或修改自定义IP段"
            echo "  outbound      - 出向中国流量限制管理（可选，默认关闭）"
            echo "  outbound-on   - 直接启用出向中国流量限制"
            echo "  outbound-off  - 直接关闭出向中国流量限制"
            echo "  uninstall     - 卸载所有配置"
            exit 1
            ;;
    esac
}

main "$@"
