diff --git a/route_test.sh b/route_test.sh new file mode 100644 index 0000000..380d7e2 --- /dev/null +++ b/route_test.sh @@ -0,0 +1,482 @@ +#!/bin/bash +# ============================================================ +# 三网路由检测脚本 (电信 / 联通 / 移动) +# 适用系统: Debian / Ubuntu +# 功能: 自动安装依赖,测试去程+回程路由 +# ============================================================ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +RESET='\033[0m' + +# ── 测试目标 IP ────────────────────────────────────────────── +# 去程(从本地到这些国内IP) +TELECOM_IPS=("202.96.209.5" "61.139.2.69" "211.137.58.20") # 电信各省 +UNICOM_IPS=("211.95.46.3" "218.104.111.114" "61.135.169.121") # 联通各省 +MOBILE_IPS=("221.179.131.11" "223.5.5.5" "120.196.165.1") # 移动各省 + +# 回程测试目标 +TELECOM_RETURN="180.97.33.108" # 电信上海 +UNICOM_RETURN="218.104.111.114" # 联通武汉 +MOBILE_RETURN="221.179.131.11" # 移动北京 + +# ── 辅助函数 ───────────────────────────────────────────────── +print_banner() { + echo -e "${CYAN}" + echo "╔══════════════════════════════════════════════════╗" + echo "║ 三网路由检测脚本 by route_test ║" + echo "║ 电信 / 联通 / 移动 去程 + 回程 ║" + echo "╚══════════════════════════════════════════════════╝" + echo -e "${RESET}" +} + +print_section() { + echo "" + echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e "${BOLD}${YELLOW} $1${RESET}" + echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +} + +check_root() { + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}[错误] 请使用 root 用户运行此脚本${RESET}" + echo " sudo bash route_test.sh" + exit 1 + fi +} + +get_vps_info() { + print_section "📋 VPS 基本信息" + local ip=$(curl -s --max-time 5 ip.sb 2>/dev/null || echo "获取失败") + local info=$(curl -s --max-time 5 ipinfo.io 2>/dev/null) + local org=$(echo "$info" | grep '"org"' | cut -d'"' -f4) + local city=$(echo "$info" | grep '"city"' | cut -d'"' -f4) + local country=$(echo "$info" | grep '"country"' | cut -d'"' -f4) + echo -e " IP地址 : ${GREEN}${ip}${RESET}" + echo -e " 归属地 : ${GREEN}${country} ${city}${RESET}" + echo -e " ASN/运营: ${GREEN}${org}${RESET}" + echo -e " 系统 : ${GREEN}$(lsb_release -d 2>/dev/null | cut -f2)${RESET}" + echo -e " 内核 : ${GREEN}$(uname -r)${RESET}" + echo -e " 时间 : ${GREEN}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}" +} + +install_deps() { + print_section "🔧 检查并安装依赖" + + # ── 检测系统架构 ────────────────────────────────────────── + local arch_raw + arch_raw=$(uname -m) + local nt_arch="" + case "$arch_raw" in + x86_64) nt_arch="amd64" ;; + aarch64|arm64) nt_arch="arm64" ;; + armv7l|armv6l) nt_arch="armv7" ;; + mips) nt_arch="mips" ;; + mips64) nt_arch="mips64";; + mipsle|mipsel) nt_arch="mipsle";; + *) nt_arch="" ;; + esac + echo -e " 系统架构: ${GREEN}${arch_raw}${RESET} → nexttrace 目标包: ${GREEN}${nt_arch:-不支持}${RESET}" + + apt-get update -qq 2>/dev/null + + # mtr + if ! command -v mtr &>/dev/null; then + echo -e " ${YELLOW}安装 mtr...${RESET}" + apt-get install -y -qq mtr-tiny 2>/dev/null && \ + echo -e " ${GREEN}✔ mtr 安装完成${RESET}" || \ + echo -e " ${RED}✘ mtr 安装失败${RESET}" + else + echo -e " ${GREEN}✔ mtr 已安装${RESET}" + fi + + # traceroute + if ! command -v traceroute &>/dev/null; then + echo -e " ${YELLOW}安装 traceroute...${RESET}" + apt-get install -y -qq traceroute 2>/dev/null && \ + echo -e " ${GREEN}✔ traceroute 安装完成${RESET}" || \ + echo -e " ${RED}✘ traceroute 安装失败${RESET}" + else + echo -e " ${GREEN}✔ traceroute 已安装${RESET}" + fi + + # nexttrace + if command -v nexttrace &>/dev/null; then + echo -e " ${GREEN}✔ nexttrace 已安装 $(nexttrace --version 2>/dev/null | head -1)${RESET}" + return + fi + + if [[ -z "$nt_arch" ]]; then + echo -e " ${RED}✘ 不支持的架构 (${arch_raw}),跳过 nexttrace 安装${RESET}" + return + fi + + echo -e " ${YELLOW}安装 nexttrace (${nt_arch})...${RESET}" + local nt_bin="/usr/local/bin/nexttrace" + local nt_file="nexttrace_linux_${nt_arch}" + local gh_url="https://github.com/nxtrace/NTrace-core/releases/latest/download/${nt_file}" + local mirror_url="https://ghfast.top/${gh_url}" + + # 依次尝试: 官方安装脚本 → GitHub 直链 → ghfast 镜像 + local installed=0 + + # 方法1: 官方安装脚本 + echo -e " ${CYAN}[1/3] 尝试官方安装脚本...${RESET}" + if curl -sf --max-time 15 https://raw.githubusercontent.com/nxtrace/NTrace-core/main/nt_install.sh \ + | bash -s -- --yes &>/dev/null; then + command -v nexttrace &>/dev/null && installed=1 + fi + + # 方法2: GitHub Releases 直链 + if [[ $installed -eq 0 ]]; then + echo -e " ${CYAN}[2/3] 尝试 GitHub 直链下载...${RESET}" + if curl -fL --max-time 30 -o "$nt_bin" "$gh_url" 2>/dev/null; then + chmod +x "$nt_bin" + command -v nexttrace &>/dev/null && installed=1 + fi + fi + + # 方法3: ghfast 镜像 + if [[ $installed -eq 0 ]]; then + echo -e " ${CYAN}[3/3] 尝试 ghfast 镜像下载...${RESET}" + if curl -fL --max-time 30 -o "$nt_bin" "$mirror_url" 2>/dev/null; then + chmod +x "$nt_bin" + command -v nexttrace &>/dev/null && installed=1 + fi + fi + + if [[ $installed -eq 1 ]]; then + echo -e " ${GREEN}✔ nexttrace 安装完成 $(nexttrace --version 2>/dev/null | head -1)${RESET}" + else + echo -e " ${RED}✘ nexttrace 全部安装方式失败,回程追踪将降级为 traceroute${RESET}" + echo -e " ${YELLOW} 手动安装: curl -Lo ${nt_bin} ${mirror_url} && chmod +x ${nt_bin}${RESET}" + fi +} + +# ── 全局结果收集变量 ───────────────────────────────────────── +# 延迟(ms): -1 = 超时 +TELECOM_AVG_LATENCY=-1 +UNICOM_AVG_LATENCY=-1 +MOBILE_AVG_LATENCY=-1 + +# 回程线路识别: unknown / cn2 / 163 / 9929 / 4837 / cmin2 / cmnet / mixed +TELECOM_RETURN_LINE="unknown" +UNICOM_RETURN_LINE="unknown" +MOBILE_RETURN_LINE="unknown" + +# MTR 最终跳丢包率(%) +TELECOM_LOSS=-1 +UNICOM_LOSS=-1 +MOBILE_LOSS=-1 + +# ── 去程测试(ping延迟)──────────────────────────────────────── +ping_test() { + local ip=$1 + local result + result=$(ping -c 3 -W 3 "$ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}') + if [[ -n "$result" ]]; then + echo -e " ${ip} → ${GREEN}${result} ms${RESET}" + echo "$result" # 供调用方捕获 + else + echo -e " ${ip} → ${RED}超时/不可达${RESET}" + echo "-1" + fi +} + +# 计算一组 IP 的平均延迟(忽略超时) +avg_latency_for() { + local -n _ips=$1 + local total=0 count=0 + for ip in "${_ips[@]}"; do + local val + val=$(ping -c 3 -W 3 "$ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}') + if [[ -n "$val" && "$val" != "-1" ]]; then + total=$(awk "BEGIN{print $total + $val}") + ((count++)) + fi + done + if [[ $count -gt 0 ]]; then + awk "BEGIN{printf \"%.1f\", $total / $count}" + else + echo "-1" + fi +} + +outbound_test() { + print_section "📤 去程延迟测试(VPS → 国内)" + + echo -e "\n ${BOLD}▶ 电信${RESET}" + for ip in "${TELECOM_IPS[@]}"; do ping_test "$ip" > /dev/null; done + # 单独计算平均值供评估用(不重复输出) + for ip in "${TELECOM_IPS[@]}"; do + local r; r=$(ping -c 3 -W 3 "$ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}') + [[ -n "$r" ]] && echo -e " ${ip} → ${GREEN}${r} ms${RESET}" || echo -e " ${ip} → ${RED}超时/不可达${RESET}" + done + TELECOM_AVG_LATENCY=$(avg_latency_for TELECOM_IPS) + + echo -e "\n ${BOLD}▶ 联通${RESET}" + for ip in "${UNICOM_IPS[@]}"; do + local r; r=$(ping -c 3 -W 3 "$ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}') + [[ -n "$r" ]] && echo -e " ${ip} → ${GREEN}${r} ms${RESET}" || echo -e " ${ip} → ${RED}超时/不可达${RESET}" + done + UNICOM_AVG_LATENCY=$(avg_latency_for UNICOM_IPS) + + echo -e "\n ${BOLD}▶ 移动${RESET}" + for ip in "${MOBILE_IPS[@]}"; do + local r; r=$(ping -c 3 -W 3 "$ip" 2>/dev/null | tail -1 | awk -F'/' '{print $5}') + [[ -n "$r" ]] && echo -e " ${ip} → ${GREEN}${r} ms${RESET}" || echo -e " ${ip} → ${RED}超时/不可达${RESET}" + done + MOBILE_AVG_LATENCY=$(avg_latency_for MOBILE_IPS) +} + +# ── 回程路由追踪 + 线路识别 ─────────────────────────────────── +# 返回识别到的线路标签,写入全局变量 _DETECTED_LINE +_DETECTED_LINE="unknown" +detect_line_from_trace() { + local trace_output="$1" + local lower + lower=$(echo "$trace_output" | tr '[:upper:]' '[:lower:]') + + if echo "$lower" | grep -qE 'as4809|cn2|cnc-gia'; then + _DETECTED_LINE="cn2" + elif echo "$lower" | grep -qE 'as58807|cmin2'; then + _DETECTED_LINE="cmin2" + elif echo "$lower" | grep -qE 'as9929|cnc-backbone|cuii'; then + _DETECTED_LINE="9929" + elif echo "$lower" | grep -qE 'as4837|cu169|169骨干'; then + _DETECTED_LINE="4837" + elif echo "$lower" | grep -qE 'as9808|cmnet'; then + _DETECTED_LINE="cmnet" + elif echo "$lower" | grep -qE 'as4134|chinanet|163骨干'; then + _DETECTED_LINE="163" + else + _DETECTED_LINE="unknown" + fi +} + +return_route_nexttrace() { + local label=$1 + local ip=$2 + local color=$3 + local result_var=$4 # 写回哪个全局变量 + + echo -e "\n ${color}${BOLD}▶ ${label} 回程路由 → ${ip}${RESET}" + echo -e " ${CYAN}$(printf '─%.0s' {1..48})${RESET}" + + local trace_output="" + if command -v nexttrace &>/dev/null; then + trace_output=$(nexttrace --no-rdns "$ip" 2>/dev/null) + echo "$trace_output" | grep -v "^$" | sed 's/^/ /' + else + echo -e " ${RED}nexttrace 未安装,使用 traceroute 替代(无ASN信息)${RESET}" + trace_output=$(traceroute -n -m 20 "$ip" 2>/dev/null) + echo "$trace_output" | sed 's/^/ /' + fi + + detect_line_from_trace "$trace_output" + eval "$result_var=\"$_DETECTED_LINE\"" +} + +return_route_test() { + print_section "📥 回程路由追踪(VPS → 国内三网)" + echo -e " ${YELLOW}提示: 关注 AS 号码${RESET}" + echo -e " ${YELLOW} 电信CN2: as4809 普通163: as4134${RESET}" + echo -e " ${YELLOW} 联通9929: as9929 普通169: as4837${RESET}" + echo -e " ${YELLOW} 移动CMIN2: as58807 普通CMNet: as9808${RESET}" + + return_route_nexttrace "电信" "$TELECOM_RETURN" "$RED" "TELECOM_RETURN_LINE" + return_route_nexttrace "联通" "$UNICOM_RETURN" "$BLUE" "UNICOM_RETURN_LINE" + return_route_nexttrace "移动" "$MOBILE_RETURN" "$GREEN" "MOBILE_RETURN_LINE" +} + +# ── MTR 质量测试 ────────────────────────────────────────────── +mtr_test() { + print_section "📊 MTR 丢包率测试(各发送10个包)" + + _run_mtr() { + local label=$1 ip=$2 loss_var=$3 + echo -e "\n ${BOLD}▶ ${label} (${ip})${RESET}" + if command -v mtr &>/dev/null; then + local mtr_out + mtr_out=$(mtr --report --report-cycles 10 --no-dns "$ip" 2>/dev/null) + echo "$mtr_out" | awk 'NR>1 {printf " %-5s %-18s Loss:%5s Avg:%7s ms\n", $1, $2, $3, $8}' | head -20 + # 取最后一个有效跳的丢包率 + local loss + loss=$(echo "$mtr_out" | awk 'NR>1 && $2!="???" {last=$3} END{print last}' | tr -d '%') + eval "$loss_var=\"${loss:--1}\"" + else + echo -e " ${RED}mtr 未安装${RESET}" + fi + } + + _run_mtr "电信上海" "$TELECOM_RETURN" "TELECOM_LOSS" + _run_mtr "联通武汉" "$UNICOM_RETURN" "UNICOM_LOSS" + _run_mtr "移动北京" "$MOBILE_RETURN" "MOBILE_LOSS" +} + +# ── 线路评级 ───────────────────────────────────────────────── +# 入参: 线路标签字符串,返回评分 0~5 和颜色 +rate_line() { + local line=$1 + case "$line" in + cn2) echo "5|${GREEN}|CN2/GIA ⭐⭐⭐⭐⭐ 顶级优化线路" ;; + 9929) echo "4|${GREEN}|AS9929 ⭐⭐⭐⭐ 联通精品线路" ;; + cmin2) echo "4|${GREEN}|CMIN2 ⭐⭐⭐⭐ 移动精品线路" ;; + 4837) echo "2|${YELLOW}|AS4837 ⭐⭐ 联通普通169骨干" ;; + cmnet) echo "2|${YELLOW}|CMNet ⭐⭐ 移动普通骨干" ;; + 163) echo "1|${YELLOW}|AS4134 ⭐ 电信普通163骨干" ;; + unknown) echo "0|${RED}|未识别 ❓ 无法判断线路质量" ;; + *) echo "0|${RED}|未知 ❓ 无法判断线路质量" ;; + esac +} + +# 延迟评级 +rate_latency() { + local ms=$1 + if [[ "$ms" == "-1" ]]; then + echo "${RED}超时/全部不可达" + elif awk "BEGIN{exit !($ms < 150)}"; then + echo "${GREEN}${ms} ms 极佳 (<150ms)" + elif awk "BEGIN{exit !($ms < 200)}"; then + echo "${GREEN}${ms} ms 良好 (<200ms)" + elif awk "BEGIN{exit !($ms < 250)}"; then + echo "${YELLOW}${ms} ms 一般 (<250ms)" + else + echo "${RED}${ms} ms 较差 (>250ms)" + fi +} + +# 丢包评级 +rate_loss() { + local loss=$1 + if [[ "$loss" == "-1" ]]; then + echo "${YELLOW}未测试" + elif awk "BEGIN{exit !($loss == 0)}"; then + echo "${GREEN}0% 无丢包" + elif awk "BEGIN{exit !($loss <= 2)}"; then + echo "${GREEN}${loss}% 正常" + elif awk "BEGIN{exit !($loss <= 5)}"; then + echo "${YELLOW}${loss}% 轻微丢包" + elif awk "BEGIN{exit !($loss <= 15)}"; then + echo "${RED}${loss}% 丢包明显" + else + echo "${RED}${loss}% 丢包严重" + fi +} + +# ── 综合评估 ───────────────────────────────────────────────── +evaluate() { + print_section "📊 综合评估报告" + + local isp_names=("电信" "联通" "移动") + local return_lines=("$TELECOM_RETURN_LINE" "$UNICOM_RETURN_LINE" "$MOBILE_RETURN_LINE") + local latencies=("$TELECOM_AVG_LATENCY" "$UNICOM_AVG_LATENCY" "$MOBILE_AVG_LATENCY") + local losses=("$TELECOM_LOSS" "$UNICOM_LOSS" "$MOBILE_LOSS") + local colors=("$RED" "$BLUE" "$GREEN") + + local total_score=0 + local scored_count=0 + + printf "\n %-6s %-28s %-22s %-14s\n" "运营商" "回程线路" "去程延迟" "丢包率" + echo -e " ${BLUE}$(printf '─%.0s' {1..75})${RESET}" + + for i in 0 1 2; do + local isp="${isp_names[$i]}" + local line="${return_lines[$i]}" + local lat="${latencies[$i]}" + local loss="${losses[$i]}" + local color="${colors[$i]}" + + local rate_info; rate_info=$(rate_line "$line") + local score="${rate_info%%|*}" + local line_color; line_color=$(echo "$rate_info" | cut -d'|' -f2) + local line_label; line_label=$(echo "$rate_info" | cut -d'|' -f3) + + local lat_str; lat_str=$(rate_latency "$lat") + local loss_str; loss_str=$(rate_loss "$loss") + + printf " ${color}${BOLD}%-6s${RESET} ${line_color}%-28s${RESET} ${lat_str}%-0s${RESET} ${loss_str}%-0s${RESET}\n" \ + "$isp" "$line_label" "" "" + + if [[ "$score" -gt 0 ]]; then + total_score=$((total_score + score)) + ((scored_count++)) + fi + done + + echo -e " ${BLUE}$(printf '─%.0s' {1..75})${RESET}" + + # ── 总体评语 ────────────────────────────────────────────── + echo "" + local avg_score=0 + [[ $scored_count -gt 0 ]] && avg_score=$((total_score / scored_count)) + + local verdict="" + local verdict_color="" + if [[ $avg_score -ge 4 ]]; then + verdict_color="${GREEN}" + verdict="优质线路,三网均走精品通道,适合对延迟敏感的业务(游戏/直播/企业)" + elif [[ $avg_score -eq 3 ]]; then + verdict_color="${GREEN}" + verdict="良好线路,部分运营商走优化通道,日常使用体验较好" + elif [[ $avg_score -eq 2 ]]; then + verdict_color="${YELLOW}" + verdict="普通线路,走普通骨干网,延迟和稳定性一般,适合轻度使用" + elif [[ $avg_score -eq 1 ]]; then + verdict_color="${YELLOW}" + verdict="较差线路,高峰期可能拥堵,建议评估是否符合使用需求" + else + verdict_color="${RED}" + verdict="线路质量未知或较差,建议进一步测试或联系商家确认" + fi + + echo -e " ${BOLD}总体评价: ${verdict_color}${verdict}${RESET}" + + # ── 分运营商建议 ────────────────────────────────────────── + echo "" + echo -e " ${BOLD}分运营商建议:${RESET}" + for i in 0 1 2; do + local isp="${isp_names[$i]}" + local line="${return_lines[$i]}" + local lat="${latencies[$i]}" + local tip="" + + case "$line" in + cn2|9929|cmin2) + tip="✅ 线路优质,推荐使用" + [[ $(awk "BEGIN{print ($lat > 0 && $lat > 220)}") == "1" ]] && \ + tip="${tip}(延迟略高,属正常范围)" + ;; + 4837|cmnet|163) + tip="⚠️ 普通骨干线路,高峰期可能拥堵,敏感业务慎用" + ;; + unknown) + tip="❓ 线路未能识别,建议手动查看回程路由中的 AS 号" + ;; + esac + echo -e " ${colors[$i]}${isp}${RESET}: ${tip}" + done + + echo "" + echo -e " ${CYAN}如需可视化路由图,访问 nexttrace 输出的 MapTrace URL${RESET}" + echo -e " ${CYAN}如需重新测试: bash route_test.sh${RESET}" + echo "" +} + +# ── 主流程 ──────────────────────────────────────────────────── +main() { + check_root + print_banner + get_vps_info + install_deps + outbound_test + return_route_test + mtr_test + evaluate +} + +main