Files
x-panel/dnsjc.sh
2026-05-03 11:34:48 +08:00

290 lines
9.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -u
set -o pipefail
COUNT=${COUNT:-10}
TOPN=${TOPN:-30}
SLEEP_META=${SLEEP_META:-0.15}
META_CACHE_DIR="${META_CACHE_DIR:-/tmp/dns_meta_cache}"
mkdir -p "$META_CACHE_DIR" >/dev/null 2>&1 || true
# deps
require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "missing dependency: $1"; exit 1; }; }
require_cmd curl
require_cmd awk
require_cmd sed
require_cmd tr
require_cmd grep
require_cmd ping
PING_BIN="ping"
PING6_BIN="ping -6"
if command -v ping6 >/dev/null 2>&1; then
PING6_BIN="ping6"
fi
# color banner (red)
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
RED=$'\033[1;31m'
RESET=$'\033[0m'
else
RED=""
RESET=""
fi
echo ""
printf "%b\n\n" "${RED}X-Panel-Pro 面板〕专属 “服务器 DNS 检测”${RESET}"
# extract valid IPs (IPv4 strict / IPv6 loose)
extract_ips() {
sed -E 's/<[^>]+>/ /g' \
| tr -c '0-9A-Fa-f:.' '\n' \
| grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^([0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}$)' \
| awk '!seen[$0]++' \
| awk -F. '
$0 ~ /:/ { print; next }
NF==4 {
ok=1
for(i=1;i<=4;i++){
if($i !~ /^[0-9]+$/ || $i<0 || $i>255){ ok=0; break }
}
if(ok) print $0
}
'
}
# filter private/reserved IPv4
is_public_ipv4() {
local ip="$1"; IFS=. read -r a b c d <<<"$ip" || return 1
if ((a==10)) || ((a==127)) || ((a==192 && b==168)) || ((a==169 && b==254)) \
|| ((a==172 && b>=16 && b<=31)) || ((a==100 && b>=64 && b<=127)) \
|| ((a==0)) || ((a>=224)); then
return 1
fi
return 0
}
# normalize country to slug
normalize_country_to_slug() {
local raw="$1"
local x compact
x=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')
x=$(printf '%s' "$x" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
compact=$(printf '%s' "$x" | sed -E 's/[^a-z0-9]+//g')
case "$compact" in
jp) echo "japan"; return;;
kr|rok) echo "southkorea"; return;;
us|usa) echo "unitedstates"; return;;
uk|gb) echo "unitedkingdom"; return;;
ae|uae) echo "unitedarabemirates"; return;;
hk) echo "hongkong"; return;;
tw) echo "taiwan"; return;;
cn) echo "china"; return;;
de) echo "germany"; return;;
fr) echo "france"; return;;
it) echo "italy"; return;;
es) echo "spain"; return;;
ru) echo "russia"; return;;
sg) echo "singapore"; return;;
th) echo "thailand"; return;;
vn) echo "vietnam"; return;;
ph) echo "philippines"; return;;
id) echo "indonesia"; return;;
my) echo "malaysia"; return;;
au) echo "australia"; return;;
nz) echo "newzealand"; return;;
nl) echo "netherlands"; return;;
be) echo "belgium"; return;;
pl) echo "poland"; return;;
cz) echo "czechia"; return;;
ch) echo "switzerland"; return;;
at) echo "austria"; return;;
se) echo "sweden"; return;;
no) echo "norway"; return;;
fi) echo "finland"; return;;
pt) echo "portugal"; return;;
ro) echo "romania"; return;;
hu) echo "hungary"; return;;
sk) echo "slovakia"; return;;
si) echo "slovenia"; return;;
gr) echo "greece"; return;;
ie) echo "ireland"; return;;
mx) echo "mexico"; return;;
ca) echo "canada"; return;;
br) echo "brazil"; return;;
ar) echo "argentina"; return;;
cl) echo "chile"; return;;
za) echo "southafrica"; return;;
esac
x=$(printf '%s' "$x" | sed -E 's/[[:space:]]+//g')
echo "$x"
}
# fetch IPs from publicdnsserver page
fetch_country_ips() {
local slug="$1"
local url="https://publicdnsserver.com/${slug}/"
local html
html=$(curl -sL --max-time 15 "$url" || true)
[ -z "$html" ] && { echo ""; return 0; }
printf '%s' "$html" | extract_ips
}
# metadata (ip-api) with 1h cache
get_meta_json() {
local ip="$1"
local cache="$META_CACHE_DIR/$ip.json"
local epoch mtime age
if [ -s "$cache" ]; then
epoch=$(date +%s)
if mtime=$(stat -c %Y "$cache" 2>/dev/null); then
age=$((epoch - mtime))
if [ "$age" -lt 3600 ]; then
cat "$cache"; return 0
fi
fi
fi
local resp
resp=$(curl -s --max-time 5 "http://ip-api.com/json/$ip?fields=status,message,country,city,as,asname,org,isp,query")
if [ -n "$resp" ]; then
printf '%s' "$resp" >"$cache" 2>/dev/null || true
printf '%s' "$resp"
else
printf '{"status":"fail","country":"","city":"","as":"","asname":"","org":"","isp":"","query":"%s"}' "$ip"
fi
sleep "$SLEEP_META"
}
# poor-man's json getter (no jq)
json_get() {
echo "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p"
}
# parse ping output (returns: loss,min,avg,max,mdev)
parse_ping() {
local out; out=$(cat)
local loss min avg max mdev rtt
loss=$(printf '%s\n' "$out" | LC_ALL=C grep -Eo '[0-9]+(\.[0-9]+)?% packet loss' | sed -E 's/%.*//')
[ -z "$loss" ] && loss="100.0"
rtt=$(printf '%s\n' "$out" | LC_ALL=C grep -E 'min/avg/max' | tail -n1 | awk -F'=' '{print $2}' | awk '{print $1}')
if [ -n "$rtt" ]; then
IFS=/ read -r min avg max mdev <<<"$rtt"
else
min="N/A"; avg="N/A"; max="N/A"; mdev="N/A"
fi
printf '%s,%s,%s,%s,%s\n' "$loss" "$min" "$avg" "$max" "$mdev"
}
# main
read -rp "请输入英文国家名(如 Japan / United States / South Korea或 ISO 两字母,如 JP " USER_REGION || USER_REGION=""
SLUG=$(normalize_country_to_slug "${USER_REGION:-}")
[ -z "$SLUG" ] && SLUG="japan"
MAP_IPS_RAW=$(fetch_country_ips "$SLUG")
declare -a TARGETS TMP_LIST RESULTS
declare -A seen
while IFS= read -r ip; do
[ -z "$ip" ] && continue
if [[ "$ip" == *:* ]]; then
TARGETS+=("$ip")
else
if is_public_ipv4 "$ip"; then
TARGETS+=("$ip")
fi
fi
done < <(printf '%s\n' "$MAP_IPS_RAW" | awk 'NF{if(!seen[$0]++){print}}')
for ip in "${TARGETS[@]}"; do
if [ -z "${seen[$ip]+x}" ]; then
TMP_LIST+=("$ip"); seen["$ip"]=1
fi
[ "${#TMP_LIST[@]}" -ge "$TOPN" ] && break
done
TARGETS=("${TMP_LIST[@]}")
for must in 1.1.1.1 8.8.8.8; do
if [ -z "${seen[$must]+x}" ]; then
TARGETS+=("$must"); seen["$must"]=1
fi
done
N=${#TARGETS[@]}
printf "地区: %sslug: %s| 目标数: %d | 每个目标 ping 次数: %d\n" "${USER_REGION:-N/A}" "$SLUG" "$N" "$COUNT"
printf "将测试的目标:%s\n" "$(printf '%s ' "${TARGETS[@]}")"
echo "开始测试 ......"
if [ "$N" -eq 0 ]; then
echo "未找到可用目标。"
exit 0
fi
idx=0
for ip in "${TARGETS[@]}"; do
idx=$((idx+1))
pct=$(( idx * 100 / (N==0 ? 1 : N) ))
printf "进度: [%d/%d | %3d%%] #%d 正在测试: %s\r" "$idx" "$N" "$pct" "$idx" "$ip"
if [[ "$ip" == *:* ]]; then
out=$(LC_ALL=C $PING6_BIN -n -c "$COUNT" -i 0.2 -w $((COUNT+4)) "$ip" 2>&1 || true)
else
out=$(LC_ALL=C $PING_BIN -n -c "$COUNT" -i 0.2 -w $((COUNT+4)) "$ip" 2>&1 || true)
fi
stats=$(printf '%s' "$out" | parse_ping)
IFS=, read -r loss min avg max mdev <<<"$stats"
meta=$(get_meta_json "$ip")
country=$(json_get "$meta" country); [ -z "$country" ] && country="N/A"
city=$(json_get "$meta" city); [ -z "$city" ] && city="N/A"
asfull=$(json_get "$meta" as); [ -z "$asfull" ] && asfull="N/A"
asname=$(json_get "$meta" asname); [ -z "$asname" ] && asname="N/A"
org=$(json_get "$meta" org)
isp=$(json_get "$meta" isp)
asn=$(printf '%s' "$asfull" | sed -n 's/.*\(AS[0-9][0-9]*\).*/\1/p')
[ -z "$asn" ] && asn="N/A"
company="$asname"
[ -z "$company" ] || [ "$company" = "N/A" ] && company="$org"
[ -z "$company" ] || [ "$company" = "N/A" ] && company="$isp"
[ -z "$company" ] && company="N/A"
printf "进度: [%d/%d | %3d%%] #%d 正在测试: %-39s | 丢包 %s%% | 最小 %sms | 平均 %sms | 最大 %sms | 抖动 %sms\n" \
"$idx" "$N" "$pct" "$idx" "$ip" "$loss" "$min" "$avg" "$max" "$mdev"
sortkey="$avg"; [[ "$sortkey" == "N/A" || -z "$sortkey" ]] && sortkey=999999999
RESULTS+=("$sortkey\t$idx\t$ip\t$country/$city\t$asn\t$company\t$loss\t$min\t$avg\t$max\t$mdev")
done
echo
HEADER=$'编号\t目标\t地区\tASN\t公司\t丢包\t最小(ms)\t平均(ms)\t最大(ms)\t抖动'
BODY=$(
printf '%b\n' "${RESULTS[@]}" \
| LC_ALL=C sort -t$'\t' -k1,1n \
| cut -f2- \
| while IFS=$'\t' read -r idx0 ip region asn company loss min avg max mdev; do
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$idx0" "$ip" "$region" "$asn" "$company" \
"$(printf '%.1f%%' "${loss:-0}")" \
"${min:-N/A}" "${avg:-N/A}" "${max:-N/A}" "${mdev:-N/A}"
done
)
if command -v column >/dev/null 2>&1; then
{ printf '%s\n' "$HEADER"; printf '%s\n' "$BODY"; } | column -t -s $'\t'
else
printf "%-4s %-39s %-18s %-8s %-28s %-6s %-9s %-9s %-9s %-7s\n" \
"编号" "目标" "地区" "ASN" "公司" "丢包" "最小(ms)" "平均(ms)" "最大(ms)" "抖动"
printf -- "-----------------------------------------------------------------------------------------------\n"
printf '%s\n' "$BODY" | while IFS=$'\t' read -r idx0 ip region asn company loss min avg max mdev; do
printf "%-4s %-39s %-18s %-8s %-28s %-6s %-9s %-9s %-9s %-7s\n" \
"$idx0" "$ip" "$region" "$asn" "$company" \
"$loss" "$min" "$avg" "$max" "$mdev"
done
fi