#!/usr/bin/env bash # # TcpQuality Canvas 持续滚动监控监测脚本 v0.9 # 参数: [-v4] [-v6] [-bj] [-gd] -t 0 (省份缩写/运行时间/默认是120秒) ####### # 基于TcpQuality代码修改并使用其API。鸣谢! # https://github.com/ibsgss/TcpQuality ###### set -e # ===================== 全局配置区(手动调速参数) ===================== DEFAULT_PORT="80" # 默认全局缺省端口 MAX_PREWARM_JOBS=15 # 启动预热并发数限制 PING_INTERVAL=1 # 每个独立节点在后台的滚动探测基础间隔(单位:秒) # ===================================================================== # NixOS 临时运行环境自适应 is_nixos() { [ -e /etc/NIXOS ] || { [ -r /etc/os-release ] && grep -Eq '^ID=(nixos|"nixos")$' /etc/os-release; }; } bootstrap_nixos_environment() { local temp_script; local -a nix_packages is_nixos || return 0 [ "${TCPQUALITY_NIX_BOOTSTRAPPED:-0}" -eq 1 ] && return 0 if ! command -v nix >/dev/null 2>&1; then exit 1; fi temp_script=$(mktemp /tmp/tcpquality-canvas.XXXXXX.sh) cat "$0" > "$temp_script"; chmod 0755 "$temp_script"; trap 'rm -f -- "$temp_script"' EXIT nix_packages=(nixpkgs#bash nixpkgs#coreutils nixpkgs#curl nixpkgs#findutils nixpkgs#gawk nixpkgs#gnugrep nixpkgs#gnused nixpkgs#iproute2 nixpkgs#iputils nixpkgs#jq nixpkgs#kmod nixpkgs#ncurses nixpkgs#traceroute) exec env TCPQUALITY_NIX_BOOTSTRAPPED=1 TCPQUALITY_NIX_TEMP_SCRIPT="$temp_script" NIXPKGS_ALLOW_UNFREE=1 nix --extra-experimental-features 'nix-command flakes' shell --impure "${nix_packages[@]}" --command bash "$temp_script" "$@" } bootstrap_nixos_environment "$@" ONLY_IPV4=0; ONLY_IPV6=0 TOTAL_TIMEOUT=120 SELECTED_PROVINCES="" # 颜色与基础控制字符定义 RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m' BLUE=$'\033[0;34m'; CYAN=$'\033[0;36m'; MAGENTA=$'\033[0;35m' WHITE=$'\033[1;37m'; BOLD=$'\033[1m'; DIM=$'\033[2m' NC=$'\033[0m'; EL=$'\033[K' CLEAR=$'\033[H\033[J';CURSOR_HOME=$'\033[H' # 双栈识别状态机 IPV4_WORK=0; IPV6_WORK=0 GET_NODES_URL="${GET_NODES_URL:-https://tcpquality.ibsgss.uk/getNodes}" REMOTE_CDN4_NODES=() REMOTE_CDN6_NODES=() RESULT_DIR=$(mktemp -d) LAST_VIEW_MODE="" # 临终平铺静态大盘:原汁原味恢复 Master 运行态 Header,顺序平铺 v4 与 v6 简洁大表 cleanup_all_processes() { local exit_code=$? trap - EXIT SIGINT SIGTERM set +e local pids=$(jobs -p) if [ "$exit_code" -ne 0 ] || [ "$SECONDS" -le 2 ]; then if [ -n "$pids" ]; then exec 2>/dev/null; kill -9 $pids 2>/dev/null || true; fi rm -rf "$RESULT_DIR" exit "$exit_code" fi printf "%s" "$CLEAR" local final_snap=$(mktemp) cat "$RESULT_DIR"/stats_* > "$final_snap" 2>/dev/null || true local r_file="$RESULT_DIR/route_map.txt" [ ! -f "$r_file" ] && r_file="/dev/null" local total_secs=$SECONDS local current_round=$(awk -F'|' 'BEGIN{s=0;n=0} {s+=$6; n++} END{if(n>0) print int(s/n)+1; else print 1}' "$final_snap") local r_status="${GREEN}回程识别已就绪 (静态复盘大盘)${NC}" if [ ! -s "$RESULT_DIR/route_map.txt" ]; then r_status="${YELLOW}回程识别未完成${NC}"; fi printf "%s\n" "${BOLD}${CYAN}TcpQuality Canvas 持续滚动监控监测脚本 v0.9${NC}" printf "%s\n" "${DIM}------------------------------------------------------------${NC}" printf "报告时间:$(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S CST (北京时间)')\n" printf "运行时间:${BOLD}${GREEN}已运行 ${total_secs}秒${NC} | ${GREEN}策略: 触发设定时限,安全关停停机${NC}\n" printf "当前周期:${BOLD}${YELLOW}第 $current_round 轮全面盘点${NC}\n" printf "回程状态:${r_status}\n\n" if grep -q "^4|" "$final_snap" 2>/dev/null; then local snap_view4=$(mktemp) grep "^4|" "$final_snap" | sort -t'|' -k2 > "$snap_view4" 2>/dev/null || true show_family_results "IPv4" "$snap_view4" "4" "$r_file" rm -f "$snap_view4" fi if grep -q "^6|" "$final_snap" 2>/dev/null; then printf "\n%s\n" "${BOLD}${CYAN}------------------------------- IPv6 简洁延伸视图 -------------------------------${NC}" local snap_view6=$(mktemp) grep "^6|" "$final_snap" | sort -t'|' -k2 > "$snap_view6" 2>/dev/null || true show_family_results "IPv6" "$snap_view6" "6" "$r_file" rm -f "$snap_view6" fi printf "\n%s\n" "${BOLD}${MAGENTA}=== 监测任务结束,当前全网历史累计监测静态大表已就绪 ===${NC}" if [ -n "$pids" ]; then exec 2>/dev/null; kill -9 $pids 2>/dev/null || true; fi rm -rf "$RESULT_DIR" "$final_snap" exit 0 } trap cleanup_all_processes EXIT SIGINT SIGTERM detect_ip_stack() { local has_v4_route=0; local has_v6_route=0 if command -v ip &>/dev/null; then if ip route show default 2>/dev/null | grep -q "dev"; then has_v4_route=1; fi if ip -6 route show default 2>/dev/null | grep -q "dev"; then has_v6_route=1; fi fi if [ "$has_v4_route" -eq 1 ] || curl -s4 --connect-timeout 2 https://www.cloudflare.com >/dev/null 2>&1; then IPV4_WORK=1; else IPV4_WORK=0; fi if [ "$has_v6_route" -eq 1 ] || curl -s6 --connect-timeout 2 https://www.cloudflare.com >/dev/null 2>&1; then IPV6_WORK=1; else IPV6_WORK=0; fi } ipv4_available() { [ "$IPV4_WORK" -eq 1 ]; } ipv6_available() { [ "$IPV6_WORK" -eq 1 ]; } province_from_code() { local code=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/^-//') case "$code" in he|河北) echo "河北" ;; sx|山西) echo "山西" ;; ln|辽宁) echo "辽宁" ;; jl|吉林) echo "吉林" ;; hl|黑龙江) echo "黑龙江" ;; js|江苏) echo "江苏" ;; zj|浙江) echo "浙江" ;; ah|安徽) echo "安徽" ;; fj|福建) echo "福建" ;; jx|江西) echo "江西" ;; sd|山东) echo "山东" ;; ha|河南) echo "河南" ;; hb|湖北) echo "湖北" ;; hn|湖南) echo "湖南" ;; gd|广东) echo "广东" ;; hi|海南) echo "海南" ;; sc|四川) echo "四川" ;; gz|贵州) echo "贵州" ;; yn|云南) echo "云南" ;; sn|陕西) echo "陕西" ;; gs|甘肃) echo "甘肃" ;; qh|青海) echo "青海" ;; nm|内蒙古) echo "内蒙古" ;; gx|广西) echo "广西" ;; xz|西藏) echo "西藏" ;; nx|宁夏) echo "宁夏" ;; xj|新疆) echo "新疆" ;; bj|北京) echo "北京" ;; tj|天津) echo "天津" ;; sh|上海) echo "上海" ;; cq|重庆) echo "重庆" ;; *) return 1 ;; esac } add_province_filter() { local province=$(province_from_code "$1") || return 1; case "$SELECTED_PROVINCES" in *"|$province|"*) ;; *) SELECTED_PROVINCES="${SELECTED_PROVINCES}|${province}|" ;; esac; } province_selected() { [ -z "$SELECTED_PROVINCES" ] || [[ "$SELECTED_PROVINCES" == *"|$1|"* ]]; } load_remote_nodes() { local tmp=$(mktemp); local sep="?"; [[ "$GET_NODES_URL" == *"?"* ]] && sep="&" if ! curl -fsSL --connect-timeout 5 --max-time 30 "${GET_NODES_URL}${sep}format=tsv" > "$tmp" 2>/dev/null; then rm -f "$tmp"; return 1; fi while IFS=$'\t' read -r type family prov isp host ip port target backup_host backup_ip backup_port rest; do [ "$type" = "type" ] || [ -z "$ip" ] && continue case "$family" in 4) REMOTE_CDN4_NODES+=("$prov|$isp|$host|$ip|${port:-80}|$backup_host|$backup_ip|${backup_port:-80}") ;; 6) REMOTE_CDN6_NODES+=("$prov|$isp|$host|$ip|${port:-80}|$backup_host|$backup_ip|${backup_port:-80}") ;; esac done < "$tmp"; rm -f "$tmp"; return 0 } print_cdn_entries() { if [ "$1" = "6" ]; then printf "%s\n" "${REMOTE_CDN6_NODES[@]}"; else printf "%s\n" "${REMOTE_CDN4_NODES[@]}"; fi; } # 后台回程骨干网识别引擎 extract_trace_ips() { awk '/^#/ || /^target/ || /^traceroute/ { next } { for (i = 1; i <= NF; i++) { field = $i; gsub(/[^0-9A-Fa-f:.%]/, " ", field); count = split(field, tokens, /[[:space:]]+/); for (j = 1; j <= count; j++) { token = tokens[j]; sub(/%.*/, "", token); if (token ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ || token ~ /:/) print token } } }' "$1" } query_cymru_asn() { local req_file=$(mktemp); { echo "begin"; echo "verbose"; sort -u "$1"; echo "end"; } > "$req_file" bash -c 'exec 3<>/dev/tcp/whois.cymru.com/43; cat "$1" >&3; cat <&3' _ "$req_file" > "$2" 2>/dev/null || true; rm -f "$req_file" } build_asn_map() { awk -F'|' 'NR == 1 { next } { asn = $1; ip = $2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", asn); gsub(/^[[:space:]]+|[[:space:]]+$/, "", ip); if (asn ~ /^[0-9]+$/) print ip "|" asn }' "$1" > "$2"; } route_label_from_ip_trace() { awk -F'|' ' function infer_asn(ip) { if (ip ~ /^59\.43\./) return "4809"; if (ip ~ /^202\.97\./ || ip ~ /^202\.96\./) return "4134" if (ip ~ /^219\.158\./) return "4837"; if (ip ~ /^221\.183\./) return "9808" if (ip ~ /^103\.214\./) return "10099"; if (ip ~ /^210\.14\./) return "9929" return "" } FILENAME == ARGV[1] { asn_by_ip[$1] = $2; next } FILENAME == ARGV[2] { ip = $0; if (seen_ip[ip]++) next; asn = asn_by_ip[ip]; if (asn == "") asn = infer_asn(ip); max_hop++; asns[max_hop] = asn; next } END { has_4809=0; has_9929=0; has_10099=0; has_58807=0; has_4837=0; has_9808=0; has_4134=0; for (h = 1; h <= max_hop; h++) { if (asns[h] == "4809") has_4809 = 1; if (asns[h] == "9929") has_9929 = 1 if (asns[h] == "10099") has_10099 = 1; if (asns[h] == "58807") has_58807 = 1 if (asns[h] == "4837") has_4837 = 1; if (asns[h] == "9808" || asns[h] == "58453") has_9808 = 1 if (asns[h] == "4134") has_4134 = 1 } if (has_4809) { print "CN2GIA"; exit }; if (has_9929) { print "9929"; exit } if (has_10099) { print "10099"; exit }; if (has_58807) { print "CMIN2"; exit } if (has_4837) { print "4837"; exit }; if (has_9808) { print "CMI"; exit } if (has_4134) { print "163"; exit } print "普通三网" } ' "$2" "$3" "$1" } route_trace_one() { local family="$1" proto="$2" prov="$3" isp="$4" host="$5" idx="$6" port="$7" ip="$8" prefix="$9" local outfile="${RESULT_DIR}/${prefix}_${idx}"; local trace_file="${RESULT_DIR}/${prefix}_trace_${idx}" if [ -z "$ip" ] || [ "$ip" = "0" ]; then echo "FAIL|$family|$prov|$isp|$proto|$host|NO_IP" > "$outfile"; return; fi local -a args=(-n "-${family}" -T -p "$7" -q 1 -w 1 -m 20 "$ip" 44) local output=$(traceroute "${args[@]}" 2>&1 || true) { printf "# %s|%s\n" "$prov" "$isp"; echo "$output"; } > "$trace_file" if extract_trace_ips "$trace_file" | grep -q .; then echo "TRACE|$family|$prov|$isp|$proto|$host|$idx" > "$outfile"; else echo "FAIL|$family|$prov|$isp|$proto|$host|TIMEOUT" > "$outfile"; fi } run_bg_route_detection() { local idx=0; local r_raw="$RESULT_DIR/r_raw.txt"; local ip_f="$RESULT_DIR/r_ips.txt"; local cym_f="$RESULT_DIR/r_cym.txt"; local amap_f="$RESULT_DIR/r_amap.txt" for node_entry in "${POLL_QUEUE[@]}"; do IFS='|' read -r family prov isp host fixed_ip port <<< "$node_entry" idx=$((idx + 1)) while [ "$(jobs -pr | wc -l | tr -cd '0-9')" -ge 8 ]; do sleep 0.2; done route_trace_one "$family" "tcp" "$prov" "$isp" "$host" "$idx" "$port" "$fixed_ip" "bgroute" & done; wait for i in $(seq 1 "$idx"); do [ -f "${RESULT_DIR}/bgroute_${i}" ] && cat "${RESULT_DIR}/bgroute_${i}" >> "$r_raw" [ -f "${RESULT_DIR}/bgroute_trace_${i}" ] && extract_trace_ips "${RESULT_DIR}/bgroute_trace_${i}" >> "$ip_f" done; sort -u "$ip_f" -o "$ip_f" 2>/dev/null || true if [ -s "$ip_f" ]; then query_cymru_asn "$ip_f" "$cym_f"; build_asn_map "$cym_f" "$amap_f"; fi if [ -f "$r_raw" ]; then while IFS='|' read -r status family prov isp protocol host value; do if [ -f "$RESULT_DIR" ] || [ -d "$RESULT_DIR" ]; then if [ "$status" = "TRACE" ] && [ -f "${RESULT_DIR}/bgroute_trace_${value}" ]; then extract_trace_ips "${RESULT_DIR}/bgroute_trace_${value}" > "${RESULT_DIR}/bgroute_trace_${value}.ips" local label=$(route_label_from_ip_trace "${RESULT_DIR}/bgroute_trace_${value}" "$amap_f" "${RESULT_DIR}/bgroute_trace_${value}.ips" "$isp") echo "OK|$family|$prov|$isp|tcp|$host|$label" >> "$RESULT_DIR/route_map.txt" else echo "FAIL|$family|$prov|$isp|tcp|$host|普通线路" >> "$RESULT_DIR/route_map.txt"; fi fi done < "$r_raw" fi; rm -f "$r_raw" "$ip_f" "$cym_f" "$amap_f" 2>/dev/null || true } # ===================== 💎 【ENVIRON 像素网格彻底解耦大渲染器】 ===================== show_provider_summary() { local file="$1"; local route_file="${2:-/dev/null}"; local target_fam="${3:-4}" green="$GREEN" yellow="$YELLOW" red="$RED" cyan="$CYAN" white="$WHITE" dim="$DIM" bold="$BOLD" magenta="$MAGENTA" nc="$NC" el="$EL" tfam="$target_fam" awk -F'|' ' BEGIN { g_len = length("国"); green=ENVIRON["green"]; yellow=ENVIRON["yellow"]; red=ENVIRON["red"]; cyan=ENVIRON["cyan"]; white=ENVIRON["white"]; dim=ENVIRON["dim"]; bold=ENVIRON["bold"]; magenta=ENVIRON["magenta"]; nc=ENVIRON["nc"]; el=ENVIRON["el"]; tfam=ENVIRON["tfam"]; } function pad_custom(text, total_w, w, i, c, pad) { w = 0; for (i = 1; i <= length(text); i++) { c = substr(text, i, 1); if (c ~ /[^\x00-\x7f]/) { w += (g_len == 1) ? 2 : 2/3; } else { w += 1; } } w = int(w + 0.5); pad = total_w - w; if (pad < 0) pad = 0; return text sprintf("%*s", pad, ""); } # 🌟【像素级格子扩容】:将 latency 宽度死锁卡在 7 个字符槽,确保 1177ms* 的注入也绝不破坏垂直大网格 function format_summary_cell(label, latency, loss, sent, lat_c, loss_c) { return white pad_custom(label, 10) nc " " lat_c sprintf("%-7s", latency) nc " " loss_c sprintf("%-7s", loss) nc " " dim sprintf("(%-4d)", sent) nc; } function get_cell(p, i, val) { val = data[p SUBSEP i]; if (val == "") return format_summary_cell("", " - ", " - ", 0, dim, dim); return val; } FILENAME == ARGV[1] && NF >= 7 { if ($2 == tfam) { if ($1 == "OK") route[$3 SUBSEP $4] = $7; else route[$3 SUBSEP $4] = ""; } next } { fam=$1; prov=$2; isp=$3; host=$4; ip=$5; sent=$6; rcvd=$7; lat_sum=$8; if (fam != tfam) next; label = ((prov SUBSEP isp) in route) ? route[prov SUBSEP isp] : ""; if (!(prov in seen)) { seen[prov] = 1; order[++n] = prov; } if (sent == 0) { data[prov SUBSEP isp] = format_summary_cell(label, " - ", " - ", 0, dim, dim); } else { loss = (sent - rcvd) * 100 / sent; loss_text = sprintf("%.2f%%", loss); lc = (loss > 20)?red:((loss > 0)?yellow:green); if (rcvd > 0) { lat = lat_sum / rcvd; # 🌟【WAF 惩罚智能高亮染色】:若平均时延触及 1000ms+,追加星号标记并无情渲染为独家高级🟣紫色 if (lat >= 1000) { lat_text = sprintf("%.0fms*", lat); ptc = magenta; } else { lat_text = sprintf("%.0fms", lat); ptc = (lat > 240)?red:((lat > 150)?yellow:green); } } else { lat_text = "failed"; ptc = red; } data[prov SUBSEP isp] = format_summary_cell(label, lat_text, loss_text, sent, ptc, lc); } } END { # 🌟【列宽完美重组】:单列对齐补偿扩展为 33 字符,保证标题横向跨度与下方数据单元 100% 垂直咬合 c1 = sprintf("%15s%s%16s", "", "电信", ""); c2 = sprintf("%15s%s%16s", "", "联通", ""); c3 = sprintf("%15s%s%16s", "", "移动", ""); printf " %s%s%s%s %s / %s / %s%s\n", bold, cyan, pad_custom("三网看板", 12), nc, cyan c1 nc, cyan c2 nc, cyan c3 nc, el for (i = 1; i <= n; i++) { prov = order[i]; if (prov == "") continue; printf " %s%s%s %s / %s / %s%s\n", cyan, pad_custom(prov, 12), nc, get_cell(prov, "电信"), get_cell(prov, "联通"), get_cell(prov, "移动"), el; } printf " %s颜色: %s正常%s %s151-240ms或有丢包%s %s>240ms或重度丢包%s %s>=1000ms(触发WAF重传惩罚)*%s%s\n\n", dim, green, dim, yellow, dim, red, dim, magenta, dim, el }' "$route_file" "$file" } show_family_results() { fam="$1" tfam="$3" bold="$BOLD" cyan="$CYAN" green="$GREEN" yellow="$YELLOW" red="$RED" nc="$NC" awk -F'|' ' BEGIN { z=0; y=0; h=0; bold=ENVIRON["bold"]; cyan=ENVIRON["cyan"]; green=ENVIRON["green"]; yellow=ENVIRON["yellow"]; red=ENVIRON["red"]; nc=ENVIRON["nc"]; } { if ($1 != ENVIRON["tfam"]) next; sent=$6; rcvd=$7; if (sent == 0) { h++; next; } loss = (sent - rcvd) * 100 / sent; if (rcvd == 0 || loss > 20) h++; else if (loss == 0) z++; else y++; } END { printf " %s%s%s 统计摘要%s %s零丢包: %d%s %s1-20%%: %d%s %s>20%%: %d%s\033[K\n\n", bold, cyan, ENVIRON["fam"], nc, green, z, nc, yellow, y, nc, red, h, nc }' "$2" local f_num="4"; if [ "$1" = "IPv6" ]; then f_num="6"; fi show_provider_summary "$2" "$4" "$f_num" } # 💎 【纯四层无损轻量探针 - 运营商自适应完全体】 probe_socket_l4() { local family="$1" prov="$2" isp="$3" host="$4" ip="$5" port="$6" local mode_arg="-4" [ "$family" = "6" ] && mode_arg="-6" # 🌟【改进 1】:动态超时墙。联通电信高防卡 2 秒熔断; # 移动在欧洲属于全球绕路高时延航线,放宽到 3 秒,给跨境重传留足物理生存时间 local timeout_sec=2 [ "$isp" = "移动" ] && timeout_sec=3 local t_start=$EPOCHREALTIME # 发起第一轮标准探测 if nc $mode_arg -w $timeout_sec -zv "$ip" "$port" &>/dev/null; then local t_end=$EPOCHREALTIME local sec1=${t_start%.*}; local fra1=${t_start#*.} local sec2=${t_end%.*}; local fra2=${t_end#*.} local int_rtt=$(( (sec2 - sec1) * 1000 + (10#$fra2 - 10#$fra1) / 1000 )) [ "$int_rtt" -le 0 ] && int_rtt=1 echo "OK|$prov|$isp|$host|$ip|1|1|0.00|$int_rtt" else # 🌟【改进 2】:移动专属双倍连击打捞机制 (Deep Salvage) # 如果移动第一发由于踩中高防硬限流被 Drop,立刻原地轻量打捞一次,防止丢包率虚高失真 if [ "$isp" = "移动" ]; then sleep 0.1 t_start=$EPOCHREALTIME if nc $mode_arg -w 3 -zv "$ip" "$port" &>/dev/null; then local t_end=$EPOCHREALTIME local sec1=${t_start%.*}; local fra1=${t_start#*.} local sec2=${t_end%.*}; local fra2=${t_end#*.} local int_rtt=$(( (sec2 - sec1) * 1000 + (10#$fra2 - 10#$fra1) / 1000 )) [ "$int_rtt" -le 0 ] && int_rtt=1 echo "OK|$prov|$isp|$host|$ip|1|1|0.00|$int_rtt" return fi fi # 两轮都没捞回来,实锤发生了真实的物理丢包或彻底阻断 echo "FAIL|$prov|$isp|$host|$ip|1|0|100.00|0" fi } parse_args() { while [ $# -gt 0 ]; do case "$1" in -v4|--v4) ONLY_IPV4=1; shift ;; -v6|--v6) ONLY_IPV6=1; shift ;; -t|--time) TOTAL_TIMEOUT="$2"; shift 2 ;; --province) add_province_filter "$2"; shift 2 ;; -??|-???) if add_province_filter "$1"; then shift; else shift; fi ;; *) shift ;; esac done } # ===================== 💎 【分时 6 秒平滑轮切中枢】 ===================== redraw_dashboard() { local snap_all="$RESULT_DIR/snap_all.txt" cat "$RESULT_DIR"/stats_* > "$snap_all" 2>/dev/null || true local has_v4=0; local has_v6=0 if grep -q "^4|" "$snap_all" 2>/dev/null; then has_v4=1; fi if grep -q "^6|" "$snap_all" 2>/dev/null; then has_v6=1; fi local view_mode="IPv4" if [ "$has_v4" -eq 1 ] && [ "$has_v6" -eq 1 ]; then if [ $((SECONDS / 6 % 2)) -eq 0 ]; then view_mode="IPv4"; else view_mode="IPv6"; fi elif [ "$has_v6" -eq 1 ]; then view_mode="IPv6"; fi if [ "$view_mode" != "$LAST_VIEW_MODE" ]; then printf "%s" "$CLEAR" LAST_VIEW_MODE="$view_mode" else printf "%s" "$CURSOR_HOME" fi local r_status="${YELLOW}后台回程精准识别中 (请稍候...)${NC}"; local r_file="$RESULT_DIR/route_map.txt" if [ -s "$r_file" ]; then r_status="${GREEN}回程识别已就绪 (表格内已自动切换对应线路分类)${NC}"; else r_file="/dev/null"; fi local time_bar=""; local total_secs=$SECONDS if [ "${TOTAL_TIMEOUT:-0}" -gt 0 ]; then local remain=$(( TOTAL_TIMEOUT - total_secs )); [ "$remain" -lt 0 ] && remain=0 time_bar=" | ${YELLOW}自动关停倒计时: ${remain}秒${NC}" else time_bar=" | ${GREEN}策略: 永续看盘模式 (低载无损自适应)${NC}"; fi printf "%s%s\n" "${BOLD}${CYAN}TcpQuality Canvas 持续滚动监控监测脚本 v0.9${NC}" "${EL}" printf "%s%s\n" "${DIM}------------------------------------------------------------${NC}" "${EL}" printf "报告时间:$(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S CST (北京时间)')%s\n" "${EL}" printf "运行时间:${BOLD}${GREEN}已运行 ${total_secs}秒${NC}${time_bar}%s\n" "${EL}" local current_round=$(awk -F'|' 'BEGIN{s=0;n=0} {s+=$6; n++} END{if(n>0) print int(s/n)+1; else print 1}' "$snap_all") printf "当前周期:${BOLD}${YELLOW}第 $current_round 轮全面盘点${NC} | 智能分时:${BOLD}${MAGENTA}${view_mode}${NC} 视图 (6秒轮切)%s\n" "${EL}" printf "回程状态:${r_status}%s\n\n" "${EL}" local tfam="4"; [ "$view_mode" = "IPv6" ] && tfam="6" local snap_view="$RESULT_DIR/snap_view.txt" grep "^${tfam}|" "$snap_all" | sort -t'|' -k2 > "$snap_view" 2>/dev/null || true if [ -s "$snap_view" ]; then show_family_results "$view_mode" "$snap_view" "$tfam" "$r_file"; fi rm -f "$snap_view" "$snap_all" } # ===================== 💎 【核心大编排入口】 ===================== main() { parse_args "$@" command -v nc &>/dev/null || { echo -e "${RED}[X] 错误熔断:当前系统缺少 nc (netcat) 二进制依赖。请先执行安装:\n -> Ubuntu/Debian: apt install -y netcat-openbsd\n -> CentOS/RedHat: yum install -y nc${NC}"; exit 1; } command -v curl &>/dev/null || { echo -e "${RED}[X] 错误熔断:当前系统缺少 curl 二进制依赖。${NC}"; exit 1; } detect_ip_stack; load_remote_nodes local ipv4_enabled=0; local ipv6_enabled=0 if [ "$ONLY_IPV6" -ne 1 ] && ipv4_available; then ipv4_enabled=1; fi if [ "$ONLY_IPV4" -ne 1 ] && ipv6_available; then ipv6_enabled=1; fi local dashboard_refresh=1 if [ "${TOTAL_TIMEOUT:-120}" -eq 0 ]; then dashboard_refresh=2 if [ "$PING_INTERVAL" -eq 1 ]; then PING_INTERVAL=3; fi fi NODES_QUEUE=() local -a families=() [ "$ipv4_enabled" -eq 1 ] && families+=(4) [ "$ipv6_enabled" -eq 1 ] && families+=(6) for family in "${families[@]}"; do while IFS='|' read -r prov isp host fixed_ip port backup_host backup_ip backup_port; do province_selected "$prov" || continue local final_port=${port:-$DEFAULT_PORT} [ "$DEFAULT_PORT" = "80" ] && [ "$port" = "80" ] && final_port="80" NODES_QUEUE+=("$family|$prov|$isp|$host|$fixed_ip|$final_port|$backup_host|$backup_ip|${backup_port:-80}") done < <(print_cdn_entries "$family") done if [ ${#NODES_QUEUE[@]} -eq 0 ]; then echo -e "${RED}[X] 错误熔断:未在当前网络协议栈下建树有效可用节点。${NC}"; exit 1; fi # 并发预热打捞矩阵 local limit_jobs=$(echo "${MAX_PREWARM_JOBS:-15}" | tr -cd '0-9'); limit_jobs=${limit_jobs:-15} echo -e "${CYAN}[i] 正在执行多线程节点可用性预检筛选 (限制并发数: ${limit_jobs})...${NC}" for node_entry in "${NODES_QUEUE[@]}"; do local current_jobs=$(jobs -pr | wc -l | tr -cd '0-9'); current_jobs=${current_jobs:-0} while [ "${current_jobs:-0}" -ge "$limit_jobs" ]; do local done_cnt=$(find "$RESULT_DIR" -maxdepth 1 -name "pre_done_*" 2>/dev/null | wc -l | tr -cd '0-9') echo -ne "\r -> 正在构建活节点白名单及本地快照... (${done_cnt:-0}/${#NODES_QUEUE[@]})" sleep 0.05 current_jobs=$(jobs -pr | wc -l | tr -cd '0-9'); current_jobs=${current_jobs:-0} done IFS='|' read -r family prov isp host fixed_ip port rest <<< "$node_entry" local k="${family}_${prov}_${isp}" ( trap 'touch "$RESULT_DIR/pre_done_${k}"' EXIT IFS='|' read -r family prov isp host fixed_ip port b_host b_ip b_port <<< "$node_entry" local max_retries=3 [ "$isp" = "移动" ] && max_retries=6 local res="" for ((retry=1; retry<=max_retries; retry++)); do res=$(probe_socket_l4 "$family" "$prov" "$isp" "$host" "$fixed_ip" "$port") if [[ "$res" =~ ^OK ]]; then echo "$family|$prov|$isp|$host|$fixed_ip|$port" > "$RESULT_DIR/pre_alive_${k}" echo "$res" > "$RESULT_DIR/pre_res_${k}" exit 0 fi sleep 0.1 done ) 2>/dev/null & sleep 0.05 done local current_jobs=$(jobs -pr | wc -l | tr -cd '0-9'); current_jobs=${current_jobs:-0} while [ "${current_jobs:-0}" -gt 0 ]; do local done_cnt=$(find "$RESULT_DIR" -maxdepth 1 -name "pre_done_*" 2>/dev/null | wc -l | tr -cd '0-9') echo -ne "\r -> 正在构建活节点白名单及本地快照... (${done_cnt:-0}/${#NODES_QUEUE[@]})" sleep 0.05 current_jobs=$(jobs -pr | wc -l | tr -cd '0-9'); current_jobs=${current_jobs:-0} done; wait POLL_QUEUE=() for node_entry in "${NODES_QUEUE[@]}"; do IFS='|' read -r family prov isp rest <<< "$node_entry" local k="${family}_${prov}_${isp}" if [ -f "$RESULT_DIR/pre_alive_${k}" ]; then POLL_QUEUE+=("$(cat "$RESULT_DIR/pre_alive_${k}")"); fi done if [ ${#POLL_QUEUE[@]} -eq 0 ]; then echo -e "\n${RED}[X] 预检失败:存活可用监测节点为 0!${NC}"; exit 1; fi printf "%s" "$CLEAR" run_bg_route_detection 2>/dev/null & # ===================== Workers 集群永续并发 ===================== local pre_idx=0 for node_entry in "${POLL_QUEUE[@]}"; do pre_idx=$((pre_idx + 1)) ( set +e IFS='|' read -r family prov isp host fixed_ip port <<< "$node_entry" local stat_file="$RESULT_DIR/stats_${family}_${prov}_${isp}" local res_file="$RESULT_DIR/pre_res_${family}_${prov}_${isp}" local staggered_delay=$(awk -v idx="$pre_idx" 'BEGIN { printf "%.2f", idx * 0.20 }') sleep "$staggered_delay" local s_sent=0; local s_rcvd=0; local s_lat_sum=0 if [ -f "$res_file" ]; then local pre_content=$(cat "$res_file") if [[ "$pre_content" =~ OK\|[^\|]+\|[^\|]+\|[^\|]+\|[^\|]+\|([0-9]+)\|([0-9]+)\|[^\|]+\|([0-9]+) ]]; then s_sent=${BASH_REMATCH[1]}; s_rcvd=${BASH_REMATCH[2]}; s_lat_sum=${BASH_REMATCH[3]} fi fi echo "$family|$prov|$isp|$host|$fixed_ip|$s_sent|$s_rcvd|$s_lat_sum" > "$stat_file" while true; do # 🌟【优化核心 1】:引入混沌频控。联通移动保持 1 秒高频, # 电信 163 每一轮都在 3~6 秒之间无规则随机抽签,彻底粉碎高防的时间序列流控雷达 local current_interval=$PING_INTERVAL if [ "$isp" = "电信" ]; then current_interval=$(( 3 + RANDOM % 4 )) elif [ "$isp" = "移动" ]; then # 🌟 给移动 CMI 网关减负,默认放宽到 2~4 秒的混沌间隔 current_interval=$(( 2 + RANDOM % 3 )) fi # 挂载基础 Smart Jitter 随机浮点数消解 sleep "${current_interval}.$(( RANDOM % 8 ))" s_sent=$((s_sent + 1)) local loop_res=$(probe_socket_l4 "$family" "$prov" "$isp" "$host" "$fixed_ip" "$port") local l_status="${loop_res%%|*}" if [ "$l_status" = "OK" ]; then s_rcvd=$((s_rcvd + 1)) local l_lat="${loop_res##*|}" s_lat_sum=$((s_lat_sum + l_lat)) # 🌟【优化核心 2】:重传退避叠加。如果在混沌状态下依然撞到 1000ms+ 惩罚 # 说明该节点处于重度敏感期,强制将下一轮基础时钟向后推延 2 秒;一般是163线路 if [ "$l_lat" -ge 1000 ]; then current_interval=$(( current_interval + 2 )) [ "$current_interval" -gt 7 ] && current_interval=7 fi fi echo "$family|$prov|$isp|$host|$fixed_ip|$s_sent|$s_rcvd|$s_lat_sum" > "${stat_file}.tmp" mv "${stat_file}.tmp" "$stat_file" # 🌟【优化核心 3】:套接字微秒级冷却阻尼 # 强制让出 10 毫秒内核控制权,确保下一轮 nc 完美命中全新随机的系统临时源端口 sleep 0.01 done ) 2>/dev/null & done while true; do redraw_dashboard if [ "${TOTAL_TIMEOUT:-0}" -gt 0 ] && [ "${SECONDS:-0}" -ge "${TOTAL_TIMEOUT:-0}" ]; then cleanup_all_processes fi sleep "$dashboard_refresh" done } main "$@"