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

