mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 04:53:56 +08:00
🏅 Sync 2026-07-25 20:22:21
This commit is contained in:
1 parent
971b549a31
commit
f708dca2cf
19 files changed
+1051
-118
No files matched your search
+135
-45
@@ -9,12 +9,13 @@ USE_PROCD=1
|
||||
VERSION="dev"
|
||||
UPD_CHANNEL="release"
|
||||
|
||||
EXTRA_COMMANDS="check_version update auto_setup auto_setup_noninteractive validate_custom_rules health_check"
|
||||
EXTRA_COMMANDS="check_version update auto_setup auto_setup_noninteractive auto_setup_detached validate_custom_rules health_check"
|
||||
EXTRA_HELP="
|
||||
check_version Check for updates
|
||||
update Update qosmate
|
||||
auto_setup Automatically configure qosmate
|
||||
auto_setup_noninteractive Automatically configure qosmate with no interaction
|
||||
auto_setup_detached Start the non-interactive auto-setup in the background
|
||||
validate_custom_rules Validate custom rules
|
||||
health_check Check if QoSmate is properly configured and running"
|
||||
|
||||
@@ -54,6 +55,7 @@ QOSMATE_DEFAULTS_FILE=${QOSMATE_D}/qosmate-defaults
|
||||
QOSMATE_CUSTOM_RULES_FILE=${QOSMATE_D}/custom_rules.nft
|
||||
QOSMATE_INLINE_RULES_FILE=${QOSMATE_D}/inline_dscptag.nft
|
||||
QOSMATE_RUN_DIR=/tmp/qosmate
|
||||
QOSMATE_AUTO_SETUP_OUTPUT=/tmp/qosmate_auto_setup_output.txt
|
||||
|
||||
# Local path of JS files
|
||||
QOSMATE_UPD_DIR=/var/run/qosmate-update
|
||||
@@ -197,7 +199,7 @@ print_msg() {
|
||||
# logs each argument separately and prints to a separate line
|
||||
# optional arguments: '-err', '-warn' to set logged error level
|
||||
log_msg() {
|
||||
local msgs_prefix='' _arg err_l=info msgs_dest
|
||||
local msgs_prefix='' _arg err_l=info
|
||||
|
||||
local IFS="$DEFAULT_IFS"
|
||||
for _arg in "$@"
|
||||
@@ -207,11 +209,12 @@ log_msg() {
|
||||
"-warn") err_l=warn msgs_prefix="Warning: " ;;
|
||||
'') printf '\n' ;; # print out empty lines
|
||||
*)
|
||||
# Write to the inherited descriptors instead of redirecting to /dev/stdout: that
|
||||
# re-opens the target, and when stdout is a regular file '>' truncates it.
|
||||
case "$err_l" in
|
||||
err|warn) msgs_dest="/dev/stderr" ;;
|
||||
*) msgs_dest="/dev/stdout"
|
||||
err|warn) printf '%s\n' "${msgs_prefix}${_arg}" >&2 ;;
|
||||
*) printf '%s\n' "${msgs_prefix}${_arg}"
|
||||
esac
|
||||
printf '%s\n' "${msgs_prefix}${_arg}" > "$msgs_dest"
|
||||
logger -t qosmate -p user."$err_l" "${msgs_prefix}${_arg}"
|
||||
msgs_prefix=''
|
||||
esac
|
||||
@@ -2218,6 +2221,48 @@ parse_speedtest_output() {
|
||||
:
|
||||
}
|
||||
|
||||
# Prints WAN throughput in Mbit/s once per second as "<rx> <tx>", until killed.
|
||||
# 1 - network device
|
||||
sample_wan_rates() {
|
||||
local stats="/sys/class/net/${1}/statistics" prev_rx prev_tx rx tx
|
||||
read -r prev_rx < "$stats/rx_bytes" && read -r prev_tx < "$stats/tx_bytes" || return 1
|
||||
while sleep 1; do
|
||||
read -r rx < "$stats/rx_bytes" && read -r tx < "$stats/tx_bytes" || return 1
|
||||
printf '%s %s\n' $(( (rx - prev_rx) * 8 / 1000000 )) $(( (tx - prev_tx) * 8 / 1000000 ))
|
||||
prev_rx=$rx prev_tx=$tx
|
||||
done
|
||||
}
|
||||
|
||||
# Median of the samples reaching at least half of the peak sample, which is the plateau of a test
|
||||
# phase. Ignores ramp-up, idle time and the ACK traffic of the opposite direction.
|
||||
# Prints 0 if there is no usable plateau.
|
||||
# 1 - samples file, 2 - column (1 = rx, 2 = tx)
|
||||
plateau_rate() {
|
||||
[ -s "$1" ] || { echo "0"; return 0; }
|
||||
awk -v col="$2" '
|
||||
{ v[NR] = $col + 0; if (v[NR] > peak) peak = v[NR] }
|
||||
END {
|
||||
if (peak <= 0) { print 0; exit }
|
||||
for (i = 1; i <= NR; i++) if (v[i] >= peak / 2) s[++n] = v[i]
|
||||
if (n < 3) { print 0; exit }
|
||||
for (i = 2; i <= n; i++) { x = s[i]; for (j = i - 1; j > 0 && s[j] > x; j--) s[j + 1] = s[j]; s[j + 1] = x }
|
||||
print (n % 2) ? s[(n + 1) / 2] : int((s[n / 2] + s[n / 2 + 1]) / 2)
|
||||
}' "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# 1, 2 - rates to compare
|
||||
lower_of() {
|
||||
awk -v a="$1" -v b="$2" 'BEGIN{print (a < b) ? a : b}'
|
||||
}
|
||||
|
||||
# Warns when the speed test result is implausibly higher than what crossed the interface.
|
||||
# 1 - direction label, 2 - reported rate, 3 - measured rate
|
||||
warn_if_overreported() {
|
||||
awk -v r="$2" -v m="$3" 'BEGIN{exit !(m > 0 && r > m * 1.25)}' &&
|
||||
print_msg "Warning: the speed test reported $2 Mbit/s $1, but only $3 Mbit/s crossed the WAN interface. Using the measured value."
|
||||
:
|
||||
}
|
||||
|
||||
auto_setup() {
|
||||
try_auto_setup "$@" && return 0
|
||||
error_out "auto-setup failed."
|
||||
@@ -2380,15 +2425,49 @@ try_auto_setup() {
|
||||
fi
|
||||
|
||||
print_msg "Running speed test... This may take a few minutes."
|
||||
SPEED_RESULT=$($SPEEDTEST_CMD)
|
||||
|
||||
DOWNLOAD_SPEED=$(printf %s "$SPEED_RESULT" | parse_speedtest_output "$SPEEDTEST_CMD" Download) &&
|
||||
UPLOAD_SPEED=$(printf %s "$SPEED_RESULT" | parse_speedtest_output "$SPEEDTEST_CMD" Upload) ||
|
||||
return 1
|
||||
local samples_file=/tmp/qosmate-speedtest-samples sampler_pid speedtest_rv retried=''
|
||||
local reported_down reported_up measured_down measured_up
|
||||
|
||||
while :; do
|
||||
sample_wan_rates "$FINAL_INTERFACE" > "$samples_file" &
|
||||
sampler_pid=$!
|
||||
SPEED_RESULT=$($SPEEDTEST_CMD 2>&1)
|
||||
speedtest_rv=$?
|
||||
kill "$sampler_pid" 2>/dev/null
|
||||
|
||||
[ "$speedtest_rv" = 0 ] &&
|
||||
DOWNLOAD_SPEED=$(printf %s "$SPEED_RESULT" | parse_speedtest_output "$SPEEDTEST_CMD" Download) &&
|
||||
UPLOAD_SPEED=$(printf %s "$SPEED_RESULT" | parse_speedtest_output "$SPEEDTEST_CMD" Upload) &&
|
||||
break
|
||||
|
||||
[ -z "$retried" ] || {
|
||||
# include the tool output, otherwise the failure is only reported generically
|
||||
error_out "Speed test failed (exit code $speedtest_rv). Speed test output:" "$SPEED_RESULT"
|
||||
return 1
|
||||
}
|
||||
retried=1
|
||||
log_msg -warn "Speed test failed (exit code $speedtest_rv), retrying once."
|
||||
done
|
||||
|
||||
# A speed test measures at the socket, so it can report several times the rate that actually
|
||||
# crossed the link (speedtest-go does this on upload). Cross-check with the WAN counters.
|
||||
reported_down="$DOWNLOAD_SPEED" reported_up="$UPLOAD_SPEED"
|
||||
measured_down=$(plateau_rate "$samples_file" 1)
|
||||
measured_up=$(plateau_rate "$samples_file" 2)
|
||||
rm -f "$samples_file"
|
||||
|
||||
# Use the lower value: the test may over-report, the counters may include foreign traffic
|
||||
[ "$measured_down" != 0 ] && DOWNLOAD_SPEED=$(lower_of "$reported_down" "$measured_down")
|
||||
[ "$measured_up" != 0 ] && UPLOAD_SPEED=$(lower_of "$reported_up" "$measured_up")
|
||||
|
||||
warn_if_overreported download "$reported_down" "$measured_down"
|
||||
warn_if_overreported upload "$reported_up" "$measured_up"
|
||||
|
||||
print_msg "Speed test results:" \
|
||||
"Download speed: $DOWNLOAD_SPEED Mbit/s" \
|
||||
"Upload speed: $UPLOAD_SPEED Mbit/s"
|
||||
"Upload speed: $UPLOAD_SPEED Mbit/s" \
|
||||
"WAN counters during the test: $measured_down Mbit/s down, $measured_up Mbit/s up (speed test: $reported_down / $reported_up)"
|
||||
fi
|
||||
|
||||
# Convert speeds to kbps and apply 90% rule
|
||||
@@ -2419,43 +2498,29 @@ try_auto_setup() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$gaming_ip" ]; then
|
||||
# Validate IP address format
|
||||
if [[ $gaming_ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
# Check if rules for this IP already exist
|
||||
local ip_configured=
|
||||
config_foreach is_ip_configured rule "$gaming_ip"
|
||||
# $ip_configured is set in is_ip_configured()
|
||||
if [ -n "$ip_configured" ]; then
|
||||
print_msg "Rules for IP $gaming_ip already exist. Skipping addition of new rules."
|
||||
gaming_ip=
|
||||
fi
|
||||
# Exactly one outcome is reported, so the UI can always tell the user what happened
|
||||
local ip_configured=
|
||||
if [ -z "$gaming_ip" ]; then
|
||||
print_msg "No gaming device IP given - no rule added."
|
||||
elif [[ ! "$gaming_ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
print_msg "Invalid IP address format '$gaming_ip' - no gaming device rule added."
|
||||
else
|
||||
config_foreach is_ip_configured rule "$gaming_ip" # sets $ip_configured
|
||||
if [ -n "$ip_configured" ]; then
|
||||
print_msg "A rule for IP $gaming_ip already exists - keeping it."
|
||||
else
|
||||
print_msg "Invalid IP address format. No gaming device rules added."
|
||||
gaming_ip=
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$gaming_ip" ]; then
|
||||
local direction ip_dir port_dir
|
||||
for direction in Inbound Outbound; do
|
||||
case "$direction" in
|
||||
Inbound) ip_dir=dest port_dir=src ;;
|
||||
Outbound) ip_dir=src port_dir=dest ;;
|
||||
esac
|
||||
# Only an egress rule is needed: the DSCP is stored in conntrack and restored on ingress via tc-ctinfo
|
||||
uci add qosmate rule 1>/dev/null &&
|
||||
uci set "qosmate.@rule[-1].name=Game_Console_${direction}" &&
|
||||
uci set "qosmate.@rule[-1].name=Game_Console" &&
|
||||
uci set "qosmate.@rule[-1].proto=udp" &&
|
||||
uci set "qosmate.@rule[-1].${ip_dir}_ip=${gaming_ip}" &&
|
||||
uci add_list "qosmate.@rule[-1].${port_dir}_port=!=80" &&
|
||||
uci add_list "qosmate.@rule[-1].${port_dir}_port=!=443" &&
|
||||
uci set "qosmate.@rule[-1].src_ip=${gaming_ip}" &&
|
||||
uci add_list "qosmate.@rule[-1].dest_port=!=80" &&
|
||||
uci add_list "qosmate.@rule[-1].dest_port=!=443" &&
|
||||
uci set "qosmate.@rule[-1].class=cs5" &&
|
||||
uci set "qosmate.@rule[-1].counter=1" ||
|
||||
return 1
|
||||
done
|
||||
print_msg "Gaming device rules added for IP: $gaming_ip"
|
||||
else
|
||||
print_msg "No gaming device IP added."
|
||||
print_msg "Gaming device rule added for IP: $gaming_ip"
|
||||
fi
|
||||
fi
|
||||
|
||||
uci commit qosmate || return 1
|
||||
@@ -2470,16 +2535,41 @@ try_auto_setup() {
|
||||
}
|
||||
|
||||
auto_setup_noninteractive() {
|
||||
local output_file="/tmp/qosmate_auto_setup_output.txt" auto_setup_rv
|
||||
local auto_setup_rv
|
||||
# Truncate up front and append afterwards. Truncating while a writer already holds an offset
|
||||
# leaves a gap that the kernel fills with NUL bytes, and ubus file.read stops at the first NUL,
|
||||
# so a polling caller would only ever see the beginning of the file.
|
||||
: > "$QOSMATE_AUTO_SETUP_OUTPUT"
|
||||
{
|
||||
echo "Starting qosmate non-interactive auto-setup..."
|
||||
auto_setup -n "$1" 2>&1
|
||||
} > "$output_file"
|
||||
auto_setup_rv=$?
|
||||
echo "$output_file"
|
||||
auto_setup_rv=$?
|
||||
# lets callers polling this file tell a finished run from a partial or stale one
|
||||
echo "AUTO_SETUP_DONE rv=$auto_setup_rv"
|
||||
} >> "$QOSMATE_AUTO_SETUP_OUTPUT"
|
||||
echo "$QOSMATE_AUTO_SETUP_OUTPUT"
|
||||
return $auto_setup_rv
|
||||
}
|
||||
|
||||
# Starts the non-interactive auto-setup detached and prints the output file path right away.
|
||||
# A speed test regularly takes longer than uhttpd's script_timeout (60s by default), which kills
|
||||
# the CGI and leaves the caller with a half-written file, so the caller polls the file instead.
|
||||
# 1 - optional gaming device IP
|
||||
auto_setup_detached() {
|
||||
printf '%s\n' "Starting qosmate auto-setup..." > "$QOSMATE_AUTO_SETUP_OUTPUT" || return 1
|
||||
# A new session is required, not just a background job: ash waits for children of the same
|
||||
# session in a command substitution, which is how callers read this function's output.
|
||||
if check_util setsid; then
|
||||
# stdin is closed as well, otherwise the run keeps the caller's pipe open - for a request
|
||||
# from the web UI that blocks the connection the UI then polls on
|
||||
setsid "$QOSMATE_SERVICE_PATH" auto_setup_noninteractive "$1" >/dev/null 2>&1 <&- &
|
||||
else
|
||||
log_msg -warn "setsid is not available, running the auto-setup in the foreground."
|
||||
auto_setup_noninteractive "$1" >/dev/null 2>&1
|
||||
fi
|
||||
echo "$QOSMATE_AUTO_SETUP_OUTPUT"
|
||||
}
|
||||
|
||||
health_check() {
|
||||
# Check if QoSmate is properly configured and running
|
||||
local status="" errors=0 service_enabled=0 config_status=''
|
||||
|
||||
Reference in new issue
Block a user