#!/usr/bin/env bash # # 默认出站源地址管理器 Pro # 功能:智能管理服务器多 IP 出站策略 # 作者:顶尖定制版 # 版本:v2.0 # set -euo pipefail # ==================== 配置 ==================== STATE_DIR="/var/lib/default-src-manager" STATE_FILE="$STATE_DIR/config.json" BACKUP_DIR="$STATE_DIR/backups" LOG_FILE="$STATE_DIR/manager.log" SERVICE_NAME="default-src-manager.service" SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME" APPLY_SCRIPT="/usr/local/sbin/default-src-apply" # ==================== 颜色 ==================== if [[ -t 1 ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' RESET='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' RESET='' fi # ==================== 工具函数 ==================== log() { local level="$1" shift local msg="$*" local timestamp timestamp="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$timestamp] [$level] $msg" >> "$LOG_FILE" } info() { echo -e "${CYAN}[INFO]${RESET} $*" log "INFO" "$*" } success() { echo -e "${GREEN}[OK]${RESET} $*" log "OK" "$*" } warn() { echo -e "${YELLOW}[WARN]${RESET} $*" log "WARN" "$*" } error() { echo -e "${RED}[ERROR]${RESET} $*" >&2 log "ERROR" "$*" } die() { error "$*" exit 1 } section() { echo echo -e "${BOLD}${BLUE}━━━━ $* ━━━━${RESET}" echo } kv() { printf " ${DIM}%-18s${RESET} %s\n" "$1:" "$2" } check_root() { [[ $EUID -eq 0 ]] || die "需要 root 权限运行" } check_deps() { local missing=() for cmd in ip jq curl ping systemctl; do command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd") done if [[ ${#missing[@]} -gt 0 ]]; then die "缺少依赖: ${missing[*]}" fi } init_dirs() { mkdir -p "$STATE_DIR" "$BACKUP_DIR" touch "$LOG_FILE" } # ==================== IP 工具 ==================== is_private_ipv4() { local ip="$1" [[ "$ip" =~ ^10\. ]] && return 0 [[ "$ip" =~ ^192\.168\. ]] && return 0 [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] && return 0 return 1 } get_ip_score() { local ip="$1" # 优先级:172.16-31 > 10 > 192.168 if [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then echo 300 elif [[ "$ip" =~ ^10\. ]]; then echo 200 elif [[ "$ip" =~ ^192\.168\. ]]; then echo 100 else echo 0 fi } # ==================== 环境检测 ==================== detect_environment() { info "正在检测网络环境..." local iface gw public_ips=() private_ips=() # 检测默认网卡和网关 iface="$(ip -4 route show default | awk 'NR==1{print $5}')" gw="$(ip -4 route show default | awk 'NR==1{print $3}')" [[ -n "$iface" ]] || die "未找到默认网卡" [[ -n "$gw" ]] || die "未找到默认网关" # 收集所有 IP while IFS= read -r line; do local ip cidr cidr="$(echo "$line" | awk '{print $4}')" ip="${cidr%/*}" if is_private_ipv4 "$ip"; then private_ips+=("$ip") else public_ips+=("$ip") fi done < <(ip -o -4 addr show dev "$iface" scope global 2>/dev/null) [[ ${#public_ips[@]} -gt 0 ]] || die "未找到公网 IP" [[ ${#private_ips[@]} -gt 0 ]] || die "未找到内网 IP" # 选择最优内网 IP(优先 172.16-31) local best_private="${private_ips[0]}" local best_score=0 for ip in "${private_ips[@]}"; do local score score="$(get_ip_score "$ip")" if [[ $score -gt $best_score ]]; then best_score=$score best_private="$ip" fi done # 保存配置 jq -n \ --arg iface "$iface" \ --arg gw "$gw" \ --arg public "${public_ips[0]}" \ --arg private "$best_private" \ '{ interface: $iface, gateway: $gw, public_ip: $public, private_ip: $private, detected_at: (now | strftime("%Y-%m-%d %H:%M:%S")) }' > "$STATE_FILE" success "环境检测完成" kv "网卡" "$iface" kv "网关" "$gw" kv "公网 IP" "${public_ips[0]}" kv "内网 IP" "$best_private" if [[ ${#private_ips[@]} -gt 1 ]]; then info "检测到多个内网 IP,已自动选择优先级最高的" fi } load_config() { [[ -f "$STATE_FILE" ]] || die "配置文件不存在,请先运行检测" IFACE="$(jq -r '.interface' "$STATE_FILE")" GATEWAY="$(jq -r '.gateway' "$STATE_FILE")" PUBLIC_IP="$(jq -r '.public_ip' "$STATE_FILE")" PRIVATE_IP="$(jq -r '.private_ip' "$STATE_FILE")" } # ==================== 策略应用 ==================== backup_routes() { local backup_file="$BACKUP_DIR/routes-$(date +%Y%m%d-%H%M%S).txt" { echo "=== Default Route ===" ip route show default echo echo "=== Policy Rules ===" ip rule show echo echo "=== Table 100 ===" ip route show table 100 2>/dev/null || echo "empty" echo echo "=== Table 200 ===" ip route show table 200 2>/dev/null || echo "empty" } > "$backup_file" info "已备份当前路由配置到 $backup_file" } clean_policy() { info "清理现有策略..." ip rule del pref 100 2>/dev/null || true ip rule del pref 110 2>/dev/null || true ip route flush table 100 2>/dev/null || true ip route flush table 200 2>/dev/null || true ip route flush cache 2>/dev/null || true } apply_private_default() { load_config backup_routes section "应用内网 IP 默认出站" info "配置主路由表..." ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" info "配置策略路由表..." clean_policy # 表 100: 内网 IP 源 ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" table 100 ip rule add pref 100 from "$PRIVATE_IP/32" table 100 # 表 200: 公网 IP 源 ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP" table 200 ip rule add pref 110 from "$PUBLIC_IP/32" table 200 ip route flush cache 2>/dev/null || true success "策略应用完成" verify_routes } rollback_public_default() { load_config backup_routes section "恢复公网 IP 默认出站" clean_policy ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP" ip route flush cache 2>/dev/null || true success "已恢复为公网 IP 默认出站" verify_routes } # ==================== 验证测试 ==================== verify_routes() { echo info "当前路由状态:" echo echo " 主默认路由:" ip route show default | sed 's/^/ /' echo echo " 策略规则:" ip rule show | grep -E "100|110" | sed 's/^/ /' || echo " (无)" } test_connectivity() { load_config section "连通性测试" # 测试 1: 默认路由选路 info "测试默认新连接选路..." ip route get 1.1.1.1 | sed 's/^/ /' echo # 测试 2: 内网 IP 出站 info "测试内网 IP 出站..." if ping -I "$PRIVATE_IP" -c 2 -W 3 1.1.1.1 >/dev/null 2>&1; then success "内网 IP 出站正常" else warn "内网 IP 出站失败" fi # 测试 3: 公网 IP 查询 info "查询实际出口 IP..." local exit_ip exit_ip="$(curl -4 --interface "$PRIVATE_IP" --connect-timeout 5 --max-time 10 -s https://api.ipify.org 2>/dev/null || echo "查询失败")" kv "出口 IP" "$exit_ip" # 测试 4: DNS 解析 info "测试 DNS 解析..." if host -W 3 google.com >/dev/null 2>&1; then success "DNS 解析正常" else warn "DNS 解析异常" fi } # ==================== 开机自启 ==================== install_service() { load_config section "安装开机自启" # 创建应用脚本 cat > "$APPLY_SCRIPT" <<'EOF' #!/usr/bin/env bash set -euo pipefail STATE_FILE="/var/lib/default-src-manager/config.json" [[ -f "$STATE_FILE" ]] || exit 1 IFACE="$(jq -r '.interface' "$STATE_FILE")" GATEWAY="$(jq -r '.gateway' "$STATE_FILE")" PUBLIC_IP="$(jq -r '.public_ip' "$STATE_FILE")" PRIVATE_IP="$(jq -r '.private_ip' "$STATE_FILE")" # 清理 ip rule del pref 100 2>/dev/null || true ip rule del pref 110 2>/dev/null || true ip route flush table 100 2>/dev/null || true ip route flush table 200 2>/dev/null || true # 应用 ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" ip route replace default via "$GATEWAY" dev "$IFACE" src "$PRIVATE_IP" table 100 ip route replace default via "$GATEWAY" dev "$IFACE" src "$PUBLIC_IP" table 200 ip rule add pref 100 from "$PRIVATE_IP/32" table 100 ip rule add pref 110 from "$PUBLIC_IP/32" table 200 ip route flush cache 2>/dev/null || true EOF chmod +x "$APPLY_SCRIPT" # 创建 systemd 服务 cat > "$SERVICE_FILE" </dev/null || true rm -f "$SERVICE_FILE" "$APPLY_SCRIPT" systemctl daemon-reload success "开机自启已移除" } # ==================== 状态显示 ==================== show_status() { if [[ ! -f "$STATE_FILE" ]]; then warn "尚未检测环境,请先运行选项 1" return fi load_config section "当前状态" kv "网卡" "$IFACE" kv "网关" "$GATEWAY" kv "公网 IP" "$PUBLIC_IP" kv "内网 IP" "$PRIVATE_IP" local detected_at detected_at="$(jq -r '.detected_at' "$STATE_FILE")" kv "检测时间" "$detected_at" echo info "当前默认路由:" ip route show default | sed 's/^/ /' if ip route show default | grep -q "src $PRIVATE_IP"; then echo success "当前模式:内网 IP 默认出站" elif ip route show default | grep -q "src $PUBLIC_IP"; then echo info "当前模式:公网 IP 默认出站" else echo warn "当前模式:未识别" fi } # ==================== 菜单 ==================== show_menu() { clear echo -e "${BOLD}${CYAN}" echo "╔════════════════════════════════════════════╗" echo "║ 默认出站源地址管理器 Pro v2.0 ║" echo "╚════════════════════════════════════════════╝" echo -e "${RESET}" show_status 2>/dev/null || echo echo echo -e "${BOLD}功能菜单${RESET}" echo echo " ${GREEN}1${RESET}) 检测网络环境" echo " ${GREEN}2${RESET}) 应用内网 IP 默认出站" echo " ${GREEN}3${RESET}) 恢复公网 IP 默认出站" echo " ${GREEN}4${RESET}) 测试连通性" echo " ${GREEN}5${RESET}) 查看详细状态" echo " ${GREEN}6${RESET}) 安装开机自启" echo " ${GREEN}7${RESET}) 移除开机自启" echo " ${GREEN}8${RESET}) 清理策略规则" echo " ${GREEN}9${RESET}) 查看操作日志" echo " ${RED}0${RESET}) 退出" echo } # ==================== 主函数 ==================== main() { check_root check_deps init_dirs while true; do show_menu read -rp "请选择 [0-9]: " choice echo case "$choice" in 1) detect_environment read -rp "按回车继续..." ;; 2) apply_private_default read -rp "按回车继续..." ;; 3) rollback_public_default read -rp "按回车继续..." ;; 4) test_connectivity read -rp "按回车继续..." ;; 5) section "详细路由状态" echo "主路由表:" ip route show | sed 's/^/ /' echo echo "策略规则:" ip rule show | sed 's/^/ /' echo echo "表 100(内网 IP 源):" ip route show table 100 2>/dev/null | sed 's/^/ /' || echo " (空)" echo echo "表 200(公网 IP 源):" ip route show table 200 2>/dev/null | sed 's/^/ /' || echo " (空)" read -rp "按回车继续..." ;; 6) install_service read -rp "按回车继续..." ;; 7) remove_service read -rp "按回车继续..." ;; 8) clean_policy success "策略规则已清理" read -rp "按回车继续..." ;; 9) section "最近 20 条日志" tail -20 "$LOG_FILE" 2>/dev/null || echo "日志为空" read -rp "按回车继续..." ;; 0) echo "再见!" exit 0 ;; *) warn "无效选项" sleep 1 ;; esac done } main "$@"