mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-03 10:53:56 +08:00
192 lines
6.9 KiB
Bash
Executable File
192 lines
6.9 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
NAME=cf-dns-speedtest
|
|
STATE=/var/lib/$NAME
|
|
RUN=/var/run/$NAME
|
|
CFG=cf_dns_speedtest.config
|
|
LOG_MAX=131072
|
|
|
|
rotate_log() {
|
|
[ -f "$STATE/run.log" ] || return 0
|
|
[ "$(wc -c < "$STATE/run.log")" -le "$LOG_MAX" ] && return 0
|
|
tail -c 65536 "$STATE/run.log" > "$STATE/run.log.new" && mv "$STATE/run.log.new" "$STATE/run.log"
|
|
}
|
|
|
|
log() {
|
|
mkdir -p "$STATE"
|
|
logger -t "$NAME" "$*"
|
|
printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$STATE/run.log"
|
|
rotate_log
|
|
}
|
|
|
|
cfg() { uci -q get "$CFG.$1"; }
|
|
|
|
uint() {
|
|
case "$1" in ''|*[!0-9]*) printf '%s' "$2";; *) printf '%s' "$1";; esac
|
|
}
|
|
|
|
domains() {
|
|
uci -q get "$CFG.domains" | tr ' ' '\n' | sed 's/^\.//;s/\.$//' |
|
|
grep -E '^[A-Za-z0-9]([A-Za-z0-9_-]*[A-Za-z0-9])?([.][A-Za-z0-9]([A-Za-z0-9_-]*[A-Za-z0-9])?)+$' | sort -u
|
|
}
|
|
|
|
valid_ipv4() {
|
|
printf '%s\n' "$1" | awk -F. 'NF==4 { for (i=1;i<=4;i++) if ($i !~ /^[0-9]+$/ || $i>255) exit 1; exit 0 } { exit 1 }'
|
|
}
|
|
|
|
remove_old_routes() {
|
|
local route old port
|
|
if [ -s "$STATE/routes.active" ]; then
|
|
while IFS= read -r route; do
|
|
[ -n "$route" ] && uci -q del_list dhcp.@dnsmasq[0].server="$route"
|
|
done < "$STATE/routes.active"
|
|
elif [ -s "$STATE/domains.active" ]; then
|
|
# Migration from versions before 1.1.0. Only remove domain-scoped
|
|
# routes owned by this package; never touch the generic DNS upstream.
|
|
port="$(uint "$(cfg listen_port)" 5335)"
|
|
while IFS= read -r old; do
|
|
[ -n "$old" ] && uci -q del_list dhcp.@dnsmasq[0].server="/$old/127.0.0.1#$port"
|
|
done < "$STATE/domains.active"
|
|
fi
|
|
}
|
|
|
|
make_config() {
|
|
local ip="$1" port d route
|
|
port="$(uint "$(cfg listen_port)" 5335)"
|
|
[ "$port" -ge 1024 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null || port=5335
|
|
mkdir -p "$STATE"
|
|
domains > "$STATE/domains.new"
|
|
[ -s "$STATE/domains.new" ] || { rm -f "$STATE/domains.new"; log "no valid domains configured"; return 1; }
|
|
: > "$STATE/hosts.txt.new"
|
|
: > "$STATE/routes.new"
|
|
while IFS= read -r d; do
|
|
printf '%s %s\n' "$d" "$ip" >> "$STATE/hosts.txt.new"
|
|
printf '/%s/127.0.0.1#%s\n' "$d" "$port" >> "$STATE/routes.new"
|
|
done < "$STATE/domains.new"
|
|
cat > "$STATE/mosdns.yaml.new" <<EOF
|
|
log:
|
|
level: warn
|
|
plugins:
|
|
- tag: cf_hosts
|
|
type: hosts
|
|
args:
|
|
files:
|
|
- $STATE/hosts.txt
|
|
- tag: main
|
|
type: sequence
|
|
args:
|
|
- exec: \$cf_hosts
|
|
- matches:
|
|
- has_resp
|
|
exec: accept
|
|
- exec: reject 3
|
|
- type: udp_server
|
|
args:
|
|
entry: main
|
|
listen: 127.0.0.1:$port
|
|
- type: tcp_server
|
|
args:
|
|
entry: main
|
|
listen: 127.0.0.1:$port
|
|
EOF
|
|
remove_old_routes
|
|
if [ "$(cfg enabled)" = 1 ]; then
|
|
while IFS= read -r route; do
|
|
[ -n "$route" ] && uci -q add_list dhcp.@dnsmasq[0].server="$route"
|
|
done < "$STATE/routes.new"
|
|
fi
|
|
uci -q commit dhcp || return 1
|
|
mv "$STATE/hosts.txt.new" "$STATE/hosts.txt"
|
|
mv "$STATE/mosdns.yaml.new" "$STATE/mosdns.yaml"
|
|
mv "$STATE/domains.new" "$STATE/domains.active"
|
|
mv "$STATE/routes.new" "$STATE/routes.active"
|
|
}
|
|
|
|
update_cron() {
|
|
local schedule tmp=/tmp/$NAME.cron.$$
|
|
schedule="$(cfg schedule)"
|
|
grep -v '# cf-dns-speedtest$' /etc/crontabs/root 2>/dev/null > "$tmp" || :
|
|
if [ -n "$schedule" ] && [ "$(cfg enabled)" = 1 ]; then
|
|
printf '%s /usr/bin/cf-dns-speedtest start # cf-dns-speedtest\n' "$schedule" >> "$tmp"
|
|
fi
|
|
mv "$tmp" /etc/crontabs/root
|
|
/etc/init.d/cron restart >/dev/null 2>&1
|
|
}
|
|
|
|
restart_dns() {
|
|
/etc/init.d/$NAME restart || return 1
|
|
/etc/init.d/dnsmasq restart || return 1
|
|
}
|
|
|
|
apply() {
|
|
local ip
|
|
mkdir -p "$STATE"
|
|
ip="$(cat "$STATE/best_ip" 2>/dev/null)"
|
|
valid_ipv4 "$ip" || ip=1.1.1.1
|
|
make_config "$ip" || return 1
|
|
update_cron
|
|
restart_dns || { log "service restart failed"; return 1; }
|
|
log "configuration applied; active IP: $ip"
|
|
}
|
|
|
|
run_test() {
|
|
local threads probes count timeout latency speed url ip old_ip
|
|
mkdir -p "$STATE" "$RUN"
|
|
if [ -f "$RUN/pid" ] && kill -0 "$(cat "$RUN/pid")" 2>/dev/null; then log "a test is already running"; return 2; fi
|
|
rm -f "$RUN/pid"
|
|
printf '%s\n' "$$" > "$RUN/pid"
|
|
trap 'rm -f "$RUN/pid" "$STATE/result.csv.new"' EXIT INT TERM
|
|
rotate_log
|
|
echo running > "$STATE/status"
|
|
threads="$(uint "$(cfg threads)" 4)"; [ "$threads" -ge 1 ] && [ "$threads" -le 64 ] || threads=4
|
|
probes="$(uint "$(cfg probes)" 4)"; [ "$probes" -ge 1 ] && [ "$probes" -le 20 ] || probes=4
|
|
count="$(uint "$(cfg download_count)" 5)"; [ "$count" -ge 1 ] && [ "$count" -le 20 ] || count=5
|
|
timeout="$(uint "$(cfg timeout)" 5)"; [ "$timeout" -ge 1 ] && [ "$timeout" -le 30 ] || timeout=5
|
|
latency="$(uint "$(cfg max_latency)" 300)"; [ "$latency" -ge 1 ] && [ "$latency" -le 9999 ] || latency=300
|
|
speed="$(cfg min_speed)"; case "$speed" in ''|*[!0-9.]*) speed=1;; esac
|
|
url="$(cfg test_url)"; case "$url" in https://*|http://*) :;; *) echo failed > "$STATE/status"; log "invalid test URL"; return 1;; esac
|
|
cd /usr/share/$NAME || return 1
|
|
log "speed test started"
|
|
if ./cfst -f ip.txt -o "$STATE/result.csv.new" -n "$threads" -t "$probes" -dn "$count" -dt "$timeout" -tl "$latency" -sl "$speed" -p 10 -url "$url" > "$STATE/cfst.out" 2>&1; then
|
|
ip="$(awk -F, 'NR==2 {gsub(/\r/,""); print $1; exit}' "$STATE/result.csv.new")"
|
|
if ! valid_ipv4 "$ip"; then echo failed > "$STATE/status"; log "no valid IPv4 result"; return 1; fi
|
|
old_ip="$(cat "$STATE/best_ip" 2>/dev/null)"
|
|
if ! make_config "$ip" || ! restart_dns; then
|
|
[ -n "$old_ip" ] && make_config "$old_ip" && restart_dns
|
|
echo failed > "$STATE/status"; log "new DNS configuration failed; previous IP restored"; return 1
|
|
fi
|
|
mv "$STATE/result.csv.new" "$STATE/result.csv"
|
|
printf '%s\n' "$ip" > "$STATE/best_ip"
|
|
date '+%F %T' > "$STATE/last_run"
|
|
echo success > "$STATE/status"
|
|
log "speed test completed; best IP: $ip"
|
|
else
|
|
echo failed > "$STATE/status"
|
|
log "speed test failed; previous DNS configuration retained"
|
|
return 1
|
|
fi
|
|
# Keep diagnostic output bounded without polluting the user-facing log.
|
|
if [ "$(wc -c < "$STATE/cfst.out")" -gt 65536 ]; then
|
|
tail -c 65536 "$STATE/cfst.out" > "$STATE/cfst.out.new" && mv "$STATE/cfst.out.new" "$STATE/cfst.out"
|
|
fi
|
|
}
|
|
|
|
stop_test() {
|
|
if [ -f "$RUN/pid" ] && kill -0 "$(cat "$RUN/pid")" 2>/dev/null; then
|
|
kill "$(cat "$RUN/pid")" 2>/dev/null
|
|
log "speed test stopped"
|
|
fi
|
|
rm -f "$RUN/pid"
|
|
echo stopped > "$STATE/status"
|
|
}
|
|
|
|
case "$1" in
|
|
start) mkdir -p "$STATE" "$RUN"; (run_test </dev/null >/dev/null 2>&1 &) ;;
|
|
run) run_test ;;
|
|
stop) stop_test ;;
|
|
apply) apply ;;
|
|
status) printf 'status=%s\nbest_ip=%s\nlast_run=%s\n' "$(cat "$STATE/status" 2>/dev/null || echo idle)" "$(cat "$STATE/best_ip" 2>/dev/null || echo '-')" "$(cat "$STATE/last_run" 2>/dev/null || echo '-')"; tail -n 20 "$STATE/run.log" 2>/dev/null ;;
|
|
ui) printf 'status=%s\nbest_ip=%s\nenabled=%s\nlast_run=%s\nscheduled=%s\n' "$(cat "$STATE/status" 2>/dev/null || echo idle)" "$(cat "$STATE/best_ip" 2>/dev/null || echo '-')" "$(cfg enabled)" "$(cat "$STATE/last_run" 2>/dev/null || echo '-')" "$(grep -c '# cf-dns-speedtest$' /etc/crontabs/root 2>/dev/null)"; [ -s "$STATE/result.csv" ] && cat "$STATE/result.csv" ;;
|
|
*) echo "Usage: $0 {start|run|stop|apply|status|ui}"; exit 1 ;;
|
|
esac
|