linux主机防火墙设置仅允许中国IP访问
脚本作用
cn-firewall-permit.sh 是一个 Linux 入站防火墙白名单配置脚本,用于限制服务器访问来源。
核心逻辑是:默认拒绝非白名单入站访问,仅放行中国 IP、已建立连接、SSH 管理端口、局域网网段和用户自定义 IP/CIDR。
主要功能
自动安装并配置防火墙规则;
自动下载并维护中国 IP 集合;
使用
ipset提升大量 IP 段匹配效率;支持
iptables和firewalld;支持 IPv4,IPv6 可选;
支持自动发现局域网网段并放行;
支持自定义 IP/CIDR 放行;
支持 SSH 端口保护;
支持中国 IP 集合定时更新;
支持规则备份、回滚和紧急恢复;
支持静默模式批量部署。
主菜单
1) 自动安装:放行中国IP集合
2) 自定义放行:查看已有、增加或修改放行白名单
3) 配置IP集自动更新策略
0) 退出选项说明
1)自动安装
执行完整安装流程,配置中国 IP 集合、防火墙规则、SSH 放行、IPset 自启和定时更新任务。
2)自定义放行
用于管理额外白名单,例如:
局域网网段;
办公网公网 IP;
堡垒机 IP;
监控平台 IP;
指定 CIDR 网段。
3)配置 IP 集自动更新策略
单独设置中国 IP 集合的更新频率和更新时间。
放行规则
默认放行:
中国 IP 集合;
已建立连接;
回环地址;
SSH 端口;
自动发现的局域网网段;
自定义 IP/CIDR。
默认拒绝:
其他非白名单入站来源。
常用命令
赋权:
chmod +x cn-firewall-permit.sh交互运行:
sudo ./cn-firewall-permit.sh查看状态:
sudo ./cn-firewall-permit.sh --status备份规则:
sudo ./cn-firewall-permit.sh --backup回滚规则:
sudo ./cn-firewall-permit.sh --rollback紧急恢复:
sudo ./cn-firewall-permit.sh --recovery修复 IPset 自启:
sudo ./cn-firewall-permit.sh --fix-ipset重要文件
配置文件示例
UPDATE_INTERVAL=1
UPDATE_TIME="03:00"
ENABLE_IPV6="false"
ALLOW_SSH="true"
SSH_PORT=22
CUSTOM_LAN_CIDRS=(
"192.168.113.0/24"
)
CUSTOM_ALLOW_IPS=(
"1.2.3.4"
"203.0.113.0/24"
)注意事项
首次运行建议通过控制台、VNC、IPMI 或云厂商控制台操作;
执行前确认 SSH 端口正确;
如果只放行中国 IP,国外网络可能无法访问;
如需远程管理,建议提前加入固定公网 IP 白名单;
出现断连时,可通过控制台执行
--recovery恢复。
源代码:
#!/bin/bash
# 兼容多系统(CentOS/Debian/Ubuntu等),修复IPset自启脚本缺失+systemd服务启动失败问题
# 核心修复:强制校验脚本存在性+自动重建缺失脚本+增强权限赋值
VERSION="v3.4-startmenu(开局配置更新策略+二级自定义放行管理)"
CRON_FILE="/etc/cron.d/update-cn-ip-firewall"
IPSET_IPV4="cn_ipv4"
IPSET_IPV6="cn_ipv6"
IPSET_IPV4_TEMP="cn_ipv4_temp"
IPSET_IPV6_TEMP="cn_ipv6_temp"
APNIC_URL="http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest"
SCRIPT_PATH="/usr/local/bin/update-cn-ipset.sh"
BACKUP_DIR="/etc/firewall-cn-backup"
LOG_FILE="/var/log/cn-firewall.log"
FIREWALL_TYPE=""
UPDATE_INTERVAL=1
UPDATE_TIME="03:00"
# 默认只放行回环网段
LAN_CIDRS=("127.0.0.1/32" "::1/128")
DETECTED_LAN_CIDRS=() # 存储自动发现的内网网段
ENABLE_IPV6="false"
SILENT_MODE="false"
DEBUG_MODE="false"
CONFIG_FILE="/etc/cn-firewall.conf"
ALLOW_SSH="true"
SSH_PORT=22
CUSTOM_LAN_CIDRS=()
CUSTOM_ALLOW_IPS=() # 自定义放行IP/CIDR,支持 1.2.3.4、1.2.3.0/24、IPv6/IPv6-CIDR
CUSTOM_ALLOW_CIDRS=() # 运行时归一化后的自定义放行地址列表
WHITELIST_MODE="cn_only" # cn_only=自动安装仅放行中国IP集合;custom=进入自定义放行管理;lan=兼容旧配置
START_ACTION="install" # install=自动安装CN IP集,custom_allow=自定义放行管理
CUSTOM_DNS="" # 自定义DNS(留空自动探测,兼容国内外)
IPTABLES_CHAIN="CN_FIREWALL_INPUT"
IP6TABLES_CHAIN="CN_FIREWALL_INPUT"
IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"
IPSET_INIT_SCRIPT="/etc/init.d/ipset-init"
IPSET_SYSTEMD_SERVICE="/etc/systemd/system/ipset-init.service"
# ===================== 颜色/日志函数 =====================
red() { echo -e "\033[31m$1\033[0m"; }
green() { echo -e "\033[32m$1\033[0m"; }
yellow() { echo -e "\033[33m$1\033[0m"; }
blue() { echo -e "\033[34m$1\033[0m"; }
cyan() { echo -e "\033[36m$1\033[0m"; }
log_info() {
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [INFO] ${msg}" >> "${LOG_FILE}"
if [ "${SILENT_MODE}" = "false" ]; then
green "✅ ${msg}"
fi
}
log_warn() {
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [WARN] ${msg}" >> "${LOG_FILE}"
if [ "${SILENT_MODE}" = "false" ]; then
yellow "⚠️ ${msg}"
fi
}
log_error() {
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [ERROR] ${msg}" >> "${LOG_FILE}"
if [ "${SILENT_MODE}" = "false" ]; then
red "❌ ${msg}"
fi
exit 1
}
log_debug() {
if [ "${DEBUG_MODE}" = "true" ]; then
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [DEBUG] ${msg}" >> "${LOG_FILE}"
if [ "${SILENT_MODE}" = "false" ]; then
cyan "🔍 ${msg}"
fi
fi
}
# ===================== 核心工具函数 =====================
show_help() {
echo -e "
CN Firewall Config Tool ${VERSION}"
echo -e "================================================"
echo -e "功能:自动安装仅放行中国IP集合,开局配置更新策略,并提供二级自定义放行管理(局域网网段/IP-CIDR)"
echo -e "================================================"
echo -e "用法:$0 [选项]
"
echo -e "核心选项:"
echo -e " -?/--help 显示此帮助信息并退出"
echo -e " --version 显示版本号并退出"
echo -e " --silent 静默模式(无交互,使用配置文件/默认值)"
echo -e " --config FILE 指定配置文件路径(默认:/etc/cn-firewall.conf)"
echo -e " --debug 启用调试模式(输出详细日志)"
echo -e "
运维选项:"
echo -e " --backup 仅备份当前防火墙规则"
echo -e " --rollback 回滚到最近的防火墙规则备份"
echo -e " --recovery 直接进入紧急恢复模式(断网时使用)"
echo -e " --fix-ipset 仅修复IPset自启脚本+systemd服务(快速修复)"
echo -e " --status 查看当前防火墙/IPset/自定义放行状态"
echo -e "
示例:"
echo -e " $0 # 开局菜单:自动安装CN IP集 / 自定义放行管理 / 配置更新策略"
echo -e " $0 --silent # 静默模式配置(批量部署)"
echo -e " $0 --fix-ipset # 快速修复IPset自启问题(无需重新配置防火墙)"
echo -e " $0 --backup # 仅备份当前规则"
echo -e " $0 --status # 查看当前状态"
echo -e "
日志文件:${LOG_FILE}"
echo -e "备份目录:${BACKUP_DIR}"
echo -e "================================================
"
exit 0
}
check_root() {
if [ "$(id -u)" -ne 0 ]; then
log_error "必须以root用户执行(sudo ./xxx.sh)"
fi
}
init_env() {
touch "${LOG_FILE}"
chmod 600 "${LOG_FILE}"
mkdir -p "${BACKUP_DIR}"
# 强制创建ipset配置目录(确保存在,避免脚本写入失败)
mkdir -p /etc/sysconfig/ipset
chmod 755 /etc/sysconfig/ipset
log_info "脚本环境初始化完成(版本:${VERSION})"
}
load_config() {
if [ -f "${CONFIG_FILE}" ]; then
log_info "加载配置文件:${CONFIG_FILE}"
# shellcheck source=/dev/null
source "${CONFIG_FILE}"
else
log_warn "配置文件不存在,使用默认值"
# 默认配置
UPDATE_INTERVAL=1
UPDATE_TIME="03:00"
ENABLE_IPV6="false"
ALLOW_SSH="true"
SSH_PORT=22
CUSTOM_LAN_CIDRS=()
CUSTOM_ALLOW_IPS=()
WHITELIST_MODE="cn_only"
fi
}
# 验证配置项,修复CUSTOM_LAN_CIDRS未定义问题
validate_config() {
log_info "开始验证配置项"
# 验证 UPDATE_INTERVAL
if ! validate_interval "${UPDATE_INTERVAL:-0}"; then
log_warn "配置项 UPDATE_INTERVAL (${UPDATE_INTERVAL:-0}) 无效,使用默认值 1"
UPDATE_INTERVAL=1
fi
# 验证 UPDATE_TIME
if ! validate_time "${UPDATE_TIME:-00:00}"; then
log_warn "配置项 UPDATE_TIME (${UPDATE_TIME:-00:00}) 无效,使用默认值 03:00"
UPDATE_TIME="03:00"
fi
# 验证 SSH_PORT
if ! echo "${SSH_PORT:-0}" | grep -E '^[0-9]+$' >/dev/null 2>&1 || [ "${SSH_PORT:-0}" -lt 1 ] || [ "${SSH_PORT:-0}" -gt 65535 ]; then
log_warn "配置项 SSH_PORT (${SSH_PORT:-0}) 无效,使用默认值 22"
SSH_PORT=22
fi
# 验证白名单模式
case "${WHITELIST_MODE:-lan}" in
lan|custom|cn_only)
;;
1)
WHITELIST_MODE="cn_only"
;;
2)
WHITELIST_MODE="custom"
;;
3)
WHITELIST_MODE="cn_only"
;;
*)
log_warn "配置项 WHITELIST_MODE (${WHITELIST_MODE:-}) 无效,使用默认值 lan"
WHITELIST_MODE="cn_only"
;;
esac
# 检查数组是否已定义,未定义则初始化为空数组
if ! declare -p CUSTOM_LAN_CIDRS >/dev/null 2>&1; then
CUSTOM_LAN_CIDRS=()
fi
if ! declare -p CUSTOM_ALLOW_IPS >/dev/null 2>&1; then
CUSTOM_ALLOW_IPS=()
fi
CUSTOM_ALLOW_CIDRS=()
# 验证 CUSTOM_LAN_CIDRS
local valid_custom_lan=()
if [ ${#CUSTOM_LAN_CIDRS[@]} -gt 0 ]; then
for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
if validate_cidr "${cidr}"; then
valid_custom_lan+=("${cidr}")
else
log_warn "配置项 CUSTOM_LAN_CIDRS 中的网段 (${cidr}) 无效,已跳过"
fi
done
fi
CUSTOM_LAN_CIDRS=("${valid_custom_lan[@]}")
# 验证 CUSTOM_ALLOW_IPS,并统一转成 CIDR,便于防火墙规则处理
local valid_custom_allow=()
if [ ${#CUSTOM_ALLOW_IPS[@]} -gt 0 ]; then
for item in "${CUSTOM_ALLOW_IPS[@]}"; do
local normalized=""
normalized=$(normalize_ip_or_cidr "${item}" 2>/dev/null || true)
if [ -n "${normalized}" ]; then
valid_custom_allow+=("${normalized}")
else
log_warn "配置项 CUSTOM_ALLOW_IPS 中的地址 (${item}) 无效,已跳过"
fi
done
fi
if [ ${#valid_custom_allow[@]} -gt 0 ]; then
CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${valid_custom_allow[@]}" | sort -u))
fi
log_info "配置项验证完成"
}
# ===================== 紧急恢复功能 =====================
emergency_recovery() {
if [ "${SILENT_MODE}" = "true" ]; then
log_error "静默模式下不支持紧急恢复操作"
fi
clear
blue "========================================"
blue " 🔥 紧急恢复模式 🔥 "
blue "========================================"
yellow "⚠️ 此功能用于防火墙配置错误导致无法访问时的紧急恢复 ⚠️"
echo ""
# 检测防火墙类型
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
FIREWALL_TYPE="firewalld"
elif command -v iptables >/dev/null 2>&1; then
FIREWALL_TYPE="iptables"
else
log_error "未检测到任何防火墙(firewalld/iptables)"
fi
cyan "当前系统防火墙类型:${FIREWALL_TYPE}"
echo ""
echo "请选择恢复方式:"
echo "1) 临时放行所有入站(重启后失效,安全)"
echo "2) 恢复防火墙默认配置(永久生效,谨慎)"
echo "3) 仅恢复SSH端口访问(保留其他规则)"
echo "4) 退出紧急恢复模式"
echo ""
while true; do
read -p "请输入选择(1-4):" recover_choice
case "${recover_choice}" in
1)
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
firewall-cmd --set-default-zone=public
firewall-cmd --reload
log_info "firewalld已临时切换到public区域(放行所有入站)"
else
iptables -F
iptables -P INPUT ACCEPT
ip6tables -F
ip6tables -P INPUT ACCEPT
log_info "iptables已清空规则并设置默认策略为ACCEPT(放行所有入站)"
fi
yellow "提示:此修改重启后失效,需重新配置规则"
break
;;
2)
read -p "⚠️ 此操作会重置防火墙所有规则,是否确认?(y/N):" confirm_reset
confirm_reset=${confirm_reset:-N}
if [ "${confirm_reset}" = "y" ] || [ "${confirm_reset}" = "Y" ]; then
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
systemctl stop firewalld
rm -rf /etc/firewalld/zones/*
systemctl start firewalld
firewall-cmd --set-default-zone=public
firewall-cmd --reload
log_info "firewalld已恢复默认配置(public区域,放行所有)"
else
iptables -F && iptables -X && iptables -P INPUT ACCEPT && iptables -P OUTPUT ACCEPT && iptables -P FORWARD ACCEPT
ip6tables -F && ip6tables -X && ip6tables -P INPUT ACCEPT && ip6tables -P OUTPUT ACCEPT && ip6tables -P FORWARD ACCEPT
# 保存默认规则
if [ -f /etc/redhat-release ]; then
iptables-save > /etc/sysconfig/iptables
ip6tables-save > /etc/sysconfig/ip6tables
elif [ -f /etc/debian_version ]; then
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
fi
log_info "iptables已恢复默认配置(所有端口放行)"
fi
log_warn "防火墙已恢复默认配置,需重新执行本脚本配置CN IP拦截规则"
else
log_warn "已取消恢复默认配置操作"
fi
break
;;
3)
read -p "请输入SSH端口(默认22):" ssh_port
ssh_port=${ssh_port:-22}
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
firewall-cmd --permanent --add-port="${ssh_port}/tcp"
firewall-cmd --reload
log_info "已放行SSH端口${ssh_port}(firewalld)"
else
iptables -A INPUT -p tcp --dport "${ssh_port}" -j ACCEPT
ip6tables -A INPUT -p tcp --dport "${ssh_port}" -j ACCEPT
if [ -f /etc/redhat-release ]; then
iptables-save > /etc/sysconfig/iptables
elif [ -f /etc/debian_version ]; then
iptables-save > /etc/iptables/rules.v4
fi
log_info "已放行SSH端口${ssh_port}(iptables)"
fi
yellow "提示:仅放行SSH端口,其他规则保持不变"
break
;;
4)
log_info "退出紧急恢复模式"
exit 0
;;
*)
log_error "输入无效,请选择1-4"
;;
esac
done
log_info "紧急恢复操作完成!建议恢复访问后重新检查防火墙规则"
exit 0
}
# ===================== 规则备份/回滚 =====================
backup_firewall_rules() {
log_info "开始备份当前防火墙规则"
local backup_path="${BACKUP_DIR}/$(date +%Y%m%d_%H%M%S)"
mkdir -p "${backup_path}"
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
cp -r /etc/firewalld/zones "${backup_path}/" 2>/dev/null || true
cp -r /etc/firewalld/ipsets "${backup_path}/" 2>/dev/null || true
firewall-cmd --list-all-zones > "${backup_path}/firewalld-all-rules.txt"
else
iptables-save > "${backup_path}/iptables.rules"
ip6tables-save > "${backup_path}/ip6tables.rules"
# 备份ipset配置
ipset save > "${backup_path}/ipset.conf" 2>/dev/null
fi
# 保留最近3个备份,清理旧备份
ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null | tail -n +4 | xargs rm -rf >/dev/null 2>&1
log_info "防火墙规则已备份到:${backup_path}"
export BACKUP_PATH="${backup_path}"
}
rollback_firewall_rules() {
if [ "${SILENT_MODE}" = "true" ]; then
log_error "静默模式下不支持手动回滚操作"
fi
blue "=== 防火墙规则回滚 ==="
local backup_dirs=($(ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null))
if [ ${#backup_dirs[@]} -eq 0 ]; then
log_error "未找到任何规则备份"
return 1
fi
echo "可用的备份版本:"
for i in "${!backup_dirs[@]}"; do
echo "$((i+1))) $(basename "${backup_dirs[$i]}")"
done
echo ""
read -p "请选择要回滚的版本序号(默认1,最新备份):" rollback_idx
rollback_idx=${rollback_idx:-1}
local target_backup="${backup_dirs[$((rollback_idx-1))]}"
log_info "开始回滚到备份版本:$(basename "${target_backup}")"
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
systemctl stop firewalld
cp -r "${target_backup}/zones"/* /etc/firewalld/zones/ 2>/dev/null || true
cp -r "${target_backup}/ipsets"/* /etc/firewalld/ipsets/ 2>/dev/null || true
systemctl start firewalld
firewall-cmd --reload
else
iptables-restore < "${target_backup}/iptables.rules"
ip6tables-restore < "${target_backup}/ip6tables.rules"
# 恢复ipset配置
if [ -f "${target_backup}/ipset.conf" ]; then
ipset restore < "${target_backup}/ipset.conf"
fi
# 持久化
if [ -f /etc/redhat-release ]; then
iptables-save > /etc/sysconfig/iptables
ip6tables-save > /etc/sysconfig/ip6tables
elif [ -f /etc/debian_version ]; then
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
fi
fi
log_info "已回滚防火墙规则到:$(basename "${target_backup}")"
}
attempt_rollback() {
log_warn "配置后网络连通性失败,尝试自动回滚规则..."
# 在子 Shell 中执行 rollback,避免其 exit 影响主流程
if ( rollback_firewall_rules 2>/dev/null ); then
log_info "自动回滚成功!"
log_error "回滚完成,终止主流程。请检查网络和防火墙配置。"
else
log_error "自动回滚失败!请手动执行 $0 --recovery"
fi
}
# ===================== 网络连通性检测(兼容国内外DNS) =====================
check_network_connectivity() {
log_info "开始网络连通性预检(兼容国内外主机)"
local gateway=$(ip route show default | awk '/default/ {print $3}' | head -1)
# 多组DNS,国内优先 + 国际兜底
local dns_list=(
"223.5.5.5" # 阿里云DNS(国内优选)
"119.29.29.29" # 腾讯云DNS(国内优选)
"8.8.8.8" # Google DNS(国际兜底)
"1.1.1.1" # Cloudflare DNS(国际兜底)
"208.67.222.222" # OpenDNS(国际兜底)
)
local usable_dns=""
local ssh_port_config=$(grep -E '^Port ' /etc/ssh/sshd_config | awk '{print $2}' | head -1)
local ssh_port=${ssh_port_config:-22}
log_debug "检测到 SSH 配置端口: ${ssh_port}"
# 检测网关
if [ -n "${gateway}" ] && ping -c 1 -W 2 "${gateway}" >/dev/null 2>&1; then
log_info "网关(${gateway})可达"
else
log_warn "网关不可达,继续配置可能导致断网"
if [ "${SILENT_MODE}" = "false" ]; then
read -p "是否继续?(y/N):" continue_flag
continue_flag=${continue_flag:-N}
if [ "${continue_flag}" != "y" ] && [ "${continue_flag}" != "Y" ]; then
log_info "用户终止配置操作"
exit 0
fi
fi
fi
# 优先使用自定义DNS,否则自动遍历DNS列表
if [ -n "${CUSTOM_DNS}" ]; then
log_info "使用用户自定义DNS(${CUSTOM_DNS})进行检测"
usable_dns="${CUSTOM_DNS}"
# 验证自定义DNS是否可用
if ! ping -c 1 -W 2 "${usable_dns}" >/dev/null 2>&1; then
log_error "用户自定义DNS(${CUSTOM_DNS})不可达,终止配置"
fi
else
# 自动探测可用DNS
log_info "正在探测可用DNS服务器(国内优先,国际兜底)"
for dns in "${dns_list[@]}"; do
if ping -c 1 -W 2 "${dns}" >/dev/null 2>&1; then
usable_dns="${dns}"
log_info "找到可用DNS(${dns}),使用该DNS进行连通性检测"
break
fi
log_debug "DNS(${dns})不可达,尝试下一个"
done
# 若没有可用DNS,直接报错终止
if [ -z "${usable_dns}" ]; then
log_error "所有预设DNS服务器均不可达,无法完成网络预检,终止配置"
fi
fi
# 使用可用DNS进行检测
log_info "DNS(${usable_dns})可达"
# 检测SSH端口
if ss -tuln | grep -E ":${ssh_port}\s" >/dev/null 2>&1; then
log_info "SSH端口(${ssh_port})已监听"
else
log_warn "SSH端口(${ssh_port})未监听,需确认SSH服务状态"
fi
}
verify_network_after_config() {
log_info "开始配置后网络连通性验证(兼容国内外主机)"
# 同样使用多组DNS兜底
local dns_list=(
"223.5.5.5" # 阿里云DNS(国内)
"119.29.29.29" # 腾讯云DNS(国内)
"8.8.8.8" # Google DNS(国际)
"1.1.1.1" # Cloudflare DNS(国际)
)
local test_success=0
# 遍历DNS,只要有一个能ping通即认为网络正常
for test_ip in "${dns_list[@]}"; do
if ping -c 1 -W 2 "${test_ip}" >/dev/null 2>&1; then
log_info "配置后网络连通性正常(通过DNS ${test_ip} 验证)"
test_success=1
break
fi
log_debug "配置后验证:DNS ${test_ip} 不可达,尝试下一个"
done
# 所有DNS都不可达,触发回滚
if [ ${test_success} -eq 0 ]; then
attempt_rollback
exit 1
fi
}
# ===================== 核心:精准计算内网网段(过滤公网IP) =====================
# IP转整数
ip_to_int() {
local ip="$1"
local a b c d
IFS=. read -r a b c d <<< "${ip}"
echo $((a * 256**3 + b * 256**2 + c * 256 + d))
}
# 整数转IP
int_to_ip() {
local int="$1"
local d=$((int % 256))
local c=$(((int / 256) % 256))
local b=$(((int / 256**2) % 256))
local a=$(((int / 256**3) % 256))
echo "${a}.${b}.${c}.${d}"
}
# 根据IP和掩码计算精准网段
calculate_network_cidr() {
local ip="$1"
local mask="$2"
# 处理掩码格式(支持24或255.255.255.0)
if echo "${mask}" | grep -E '\.' >/dev/null 2>&1; then
# 掩码是点分十进制(如255.255.255.0),转成前缀长度
local mask_int=$(ip_to_int "${mask}")
local bin_mask=$(echo "obase=2; ${mask_int}" | bc)
mask=$(echo "${bin_mask}" | tr -d '0' | wc -c)
mask=$((mask - 1))
fi
# 计算网络地址
local ip_int=$(ip_to_int "${ip}")
local mask_int=$((0xFFFFFFFF << (32 - mask)))
local network_int=$((ip_int & mask_int))
local network_ip=$(int_to_ip "${network_int}")
echo "${network_ip}/${mask}"
}
# 精准识别内网IP(仅RFC1918私有IP)
detect_lan_cidrs() {
log_info "开始精准识别当前网卡的内网网段(仅RFC1918私有IP)"
# 安全初始化数组
DETECTED_LAN_CIDRS=()
# 步骤1:找到活跃网卡(有默认路由的网卡)
local active_iface=$(ip route show default | awk '/default/ {print $5}' | head -1)
if [ -z "${active_iface}" ]; then
# 无默认路由,取第一个非lo网卡
active_iface=$(ip link show | awk -F': ' '/^[0-9]+:/ && !/lo/ {print $2; exit}' | cut -d'@' -f1)
log_info "未找到默认路由网卡,使用第一个非回环网卡:${active_iface}"
else
log_info "识别到活跃网卡(默认路由):${active_iface}"
fi
local found=0
# 提取活跃网卡的内网IP和掩码(仅过滤RFC1918私有IP)
local ip_cidrs_active=$(ip addr show "${active_iface}" 2>/dev/null | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
for ip_cidr in ${ip_cidrs_active}; do
local ip=$(echo "${ip_cidr}" | cut -d/ -f1)
# 判断IP是否属于RFC1918
if echo "$ip" | grep -E '^(192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|10\.)' >/dev/null 2>&1; then
local mask=$(echo "${ip_cidr}" | cut -d/ -f2)
# 计算精准内网网段
local network_cidr=$(calculate_network_cidr "${ip}" "${mask}")
DETECTED_LAN_CIDRS+=("${network_cidr}")
log_info "活跃网卡${active_iface}的内网IP段:${ip_cidr} → 精准内网网段:${network_cidr}"
found=1
fi
done
# 兜底:识别所有网卡的内网IP(仅RFC1918)
log_info "开始扫描所有网卡以发现 RFC1918 私有IP网段"
local all_ips_cidr=$(ip addr show 2>/dev/null | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}')
for ip_cidr in ${all_ips_cidr}; do
local ip=$(echo "${ip_cidr}" | cut -d/ -f1)
[ "$ip" = "127.0.0.1" ] && continue
if echo "$ip" | grep -E '^(192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|10\.)' >/dev/null 2>&1; then
local mask=$(echo "${ip_cidr}" | cut -d/ -f2)
local network_cidr=$(calculate_network_cidr "$ip" "$mask")
# 避免重复添加
if ! printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | grep -Fxq "${network_cidr}" 2>/dev/null; then
DETECTED_LAN_CIDRS+=("${network_cidr}")
log_info "识别到内网IP段:${ip_cidr} → 精准内网网段:${network_cidr}"
found=1
else
log_debug "内网网段 ${network_cidr} 已存在,跳过添加"
fi
fi
done
if [ $found -eq 0 ]; then
log_warn "未扫描到任何 RFC1918 私有IP网段"
log_info "服务器可能直接使用公网IP。"
fi
# 去重
DETECTED_LAN_CIDRS=($(printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | sort -u))
# 禁用IPv6时过滤
if [ "${ENABLE_IPV6}" = "false" ]; then
DETECTED_LAN_CIDRS=($(printf "%s\n" "${DETECTED_LAN_CIDRS[@]}" | grep -E '\.'))
fi
log_info "自动发现内网网段完成:${DETECTED_LAN_CIDRS[*]:-}"
}
validate_cidr() {
local cidr="$1"
local ip=$(echo "${cidr}" | cut -d/ -f1)
local mask=$(echo "${cidr}" | cut -d/ -f2)
if ! echo "${mask}" | grep -E '^[0-9]+$' >/dev/null 2>&1; then
return 1
fi
# IPv4校验
if echo "${ip}" | grep -E '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' >/dev/null 2>&1; then
[ "${mask}" -ge 0 ] && [ "${mask}" -le 32 ] && return 0
# IPv6校验
elif echo "${ip}" | grep -E '^([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}$' >/dev/null 2>&1 && [ "${ENABLE_IPV6}" = "true" ]; then
[ "${mask}" -ge 0 ] && [ "${mask}" -le 128 ] && return 0
fi
return 1
}
# 将单个IP或CIDR统一转换为CIDR格式:IPv4 -> /32,IPv6 -> /128
normalize_ip_or_cidr() {
local item="$1"
item=$(echo "${item}" | tr -d '[:space:]')
[ -z "${item}" ] && return 1
if echo "${item}" | grep -q '/'; then
if validate_cidr "${item}"; then
echo "${item}"
return 0
fi
return 1
fi
# IPv4 单IP
if echo "${item}" | grep -E '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' >/dev/null 2>&1; then
echo "${item}/32"
return 0
fi
# IPv6 单IP:这里允许压缩格式,最终交给 ip6tables/firewalld 处理
if echo "${item}" | grep -E '^[0-9a-fA-F:]+$' >/dev/null 2>&1 && echo "${item}" | grep -q ':'; then
if [ "${ENABLE_IPV6}" = "true" ]; then
echo "${item}/128"
return 0
else
log_warn "IPv6地址 ${item} 已跳过,因为当前未启用IPv6规则"
return 1
fi
fi
return 1
}
select_start_action() {
if [ "${SILENT_MODE}" = "true" ]; then
START_ACTION="install"
WHITELIST_MODE="${WHITELIST_MODE:-cn_only}"
log_info "静默模式执行自动安装,白名单模式:${WHITELIST_MODE},更新策略使用配置文件/默认值"
return 0
fi
while true; do
blue "=== CN Firewall 开局菜单 ==="
echo "请选择操作:"
echo " 1) 自动安装:放行中国IP集合(原脚本安装/交互式安装流程)"
echo " 2) 自定义放行:查看已有、增加或修改放行白名单"
echo " 3) 配置IP集自动更新策略(更新频率/更新时间)"
echo " 0) 退出"
echo ""
echo "当前更新策略:每${UPDATE_INTERVAL}天 ${UPDATE_TIME}"
echo "说明:SSH端口仍会按配置单独放行,避免远程管理被锁死。"
echo ""
read -p "请输入选择(0-3,默认1):" start_choice
start_choice=${start_choice:-1}
case "${start_choice}" in
1)
START_ACTION="install"
WHITELIST_MODE="cn_only"
log_info "已选择:自动安装,仅放行中国IP集合 + SSH"
break
;;
2)
START_ACTION="custom_allow"
WHITELIST_MODE="custom"
log_info "已选择:自定义放行管理"
break
;;
3)
configure_update_strategy
save_runtime_config
echo ""
read -p "更新策略已保存,按回车返回开局菜单..." _
;;
0)
log_info "用户退出开局菜单"
exit 0
;;
*)
yellow "输入无效,请输入 0-3"
;;
esac
done
}
reset_to_cn_only_allow() {
LAN_CIDRS=("127.0.0.1/32")
[ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
CUSTOM_ALLOW_CIDRS=()
log_info "自动安装模式:仅放行中国IP集合、回环地址与SSH端口"
}
print_allow_lists() {
green "当前配置放行列表:"
echo " 内网/回环放行:${LAN_CIDRS[*]:-无}"
echo " 自定义IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]:-无}"
echo ""
}
show_runtime_allow_rules() {
green "当前运行中规则摘录:"
if [ "${FIREWALL_TYPE}" = "iptables" ]; then
echo "IPv4链 ${IPTABLES_CHAIN}:"
iptables -S "${IPTABLES_CHAIN}" 2>/dev/null || echo " 未发现IPv4独立链"
if [ "${ENABLE_IPV6}" = "true" ]; then
echo "IPv6链 ${IP6TABLES_CHAIN}:"
ip6tables -S "${IP6TABLES_CHAIN}" 2>/dev/null || echo " 未发现IPv6独立链"
fi
elif [ "${FIREWALL_TYPE}" = "firewalld" ]; then
firewall-cmd --zone=drop --list-rich-rules 2>/dev/null || echo " 未能读取firewalld rich rules"
else
echo " 防火墙类型未知,无法读取运行规则"
fi
echo ""
}
save_runtime_config() {
mkdir -p "$(dirname "${CONFIG_FILE}")"
cat > "${CONFIG_FILE}" <<EOF
# CN Firewall 配置文件,由 ${VERSION} 自动生成/更新
UPDATE_INTERVAL=${UPDATE_INTERVAL}
UPDATE_TIME="${UPDATE_TIME}"
ENABLE_IPV6="${ENABLE_IPV6}"
ALLOW_SSH="${ALLOW_SSH}"
SSH_PORT=${SSH_PORT}
WHITELIST_MODE="${WHITELIST_MODE}"
CUSTOM_DNS="${CUSTOM_DNS}"
CUSTOM_LAN_CIDRS=(
EOF
for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
echo " \"${cidr}\"" >> "${CONFIG_FILE}"
done
cat >> "${CONFIG_FILE}" <<EOF
)
CUSTOM_ALLOW_IPS=(
EOF
for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
echo " \"${cidr}\"" >> "${CONFIG_FILE}"
done
cat >> "${CONFIG_FILE}" <<EOF
)
EOF
chmod 600 "${CONFIG_FILE}"
log_info "配置已保存到:${CONFIG_FILE}"
}
custom_allow_menu() {
if [ "${SILENT_MODE}" = "true" ]; then
# 静默模式按配置文件内容应用自定义放行
configure_lan_allow
configure_custom_allow_ips
return 0
fi
while true; do
blue "=== 自定义放行管理 ==="
print_allow_lists
echo "请选择二级功能:"
echo " 1) 发现局域网网段放行"
echo " 2) 自定义IP/CIDR放行"
echo " 3) 查看当前运行规则"
echo " 4) 清空自定义放行并仅保留中国IP集合"
echo " 0) 完成并应用规则"
echo ""
read -p "请输入选择(0-4):" custom_choice
case "${custom_choice}" in
1)
manage_lan_allow_menu
;;
2)
manage_custom_ip_allow_menu
;;
3)
show_runtime_allow_rules
read -p "按回车继续..." _
;;
4)
read -p "确认清空局域网和自定义IP放行?(y/N):" confirm_clear
confirm_clear=${confirm_clear:-N}
if [ "${confirm_clear}" = "y" ] || [ "${confirm_clear}" = "Y" ]; then
LAN_CIDRS=("127.0.0.1/32")
[ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
CUSTOM_LAN_CIDRS=()
CUSTOM_ALLOW_CIDRS=()
CUSTOM_ALLOW_IPS=()
WHITELIST_MODE="cn_only"
log_info "已清空自定义放行,保留中国IP集合、回环与SSH"
fi
;;
0)
WHITELIST_MODE="custom"
save_runtime_config
log_info "自定义放行配置完成,准备应用防火墙规则"
break
;;
*)
yellow "输入无效,请输入 0-4"
;;
esac
done
}
manage_lan_allow_menu() {
detect_lan_cidrs
blue "=== 发现局域网网段放行 ==="
echo "当前内网/回环放行:${LAN_CIDRS[*]:-无}"
echo ""
if [ ${#DETECTED_LAN_CIDRS[@]} -gt 0 ]; then
echo "自动发现的局域网网段:"
for cidr in "${DETECTED_LAN_CIDRS[@]}"; do
echo " - ${cidr}"
done
else
yellow "未自动发现RFC1918局域网网段。"
fi
echo ""
echo "操作:"
echo " 1) 追加自动发现的局域网网段"
echo " 2) 覆盖为自动发现的局域网网段"
echo " 3) 手动增加局域网CIDR"
echo " 4) 清空局域网放行,仅保留回环"
echo " 0) 返回"
read -p "请输入选择(0-4):" lan_action
case "${lan_action}" in
1)
LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
CUSTOM_LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
;;
2)
LAN_CIDRS=("127.0.0.1/32")
[ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
CUSTOM_LAN_CIDRS=("${DETECTED_LAN_CIDRS[@]}")
;;
3)
while true; do
read -p "请输入局域网CIDR(示例:192.168.1.0/24,留空结束):" custom_cidr
[ -z "${custom_cidr}" ] && break
if validate_cidr "${custom_cidr}"; then
LAN_CIDRS+=("${custom_cidr}")
CUSTOM_LAN_CIDRS+=("${custom_cidr}")
log_info "已添加局域网CIDR:${custom_cidr}"
else
yellow "CIDR格式无效,已跳过:${custom_cidr}"
fi
done
;;
4)
LAN_CIDRS=("127.0.0.1/32")
[ "${ENABLE_IPV6}" = "true" ] && LAN_CIDRS+=("::1/128")
CUSTOM_LAN_CIDRS=()
;;
0)
return 0
;;
*)
yellow "输入无效"
;;
esac
LAN_CIDRS=($(printf "%s\n" "${LAN_CIDRS[@]}" | sed '/^$/d' | sort -u))
CUSTOM_LAN_CIDRS=($(printf "%s\n" "${CUSTOM_LAN_CIDRS[@]}" | sed '/^$/d' | sort -u))
log_info "当前内网放行列表:${LAN_CIDRS[*]:-无}"
}
manage_custom_ip_allow_menu() {
blue "=== 自定义IP/CIDR放行 ==="
echo "当前自定义IP/CIDR放行:${CUSTOM_ALLOW_CIDRS[*]:-无}"
echo ""
echo "操作:"
echo " 1) 追加自定义IP/CIDR"
echo " 2) 覆盖修改自定义IP/CIDR列表"
echo " 3) 清空自定义IP/CIDR列表"
echo " 0) 返回"
read -p "请输入选择(0-3):" ip_action
case "${ip_action}" in
1)
add_custom_ip_items "append"
;;
2)
CUSTOM_ALLOW_CIDRS=()
CUSTOM_ALLOW_IPS=()
add_custom_ip_items "replace"
;;
3)
CUSTOM_ALLOW_CIDRS=()
CUSTOM_ALLOW_IPS=()
log_info "已清空自定义IP/CIDR放行列表"
;;
0)
return 0
;;
*)
yellow "输入无效"
;;
esac
if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | sed '/^$/d' | sort -u))
fi
CUSTOM_ALLOW_IPS=("${CUSTOM_ALLOW_CIDRS[@]}")
log_info "当前自定义IP/CIDR放行列表:${CUSTOM_ALLOW_CIDRS[*]:-无}"
}
add_custom_ip_items() {
local mode="${1:-append}"
while true; do
read -p "请输入要放行的IP/CIDR(示例:8.8.8.8、1.2.3.0/24,留空结束):" allow_item
[ -z "${allow_item}" ] && break
local normalized=""
normalized=$(normalize_ip_or_cidr "${allow_item}" 2>/dev/null || true)
if [ -n "${normalized}" ]; then
if printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | grep -Fxq "${normalized}" 2>/dev/null; then
log_warn "自定义放行地址 ${normalized} 已存在,无需重复添加"
else
CUSTOM_ALLOW_CIDRS+=("${normalized}")
CUSTOM_ALLOW_IPS+=("${normalized}")
log_info "已添加自定义放行地址:${normalized}"
fi
else
yellow "地址格式无效或IPv6未启用,已跳过:${allow_item}"
fi
done
[ "${mode}" = "replace" ] && log_info "自定义IP/CIDR列表已覆盖修改"
}
apply_selected_action() {
case "${START_ACTION}" in
install)
reset_to_cn_only_allow
;;
custom_allow)
custom_allow_menu
;;
*)
log_warn "未知开局动作 ${START_ACTION},默认执行自动安装"
reset_to_cn_only_allow
;;
esac
}
configure_lan_allow() {
detect_lan_cidrs # 调用检测函数
if [ "${SILENT_MODE}" = "false" ]; then
blue "=== 配置内网网段放行规则 ==="
echo "当前已放行的网段:"
for cidr in "${LAN_CIDRS[@]}"; do
echo " - ${cidr}"
done
echo ""
# 展示自动发现的网段
local array_len=${#DETECTED_LAN_CIDRS[@]}
if [ $array_len -gt 0 ]; then
echo "自动发现的内网网段:"
for cidr in "${DETECTED_LAN_CIDRS[@]}"; do
echo " - ${cidr}"
done
read -p "是否将自动发现的内网网段添加到放行列表?(y/n,默认y):" add_detected
add_detected=${add_detected:-y}
if [ "${add_detected}" = "y" ] || [ "${add_detected}" = "Y" ]; then
LAN_CIDRS+=("${DETECTED_LAN_CIDRS[@]}")
log_info "已添加自动发现的内网网段:${DETECTED_LAN_CIDRS[*]:-}"
else
log_warn "用户选择不添加自动发现的内网网段"
fi
echo ""
else
log_warn "未自动发现任何内网网段"
fi
read -p "是否需要添加自定义内网网段?(y/n,默认n):" add_custom_lan
add_custom_lan=${add_custom_lan:-n}
if [ "${add_custom_lan}" = "y" ] || [ "${add_custom_lan}" = "Y" ]; then
while true; do
read -p "请输入自定义CIDR内网网段(示例:192.168.1.0/24 或 10.0.0.0/8):" custom_cidr
if validate_cidr "${custom_cidr}"; then
if printf "%s\n" "${LAN_CIDRS[@]}" | grep -x "${custom_cidr}" >/dev/null 2>&1; then
log_warn "内网网段${custom_cidr}已存在,无需重复添加"
else
LAN_CIDRS+=("${custom_cidr}")
log_info "已添加自定义内网网段:${custom_cidr}"
fi
read -p "是否继续添加?(y/n,默认n):" continue_add
continue_add=${continue_add:-n}
[ "${continue_add}" != "y" ] && break
else
log_error "CIDR格式无效!示例:192.168.1.0/24(IPv4)或 fd00::/8(IPv6)"
fi
done
fi
fi
# 静默模式加载自定义网段
if [ "${SILENT_MODE}" = "true" ]; then
if [ ${#CUSTOM_LAN_CIDRS[@]} -gt 0 ]; then
for cidr in "${CUSTOM_LAN_CIDRS[@]}"; do
if validate_cidr "${cidr}"; then
LAN_CIDRS+=("${cidr}")
log_info "静默模式添加自定义内网网段:${cidr}"
else
log_error "自定义内网网段${cidr}格式无效,已跳过"
fi
done
fi
fi
# 最终去重
LAN_CIDRS=($(printf "%s\n" "${LAN_CIDRS[@]}" | sort -u))
log_info "最终放行的网段列表:${LAN_CIDRS[*]:-}"
}
configure_custom_allow_ips() {
if [ "${SILENT_MODE}" = "false" ]; then
blue "=== 配置自定义放行IP/CIDR ==="
if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
echo "配置文件中已有自定义放行地址:"
for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
echo " - ${cidr}"
done
echo ""
fi
read -p "是否需要添加自定义放行IP或CIDR?(y/n,默认n):" add_custom_allow
add_custom_allow=${add_custom_allow:-n}
if [ "${add_custom_allow}" = "y" ] || [ "${add_custom_allow}" = "Y" ]; then
while true; do
read -p "请输入要放行的IP/CIDR(示例:8.8.8.8、1.2.3.0/24、240e::1):" allow_item
local normalized=""
normalized=$(normalize_ip_or_cidr "${allow_item}" 2>/dev/null || true)
if [ -n "${normalized}" ]; then
if printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | grep -Fxq "${normalized}" 2>/dev/null; then
log_warn "自定义放行地址 ${normalized} 已存在,无需重复添加"
else
CUSTOM_ALLOW_CIDRS+=("${normalized}")
log_info "已添加自定义放行地址:${normalized}"
fi
read -p "是否继续添加?(y/n,默认n):" continue_add_allow
continue_add_allow=${continue_add_allow:-n}
[ "${continue_add_allow}" != "y" ] && [ "${continue_add_allow}" != "Y" ] && break
else
log_warn "地址格式无效或IPv6未启用,请重新输入"
fi
done
fi
fi
if [ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ]; then
CUSTOM_ALLOW_CIDRS=($(printf "%s\n" "${CUSTOM_ALLOW_CIDRS[@]}" | sort -u))
log_info "最终自定义放行地址:${CUSTOM_ALLOW_CIDRS[*]}"
else
log_info "未配置自定义放行IP/CIDR"
fi
}
configure_ipv6_switch() {
if [ "${SILENT_MODE}" = "false" ]; then
blue "=== 配置IPv6规则开关 ==="
read -p "是否启用IPv6相关规则?(y/n,默认n):" enable_ipv6
enable_ipv6=${enable_ipv6:-n}
ENABLE_IPV6=$([ "${enable_ipv6}" = "y" ] && echo "true" || echo "false")
fi
if [ "${ENABLE_IPV6}" = "true" ]; then
log_info "已启用IPv6规则"
else
log_info "已禁用IPv6规则,仅配置IPv4"
fi
}
# ===================== 依赖安装(日志优化版)=====================
install_dependencies() {
log_info "开始检测系统依赖"
if [ -f /etc/os-release ]; then
source /etc/os-release
OS_ID="${ID}"; OS_VERSION_ID="${VERSION_ID}"
else
if [ -f /etc/redhat-release ]; then OS_ID="centos"; fi
fi
log_info "识别到系统:${OS_ID} ${OS_VERSION_ID:-unknown}"
case "${OS_ID}" in
rhel|centos|fedora)
if [ "${OS_VERSION_ID}" = "7" ]; then
INSTALL_CMD="yum install -y --nogpgcheck --skip-broken"
else
INSTALL_CMD="dnf install -y --nogpgcheck --skip-broken"
fi
NET_TOOL="iproute"
# 安装iptables-services(CentOS持久化工具)
log_info "预安装CentOS专属iptables持久化工具:iptables-services"
${INSTALL_CMD} iptables-services >/dev/null 2>&1
;;
debian|ubuntu)
apt-get update -y >/dev/null 2>&1
INSTALL_CMD="apt install -y --no-install-recommends --fix-missing"
NET_TOOL="iproute2"
;;
alpine)
INSTALL_CMD="apk add --no-cache --allow-untrusted"
NET_TOOL="iproute2"
;;
suse|opensuse-leap|opensuse-tumbleweed)
INSTALL_CMD="zypper install -y --no-gpg-checks --non-interactive"
NET_TOOL="iproute2"
;;
arch|manjaro)
INSTALL_CMD="pacman -S --noconfirm --needed"
NET_TOOL="iproute2"
;;
*)
log_warn "未知系统,尝试通用安装命令(yum/apt)"
if command -v yum >/dev/null 2>&1; then
INSTALL_CMD="yum install -y --nogpgcheck"
NET_TOOL="iproute2"
elif command -v apt >/dev/null 2>&1; then
INSTALL_CMD="apt install -y"
NET_TOOL="iproute2"
else
log_error "不支持的系统,手动安装依赖后重试"
exit 1
fi
;;
esac
DEPS_NEEDED=("$NET_TOOL" "bc" "ipset" "wget" "iptables")
[ "${OS_ID:-}" = "centos" ] && DEPS_NEEDED+=("firewalld")
# 检查是否全部已安装
local missing_deps=()
# 定义包名到关键命令的映射
declare -A PKG_TO_CMD=(
["iproute2"]="ip"
["iproute"]="ip"
["bc"]="bc"
["ipset"]="ipset"
["wget"]="wget"
["iptables"]="iptables"
["firewalld"]="firewall-cmd"
["iptables-services"]="iptables"
)
for dep in "${DEPS_NEEDED[@]}"; do
pkg_name="${dep%%:*}"
cmd_name="${PKG_TO_CMD[$pkg_name]:-$pkg_name}"
if ! command -v "$cmd_name" >/dev/null 2>&1; then
missing_deps+=("$dep")
else
log_info "依赖${pkg_name}已安装(命令: $cmd_name),跳过"
fi
done
if [ ${#missing_deps[@]} -eq 0 ]; then
log_info "依赖检测已完成,所有组件均已安装"
return 0
fi
log_info "正在安装缺失依赖:${missing_deps[*]}"
# 安装函数(带兜底)
install_with_fallback() {
local dep="$1"
local fallback="${2:-}"
local pkg_name="${dep%%:*}"
local cmd_name="${PKG_TO_CMD[$pkg_name]:-$pkg_name}"
if command -v "$cmd_name" >/dev/null 2>&1; then
log_info "依赖${dep}已安装,跳过"
return 0
fi
# 安装主包
log_info "安装依赖:${dep}"
if ${INSTALL_CMD} "${dep}"; then
log_info "依赖${dep}安装成功"
return 0
else
log_warn "主包${dep}安装失败,准备尝试备选包或输出修复建议"
fi
# 尝试备选包
if [ -n "${fallback}" ]; then
log_warn "尝试安配备选包:${fallback}"
if ${INSTALL_CMD} "${fallback}"; then
log_info "备选包${fallback}安装成功"
return 0
else
log_error "备选包${fallback}安装失败!"
fi
fi
# 按系统输出修复建议
case "${OS_ID}" in
debian|ubuntu)
log_error "依赖${dep}安装失败!修复建议:
1. apt --fix-broken install -y
2. apt clean && apt autoclean
3. apt install -y ${dep}"
;;
rhel|centos|fedora)
local pkg_cmd=$([ "${OS_VERSION_ID}" -ge 8 ] && echo "dnf" || echo "yum")
log_error "依赖${dep}安装失败!修复建议:
1. ${pkg_cmd} clean all && ${pkg_cmd} makecache
2. ${pkg_cmd} install -y ${dep}"
;;
*)
log_error "依赖${dep}安装失败,请手动安装后重试"
;;
esac
exit 1
}
for dep in "${missing_deps[@]}"; do
local fallback=""
case "$dep" in
iproute2|iproute) fallback="iproute";;
bc) fallback="bc-bin";;
ipset) fallback="iptables-ipset";;
wget) fallback="wget-minimal";;
iptables) fallback="iptables-services";;
firewalld) fallback="firewalld-services";;
iptables-services) fallback="iptables";;
esac
install_with_fallback "${dep}" "${fallback}"
done
# 验证关键依赖
local critical_deps=("ip" "bc" "ipset" "wget" "iptables")
local missing_critical=()
for dep in "${critical_deps[@]}"; do
if ! command -v "${dep}" >/dev/null 2>&1; then
missing_critical+=("${dep}")
fi
done
if [ ${#missing_critical[@]} -gt 0 ]; then
log_error "关键依赖缺失:${missing_critical[*]}!请手动安装后重试"
fi
log_info "所有依赖安装完成(全系统兼容模式)"
}
# ===================== 防火墙配置 =====================
detect_firewall() {
log_info "开始检测系统防火墙类型"
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
FIREWALL_TYPE="firewalld"
log_info "检测到firewalld已启用"
elif command -v iptables >/dev/null 2>&1; then
FIREWALL_TYPE="iptables"
log_info "检测到iptables已安装"
# 启用iptables-services服务(CentOS持久化)
systemctl enable --now iptables >/dev/null 2>&1
[ "${ENABLE_IPV6}" = "true" ] && systemctl enable --now ip6tables >/dev/null 2>&1
else
log_warn "未检测到任何防火墙,开始自动安装"
if [ "${OS_ID:-rhel}" = "rhel" ] || [ "${OS_ID:-centos}" = "centos" ]; then
${INSTALL_CMD} firewalld >/dev/null 2>&1
systemctl enable --now firewalld >/dev/null 2>&1
FIREWALL_TYPE="firewalld"
else
${INSTALL_CMD} iptables >/dev/null 2>&1
FIREWALL_TYPE="iptables"
systemctl enable --now iptables >/dev/null 2>&1
fi
log_info "已安装并启用${FIREWALL_TYPE}"
fi
}
# 检测系统初始化类型(chkconfig/systemd)
detect_init_system() {
if command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then
echo "systemd"
elif command -v chkconfig >/dev/null 2>&1 && [ -f /etc/init.d/functions ]; then
echo "chkconfig"
else
echo "unknown"
fi
}
# 核心修复:强制校验并重建ipset-init脚本
force_rebuild_ipset_init_script() {
log_info "【修复步骤】开始校验并重建ipset-init脚本(解决文件缺失问题)"
# 1. 强制删除无效脚本(若存在但不可执行)
if [ -f "${IPSET_INIT_SCRIPT}" ] && [ ! -x "${IPSET_INIT_SCRIPT}" ]; then
rm -f "${IPSET_INIT_SCRIPT}"
log_warn "发现无效ipset-init脚本(无执行权限),已删除"
fi
# 2. 重建脚本(无论是否存在,强制覆盖,确保完整性)
# 先创建父目录,避免No such file or directory错误
mkdir -p $(dirname "${IPSET_INIT_SCRIPT}")
cat > "${IPSET_INIT_SCRIPT}" << 'EOF'
#!/bin/bash
# chkconfig: 2345 10 90
# description: IPset auto load configuration on boot
IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"
case "$1" in
start)
# 加载 IPset 配置
if [ -f "${IPSET_CONF}" ]; then
ipset restore < "${IPSET_CONF}"
echo "IPset configuration loaded successfully"
else
echo "IPset config file not found"
fi
;;
stop)
# 保存当前 IPset 配置
mkdir -p $(dirname "${IPSET_CONF}")
ipset save > "${IPSET_CONF}"
echo "IPset configuration saved successfully"
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit 0
EOF
# 3. 强制赋予执行权限(755,确保所有用户可执行,避免权限不足)
chmod 755 "${IPSET_INIT_SCRIPT}"
# 4. 验证脚本存在性和可执行性
if [ -f "${IPSET_INIT_SCRIPT}" ] && [ -x "${IPSET_INIT_SCRIPT}" ]; then
log_info "ipset-init脚本重建成功,路径:${IPSET_INIT_SCRIPT},权限:755"
else
log_error "ipset-init脚本重建失败!请手动创建该文件"
fi
}
create_ipset_script() {
log_info "开始创建IP段更新脚本"
# 创建父目录,避免No such file or directory错误
mkdir -p $(dirname "${SCRIPT_PATH}")
cat > "${SCRIPT_PATH}" << 'EOF'
#!/bin/bash
set -eu
# 配置项
IPSET_IPV4="cn_ipv4"
IPSET_IPV6="cn_ipv6"
IPSET_IPV4_TEMP="cn_ipv4_temp"
IPSET_IPV6_TEMP="cn_ipv6_temp"
TMP_FILE="/tmp/apnic-cn-ips.tmp"
RETRY_COUNT=3
RETRY_INTERVAL=5
LOG_FILE="/var/log/cn-firewall.log"
ENABLE_IPV6="${ENABLE_IPV6:-false}"
IPSET_CONF="/etc/sysconfig/ipset/ipset.conf"
# 日志函数
log_info() {
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [IPSET] ${msg}" >> "${LOG_FILE}"
echo "[${timestamp}] [IPSET] ${msg}" >&2
}
log_error() {
local msg="$1"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[${timestamp}] [IPSET] ${msg}" >> "${LOG_FILE}"
echo "[${timestamp}] [IPSET] ${msg}" >&2
exit 1
}
# 下载函数(带重试)
download_apnic_data() {
local retry=0
local downloaded=false
while [ ${retry} -lt ${RETRY_COUNT} ]; do
if wget --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
--header="Accept: text/plain" \
-O- "http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest" > "${TMP_FILE}"; then
log_info "APNIC IP段数据下载成功"
downloaded=true
break
fi
retry=$((retry+1))
log_info "下载失败,${RETRY_INTERVAL}秒后重试(${retry}/${RETRY_COUNT})"
sleep ${RETRY_INTERVAL}
done
if [ "$downloaded" = false ]; then
log_error "下载失败,已重试${RETRY_COUNT}次"
fi
if [ ! -s "${TMP_FILE}" ]; then
log_error "下载的文件为空"
fi
}
# 主逻辑
log_info "开始更新CN IP段数据"
download_apnic_data
# --- IPv4 处理 ---
if ! ipset list "${IPSET_IPV4}" -n >/dev/null 2>&1; then
log_info "目标IPv4 ipset (${IPSET_IPV4}) 不存在,正在创建..."
ipset create "${IPSET_IPV4}" hash:net family inet
fi
# 清理临时ipset
ipset destroy "${IPSET_IPV4_TEMP}" 2>/dev/null || true
# 创建临时ipset
ipset create "${IPSET_IPV4_TEMP}" hash:net family inet
# 批量添加IPv4
temp_commands_v4="/tmp/ipset_commands_v4.tmp"
{
awk -F'|' '$2=="CN" && $3=="ipv4" {print "add '"${IPSET_IPV4_TEMP}"' " $4 "/" 32-log($5)/log(2)}' "${TMP_FILE}"
} > "${temp_commands_v4}"
# 批量加载
cat "${temp_commands_v4}" | ipset restore
rm -f "${temp_commands_v4}"
# 交换ipset
ipset swap "${IPSET_IPV4}" "${IPSET_IPV4_TEMP}"
ipset destroy "${IPSET_IPV4_TEMP}"
# --- IPv6 处理(如果启用) ---
if [ "${ENABLE_IPV6}" = "true" ]; then
if ! ipset list "${IPSET_IPV6}" -n >/dev/null 2>&1; then
log_info "目标IPv6 ipset (${IPSET_IPV6}) 不存在,正在创建..."
ipset create "${IPSET_IPV6}" hash:net family inet6
fi
# 清理临时ipset
ipset destroy "${IPSET_IPV6_TEMP}" 2>/dev/null || true
# 创建临时ipset
ipset create "${IPSET_IPV6_TEMP}" hash:net family inet6
# 批量添加IPv6
temp_commands_v6="/tmp/ipset_commands_v6.tmp"
{
awk -F'|' '$2=="CN" && $3=="ipv6" {print "add '"${IPSET_IPV6_TEMP}"' " $4 "/" $5}' "${TMP_FILE}"
} > "${temp_commands_v6}"
# 批量加载
cat "${temp_commands_v6}" | ipset restore
rm -f "${temp_commands_v6}"
# 交换ipset
ipset swap "${IPSET_IPV6}" "${IPSET_IPV6_TEMP}"
ipset destroy "${IPSET_IPV6_TEMP}"
fi
# 清理临时文件
rm -f "${TMP_FILE}"
# 保存ipset配置(持久化)
mkdir -p $(dirname "${IPSET_CONF}")
ipset save > "${IPSET_CONF}"
# 输出统计
ipv4_count=$(ipset list "${IPSET_IPV4}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
ipv6_count=$([ "${ENABLE_IPV6}" = "true" ] && ipset list "${IPSET_IPV6}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
log_info "IPset更新完成:IPv4(${ipv4_count}条) | IPv6(${ipv6_count}条)"
log_info "IPset配置已自动保存到 ${IPSET_CONF}"
EOF
chmod +x "${SCRIPT_PATH}"
log_info "IP段更新脚本已创建:${SCRIPT_PATH}"
# ===== 核心修复:强制重建ipset-init脚本 + 兼容chkconfig/systemd =====
force_rebuild_ipset_init_script
# 2. 检测系统初始化类型,自动配置对应自启方式
local init_system=$(detect_init_system)
log_info "检测到系统初始化类型:${init_system}"
if [ "${init_system}" = "chkconfig" ]; then
# CentOS 6 等chkconfig系统
chkconfig --add ipset-init >/dev/null 2>&1
chkconfig ipset-init on >/dev/null 2>&1
/etc/init.d/ipset-init start >/dev/null 2>&1
log_info "已配置chkconfig开机自启(IPset服务)"
elif [ "${init_system}" = "systemd" ]; then
# CentOS 7+/Debian/Ubuntu 等systemd系统
# 强制创建/覆盖systemd服务文件,确保路径正确
mkdir -p $(dirname "${IPSET_SYSTEMD_SERVICE}")
cat > "${IPSET_SYSTEMD_SERVICE}" << 'EOF'
[Unit]
Description=IPset Auto Load Configuration
After=network.target iptables.service firewalld.service
[Service]
Type=oneshot
ExecStart=/etc/init.d/ipset-init start
ExecStop=/etc/init.d/ipset-init stop
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# 重新加载systemd配置,启用并启动服务(修复启动失败问题)
systemctl daemon-reload >/dev/null 2>&1
systemctl disable ipset-init >/dev/null 2>&1 # 先禁用,避免残留配置冲突
systemctl enable ipset-init >/dev/null 2>&1
systemctl stop ipset-init >/dev/null 2>&1 # 停止失败的服务
systemctl start ipset-init >/dev/null 2>&1
log_info "已配置systemd开机自启(IPset服务),服务文件:${IPSET_SYSTEMD_SERVICE}"
else
log_warn "未知系统初始化类型,需手动配置IPset开机自启"
fi
log_info "IPset开机自启配置完成(已修复脚本缺失问题)"
}
init_ipset() {
log_info "开始初始化IPset并加载CN IP段"
ENABLE_IPV6="${ENABLE_IPV6}" bash "${SCRIPT_PATH}"
log_info "IPset初始化完成"
}
# firewalld规则配置
configure_firewalld_rules() {
log_info "开始配置firewalld规则"
firewall-cmd --set-default-zone=drop >/dev/null
# 放行内网网段
for cidr in "${LAN_CIDRS[@]}"; do
if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv4 source address=${cidr} accept" >/dev/null 2>&1 || true
else
firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source address=${cidr} accept" >/dev/null 2>&1 || true
fi
done
# 放行自定义IP/CIDR
for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv4 source address=${cidr} accept" >/dev/null 2>&1 || true
else
if [ "${ENABLE_IPV6}" = "true" ]; then
firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source address=${cidr} accept" >/dev/null 2>&1 || true
fi
fi
done
[ ${#CUSTOM_ALLOW_CIDRS[@]} -gt 0 ] && log_info "已放行自定义IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]}"
# 放行CN IP
firewall-cmd --permanent --zone=drop --add-rich-rule="rule source ipset=${IPSET_IPV4} accept" >/dev/null 2>&1 || true
# IPv6规则
if [ "${ENABLE_IPV6}" = "true" ]; then
firewall-cmd --permanent --zone=drop --add-rich-rule="rule family=ipv6 source ipset=${IPSET_IPV6} accept" >/dev/null 2>&1 || true
fi
# 放行SSH
if [ "${ALLOW_SSH}" = "true" ]; then
local ssh_port=${SSH_PORT:-22}
firewall-cmd --permanent --zone=drop --add-port="${ssh_port}/tcp" >/dev/null
log_info "已放行SSH端口:${ssh_port}"
fi
# 重载规则
firewall-cmd --reload >/dev/null
firewall-cmd --runtime-to-permanent >/dev/null 2>&1 || true
log_info "firewalld规则配置完成"
}
# iptables规则配置:使用独立链,避免重复规则堆到 INPUT,便于后续扩展和卸载
configure_iptables_rules() {
log_info "开始配置iptables规则(独立链:${IPTABLES_CHAIN})"
# 创建并刷新IPv4独立链
iptables -N "${IPTABLES_CHAIN}" 2>/dev/null || true
iptables -F "${IPTABLES_CHAIN}"
iptables -C INPUT -j "${IPTABLES_CHAIN}" 2>/dev/null || iptables -I INPUT 1 -j "${IPTABLES_CHAIN}"
# 设置默认策略
iptables -P INPUT DROP
# 独立链规则顺序:已建立连接 -> 内网 -> 自定义放行 -> CN IP -> SSH -> RETURN
iptables -A "${IPTABLES_CHAIN}" -m state --state ESTABLISHED,RELATED -j ACCEPT
for cidr in "${LAN_CIDRS[@]}"; do
if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
iptables -A "${IPTABLES_CHAIN}" -s "${cidr}" -j ACCEPT
fi
done
for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
if echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
iptables -A "${IPTABLES_CHAIN}" -s "${cidr}" -j ACCEPT
fi
done
iptables -A "${IPTABLES_CHAIN}" -m set --match-set "${IPSET_IPV4}" src -j ACCEPT
if [ "${ALLOW_SSH}" = "true" ]; then
local ssh_port=${SSH_PORT:-22}
iptables -A "${IPTABLES_CHAIN}" -p tcp --dport "${ssh_port}" -j ACCEPT
log_info "已放行SSH端口:${ssh_port}"
fi
iptables -A "${IPTABLES_CHAIN}" -j RETURN
# IPv6规则
if [ "${ENABLE_IPV6}" = "true" ]; then
log_info "开始配置ip6tables规则(独立链:${IP6TABLES_CHAIN})"
ip6tables -N "${IP6TABLES_CHAIN}" 2>/dev/null || true
ip6tables -F "${IP6TABLES_CHAIN}"
ip6tables -C INPUT -j "${IP6TABLES_CHAIN}" 2>/dev/null || ip6tables -I INPUT 1 -j "${IP6TABLES_CHAIN}"
ip6tables -P INPUT DROP
ip6tables -A "${IP6TABLES_CHAIN}" -m state --state ESTABLISHED,RELATED -j ACCEPT
for cidr in "${LAN_CIDRS[@]}"; do
if ! echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
ip6tables -A "${IP6TABLES_CHAIN}" -s "${cidr}" -j ACCEPT
fi
done
for cidr in "${CUSTOM_ALLOW_CIDRS[@]}"; do
if ! echo "${cidr}" | grep -E '\.' >/dev/null 2>&1; then
ip6tables -A "${IP6TABLES_CHAIN}" -s "${cidr}" -j ACCEPT
fi
done
ip6tables -A "${IP6TABLES_CHAIN}" -m set --match-set "${IPSET_IPV6}" src -j ACCEPT
[ "${ALLOW_SSH}" = "true" ] && ip6tables -A "${IP6TABLES_CHAIN}" -p tcp --dport "${SSH_PORT:-22}" -j ACCEPT
ip6tables -A "${IP6TABLES_CHAIN}" -j RETURN
fi
# 保存规则
if [ -f /etc/redhat-release ]; then
iptables-save > /etc/sysconfig/iptables
[ "${ENABLE_IPV6}" = "true" ] && ip6tables-save > /etc/sysconfig/ip6tables
elif [ -f /etc/debian_version ]; then
apt install -y iptables-persistent >/dev/null 2>&1
iptables-save > /etc/iptables/rules.v4
[ "${ENABLE_IPV6}" = "true" ] && ip6tables-save > /etc/iptables/rules.v6
fi
log_info "iptables规则配置完成并持久化"
}
# ===================== 定时任务配置 =====================
# 检测是否已有定时任务
check_existing_cron() {
local cron_exists=0
if [ -f "${CRON_FILE}" ]; then
if grep -q "${SCRIPT_PATH}" "${CRON_FILE}"; then
cron_exists=1
fi
fi
echo "${cron_exists}"
}
# 更新/创建定时任务
update_cron_job() {
local hour=$(echo "${UPDATE_TIME}" | cut -d: -f1)
local minute=$(echo "${UPDATE_TIME}" | cut -d: -f2)
local cron_expr="${minute} ${hour} $( [ "${UPDATE_INTERVAL}" -eq 1 ] && echo "*" || echo "*/${UPDATE_INTERVAL}" ) * *"
local new_cron="${cron_expr} root ENABLE_IPV6=${ENABLE_IPV6} ${SCRIPT_PATH} >/dev/null 2>&1"
local cron_exists=$(check_existing_cron)
if [ "${cron_exists}" -eq 1 ]; then
# 使用sed替换现有任务,兼容多系统
sed -i "s|.*${SCRIPT_PATH}.*|${new_cron}|" "${CRON_FILE}"
log_info "已更新现有定时任务:${new_cron}"
else
# 新建定时任务文件
echo "${new_cron}" > "${CRON_FILE}"
chmod 644 "${CRON_FILE}"
log_info "已创建新定时任务:${new_cron}"
fi
# 重启crond服务
if command -v systemctl >/dev/null 2>&1; then
systemctl restart crond >/dev/null 2>&1 || systemctl restart cron >/dev/null 2>&1
else
service crond restart >/dev/null 2>&1 || service cron restart >/dev/null 2>&1
fi
log_info "定时任务服务已重启"
}
# 验证更新间隔
validate_interval() {
local input="$1"
echo "${input}" | grep -E '^[0-9]+$' >/dev/null 2>&1 && [ "${input}" -ge 1 ] && [ "${input}" -le 365 ]
}
# 验证更新时间
validate_time() {
local input="$1"
echo "${input}" | grep -E '^([01][0-9]|2[0-3]):[0-5][0-9]$' >/dev/null 2>&1
}
# 配置更新策略(仅在开局菜单主动选择时交互;安装/自定义放行流程不再重复询问)
configure_update_strategy() {
if [ "${SILENT_MODE}" = "false" ]; then
blue "=== 配置IP段自动更新策略 ==="
local cron_exists=$(check_existing_cron)
if [ "${cron_exists}" -eq 1 ]; then
yellow "当前已有定时任务,将使用新策略覆盖更新"
fi
# 更新频率
while true; do
read -p "请输入更新频率(天,默认${UPDATE_INTERVAL},1-365):" input_interval
input_interval=${input_interval:-${UPDATE_INTERVAL}}
if validate_interval "${input_interval}"; then
UPDATE_INTERVAL="${input_interval}"
break
else
yellow "输入无效!请输入1-365之间的数字"
fi
done
# 更新时间
while true; do
read -p "请输入更新时间(HH:MM,默认${UPDATE_TIME},示例:00:30):" input_time
input_time=${input_time:-${UPDATE_TIME}}
if validate_time "${input_time}"; then
UPDATE_TIME="${input_time}"
break
else
yellow "输入无效!请按格式输入(如03:00、18:30)"
fi
done
fi
log_info "更新策略确认:每${UPDATE_INTERVAL}天的${UPDATE_TIME}自动更新IP段"
update_cron_job
}
# 安装/应用规则时只确保定时任务存在,不再交互询问更新频率
ensure_update_cron_job() {
if ! validate_interval "${UPDATE_INTERVAL:-0}"; then
UPDATE_INTERVAL=1
fi
if ! validate_time "${UPDATE_TIME:-00:00}"; then
UPDATE_TIME="03:00"
fi
log_info "使用当前更新策略:每${UPDATE_INTERVAL}天的${UPDATE_TIME}自动更新IP段"
update_cron_job
}
# ===================== 验证/状态检查 =====================
verify_rules() {
log_info "开始验证防火墙配置"
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
local default_zone=$(firewall-cmd --get-default-zone)
log_info "firewalld默认区域:${default_zone}"
[ "${default_zone}" != "drop" ] && log_warn "firewalld默认区域未设置为drop"
else
local input_policy=$(iptables -S INPUT 2>/dev/null | head -1 | awk '{print $3}')
log_info "iptables INPUT默认策略:${input_policy}"
[ "${input_policy}" != "DROP" ] && log_warn "iptables INPUT默认策略未设置为DROP"
fi
log_info "防火墙配置验证完成"
}
# 检查防火墙和ipset持久化状态(增强版,验证修复结果)
check_persistence_status() {
log_info "检查防火墙和ipset持久化状态(修复验证)..."
local status_messages=()
local has_warn=0
# 检查防火墙服务
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
if systemctl is-enabled --quiet firewalld 2>/dev/null && systemctl is-active --quiet firewalld 2>/dev/null; then
status_messages+=("✅ firewalld 服务已启用并运行")
else
status_messages+=("❌ firewalld 服务未正确启用或运行")
has_warn=1
fi
else # iptables
if command -v iptables-services >/dev/null 2>&1 || [ -f /etc/sysconfig/iptables ]; then
if systemctl is-enabled --quiet iptables 2>/dev/null && systemctl is-active --quiet iptables 2>/dev/null; then
status_messages+=("✅ iptables-services 服务已启用并运行(CentOS持久化)")
else
status_messages+=("⚠️ iptables-services 服务未运行,但规则已持久化到 /etc/sysconfig/iptables")
has_warn=1
fi
elif command -v netfilter-persistent >/dev/null 2>&1; then
if systemctl is-enabled --quiet netfilter-persistent 2>/dev/null && systemctl is-active --quiet netfilter-persistent 2>/dev/null; then
status_messages+=("✅ netfilter-persistent 服务已启用并运行(iptables持久化)")
else
status_messages+=("❌ netfilter-persistent 服务未正确启用或运行")
has_warn=1
fi
else
status_messages+=("⚠️ 未找到iptables持久化工具,但规则已手动保存")
has_warn=1
fi
fi
# 检查ipset脚本(核心修复验证)
if [ -f "${IPSET_INIT_SCRIPT}" ] && [ -x "${IPSET_INIT_SCRIPT}" ]; then
status_messages+=("✅ ipset-init 脚本存在且可执行:${IPSET_INIT_SCRIPT}")
else
status_messages+=("❌ ipset-init 脚本缺失或无执行权限")
has_warn=1
fi
# 检查ipset服务
local init_system=$(detect_init_system)
if [ "${init_system}" = "chkconfig" ]; then
if chkconfig --list ipset-init 2>/dev/null | grep -q "on"; then
status_messages+=("✅ IPset chkconfig 自启已启用")
else
status_messages+=("❌ IPset chkconfig 自启未启用")
has_warn=1
fi
elif [ "${init_system}" = "systemd" ]; then
if systemctl is-enabled --quiet ipset-init 2>/dev/null && systemctl is-active --quiet ipset-init 2>/dev/null; then
status_messages+=("✅ IPset systemd 服务已启用并运行(修复成功)")
else
status_messages+=("⚠️ IPset systemd 服务状态异常,可执行 'systemctl restart ipset-init' 手动修复")
has_warn=1
fi
else
status_messages+=("⚠️ 未知初始化系统,无法检测IPset自启状态")
has_warn=1
fi
# 检查ipset是否存在
if ipset list "${IPSET_IPV4}" -n >/dev/null 2>&1; then
status_messages+=("✅ IPset '${IPSET_IPV4}' 存在")
else
status_messages+=("❌ IPset '${IPSET_IPV4}' 不存在")
has_warn=1
fi
if [ "${ENABLE_IPV6}" = "true" ]; then
if ipset list "${IPSET_IPV6}" -n >/dev/null 2>&1; then
status_messages+=("✅ IPset '${IPSET_IPV6}' 存在")
else
status_messages+=("❌ IPset '${IPSET_IPV6}' 不存在")
has_warn=1
fi
fi
# 检查ipset配置文件
if [ -f "${IPSET_CONF}" ] && [ -s "${IPSET_CONF}" ]; then
status_messages+=("✅ IPset 配置文件存在且非空:${IPSET_CONF}")
else
status_messages+=("❌ IPset 配置文件缺失或为空:${IPSET_CONF}")
has_warn=1
fi
# 输出所有状态信息
for msg in "${status_messages[@]}"; do
echo "${msg}"
done
# 提示异常处理
if [ ${has_warn} -eq 1 ]; then
log_warn "存在部分状态异常,请根据上述提示排查(可执行 $0 --fix-ipset 快速修复)"
else
log_info "所有持久化状态正常(IPset自启修复成功)"
fi
}
# ===================== 快速修复IPset功能(独立入口) =====================
fix_ipset_only() {
log_info "进入IPset快速修复模式(仅修复自启脚本+systemd服务)"
check_root
init_env
# 强制重建ipset-init脚本
force_rebuild_ipset_init_script
# 检测初始化系统,修复自启配置
local init_system=$(detect_init_system)
if [ "${init_system}" = "systemd" ]; then
# 修复systemd服务
if [ ! -f "${IPSET_SYSTEMD_SERVICE}" ]; then
log_info "创建缺失的systemd服务文件:${IPSET_SYSTEMD_SERVICE}"
mkdir -p $(dirname "${IPSET_SYSTEMD_SERVICE}")
cat > "${IPSET_SYSTEMD_SERVICE}" << 'EOF'
[Unit]
Description=IPset Auto Load Configuration
After=network.target iptables.service firewalld.service
[Service]
Type=oneshot
ExecStart=/etc/init.d/ipset-init start
ExecStop=/etc/init.d/ipset-init stop
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
fi
# 重新加载配置,重启服务
systemctl daemon-reload
systemctl disable ipset-init >/dev/null 2>&1
systemctl enable ipset-init >/dev/null 2>&1
systemctl stop ipset-init >/dev/null 2>&1
systemctl start ipset-init >/dev/null 2>&1
# 验证服务状态
if systemctl is-active --quiet ipset-init 2>/dev/null; then
log_info "IPset systemd服务修复成功,状态正常"
else
log_warn "IPset systemd服务启动失败,可手动执行:systemctl status ipset-init 查看详情"
fi
elif [ "${init_system}" = "chkconfig" ]; then
# 修复chkconfig自启
chkconfig --add ipset-init >/dev/null 2>&1
chkconfig ipset-init on >/dev/null 2>&1
/etc/init.d/ipset-init restart >/dev/null 2>&1
log_info "IPset chkconfig服务修复成功"
fi
# 验证ipset配置
if [ -f "${IPSET_CONF}" ] && [ -s "${IPSET_CONF}" ]; then
# 重新加载ipset规则
/etc/init.d/ipset-init start >/dev/null 2>&1
log_info "IPset规则重新加载成功"
else
log_warn "IPset配置文件为空,若需加载CN IP段,请执行:${SCRIPT_PATH}"
fi
log_info "IPset快速修复操作完成"
}
# ===================== 状态查看功能 =====================
show_status() {
check_root
init_env
load_config
validate_config
detect_firewall
blue "================================================"
green "CN Firewall 当前状态"
blue "================================================"
echo "版本:${VERSION}"
echo "防火墙类型:${FIREWALL_TYPE:-unknown}"
echo "IPv6启用:${ENABLE_IPV6}"
echo "白名单模式:${WHITELIST_MODE}(cn_only=自动安装仅CN IP集,custom=自定义放行管理,lan=兼容旧配置)"
echo "SSH放行:${ALLOW_SSH},端口:${SSH_PORT}"
echo "配置文件:${CONFIG_FILE}"
echo "日志文件:${LOG_FILE}"
echo ""
green "规则状态:"
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
echo "firewalld 默认区域:$(firewall-cmd --get-default-zone 2>/dev/null || echo unknown)"
echo "firewalld 是否运行:$(systemctl is-active firewalld 2>/dev/null || echo unknown)"
else
echo "iptables INPUT默认策略:$(iptables -S INPUT 2>/dev/null | head -1 | awk '{print $3}')"
if iptables -S "${IPTABLES_CHAIN}" >/dev/null 2>&1; then
echo "IPv4独立链:${IPTABLES_CHAIN} 已存在"
else
echo "IPv4独立链:${IPTABLES_CHAIN} 不存在"
fi
if [ "${ENABLE_IPV6}" = "true" ]; then
if ip6tables -S "${IP6TABLES_CHAIN}" >/dev/null 2>&1; then
echo "IPv6独立链:${IP6TABLES_CHAIN} 已存在"
else
echo "IPv6独立链:${IP6TABLES_CHAIN} 不存在"
fi
fi
fi
echo ""
green "IPset状态:"
local ipv4_count=0
local ipv6_count=0
ipv4_count=$(ipset list "${IPSET_IPV4}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
ipv6_count=$(ipset list "${IPSET_IPV6}" 2>/dev/null | awk -F': ' '/Number of entries/ {print $2}' || echo 0)
echo "${IPSET_IPV4}:${ipv4_count:-0} 条"
[ "${ENABLE_IPV6}" = "true" ] && echo "${IPSET_IPV6}:${ipv6_count:-0} 条"
echo "IPset配置文件:$([ -s "${IPSET_CONF}" ] && echo "存在" || echo "缺失或为空") (${IPSET_CONF})"
echo ""
green "放行列表:"
echo "内网/回环放行:${LAN_CIDRS[*]:-无}"
echo "自定义放行IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]:-无}"
echo ""
green "定时任务:"
if [ -f "${CRON_FILE}" ]; then
cat "${CRON_FILE}"
else
echo "未发现 ${CRON_FILE}"
fi
echo ""
check_persistence_status
blue "================================================"
}
# ===================== 主流程入口 =====================
main() {
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
-?|--help)
show_help
;;
--version)
echo "CN Firewall Config Tool ${VERSION}"
exit 0
;;
--silent)
SILENT_MODE="true"
shift
;;
--config)
CONFIG_FILE="$2"
shift 2
;;
--debug)
DEBUG_MODE="true"
shift
;;
--backup)
check_root
init_env
detect_firewall
backup_firewall_rules
exit 0
;;
--rollback)
check_root
init_env
detect_firewall
rollback_firewall_rules
exit 0
;;
--recovery)
check_root
emergency_recovery
exit 0
;;
--fix-ipset)
fix_ipset_only
exit 0
;;
--status)
show_status
exit 0
;;
*)
log_error "无效参数:$1!使用 $0 --help 查看帮助"
shift
;;
esac
done
# 核心流程执行
check_root
init_env
load_config
validate_config
check_network_connectivity
install_dependencies
detect_firewall
backup_firewall_rules
configure_ipv6_switch
select_start_action
apply_selected_action
create_ipset_script
init_ipset
# 按防火墙类型配置规则
if [ "${FIREWALL_TYPE}" = "firewalld" ]; then
configure_firewalld_rules
else
configure_iptables_rules
fi
ensure_update_cron_job
verify_rules
verify_network_after_config
check_persistence_status
# 最终汇总信息
clear
blue "================================================"
green "🎉 防火墙+IPset自启配置全部完成(版本:${VERSION})"
blue "================================================"
echo ""
green "✅ 核心配置信息:"
echo " 防火墙类型:${FIREWALL_TYPE}"
echo " IPset IPv4名称:${IPSET_IPV4}"
echo " IPset IPv6名称:${IPSET_IPV6}(启用状态:${ENABLE_IPV6})"
echo " 放行SSH端口:${SSH_PORT}(放行状态:${ALLOW_SSH})"
echo " 白名单模式:${WHITELIST_MODE}"
echo " 放行网段:${LAN_CIDRS[*]:-默认回环网段}"
echo " 自定义放行IP/CIDR:${CUSTOM_ALLOW_CIDRS[*]:-无}"
echo " IP段更新频率:每${UPDATE_INTERVAL}天 ${UPDATE_TIME}"
echo ""
green "✅ 关键文件路径:"
echo " IP段更新脚本:${SCRIPT_PATH}"
echo " IPset配置文件:${IPSET_CONF}"
echo " IPset自启脚本:${IPSET_INIT_SCRIPT}"
echo " IPset systemd服务:${IPSET_SYSTEMD_SERVICE}"
echo " 定时任务文件:${CRON_FILE}"
echo " 日志文件:${LOG_FILE}"
echo " 备份目录:${BACKUP_DIR}"
echo ""
green "✅ 常用操作命令:"
echo " 手动更新IP段:${SCRIPT_PATH}"
echo " 查看IPset规则:ipset list ${IPSET_IPV4}"
echo " 查看防火墙状态:${FIREWALL_TYPE} status"
echo " 备份规则:$0 --backup"
echo " 回滚规则:$0 --rollback"
echo " 紧急恢复:$0 --recovery"
echo " 快速修复IPset:$0 --fix-ipset"
echo " 进入开局菜单/自定义放行管理:$0"
echo ""
blue "================================================"
log_info "所有操作执行完毕,服务器重启后IPset规则将自动加载"
}
# 启动主流程
main "$@"
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 TAO
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

