Add default-src-manager Pro v2.0
This commit is contained in:
529
scripts/default-src-manager.sh
Normal file
529
scripts/default-src-manager.sh
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
#!/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" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Default Source IP Manager
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=$APPLY_SCRIPT
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now "$SERVICE_NAME"
|
||||||
|
|
||||||
|
success "开机自启已安装"
|
||||||
|
systemctl status "$SERVICE_NAME" --no-pager || true
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_service() {
|
||||||
|
section "移除开机自启"
|
||||||
|
|
||||||
|
systemctl disable --now "$SERVICE_NAME" 2>/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 "$@"
|
||||||
Reference in New Issue
Block a user