Files
NeoTachyon/tests/wan_monitor_hotplug.sh
Dushnilin 2019ddd12a release: 1.3.26 — Watchdog proxy restart protection, WAN monitor debounce, and subscription bootstrap resilience
- Fix #52: Watchdog proxy restart on single URL failure:
  * Validate failure against secondary independent probe URL before declaring proxy down
  * Skip restart when direct WAN is also degraded or down (!direct_ok)
  * Implement 120s cooldown between automated proxy restarts
  * Fix Telegram notification to accurately state 'Перезапуск sing-box' instead of 'Перезапуск роутера'
- Fix #51: Telegram bot spamming WAN up/down notifications:
  * Filter hotplug events strictly by badwan_monitored_interfaces (defaults to wan)
  * Add atomic directory lock with debounce delay and real ubus state query
  * Prevent repeated notifications when interface state has not transitioned
- Feat: 2-VPN deadlock elimination & self-download subscription proxy:
  * Isolate unbootstrapped dependent subscription sections on initial boot (deferred_sections)
  * Allow self-download proxy target for subscriptions with explicit UI hint '(текущее правило при активности)'
  * Enable automatic local proxy fallback for subscription refresh when direct WAN download fails while sing-box is active
  * Add regression test in tests/subscription_bootstrap_deadlock.sh
- Fix: Parental control quota enforcement:
  * Correct route rule filtering when daily quota minutes are exhausted without a fixed time interval
- i18n & Version bump:
  * Bump fe-app-tachyon to 1.3.26 and update Russian PO/POT translations
2026-09-12 16:50:04 +03:00

182 lines
5.8 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)"
MONITOR_SCRIPT="$ROOT_DIR/tachyon/files/etc/hotplug.d/iface/99-tachyon-wan-monitor"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
[ -f "$MONITOR_SCRIPT" ] || fail "99-tachyon-wan-monitor not found"
TMP_DIR="$(mktemp -d /tmp/tachyon_wan_monitor_test.XXXXXX)"
trap 'rm -rf "$TMP_DIR"' EXIT
BIN_DIR="$TMP_DIR/bin"
VAR_RUN="$TMP_DIR/var_run"
mkdir -p "$BIN_DIR" "$VAR_RUN"
# Mock uci
cat > "$BIN_DIR/uci" << 'EOF'
#!/bin/sh
cmd="$1"
shift
case "$cmd" in
-q)
subcmd="$1"
shift
if [ "$subcmd" = "get" ]; then
key="$1"
case "$key" in
tachyon.settings.enable_badwan_interface_monitoring)
cat "${TEST_UCI_DIR}/enable_badwan" 2>/dev/null || echo "0"
;;
tachyon.telegram.enabled)
cat "${TEST_UCI_DIR}/telegram_enabled" 2>/dev/null || echo "0"
;;
tachyon.settings.badwan_monitored_interfaces)
cat "${TEST_UCI_DIR}/monitored_ifaces" 2>/dev/null || echo ""
;;
tachyon.settings.badwan_reload_delay)
cat "${TEST_UCI_DIR}/reload_delay" 2>/dev/null || echo "50"
;;
*)
echo ""
;;
esac
fi
;;
esac
EOF
chmod +x "$BIN_DIR/uci"
# Mock ubus
cat > "$BIN_DIR/ubus" << 'EOF'
#!/bin/sh
# Check if call network.interface.<iface> status
for arg in "$@"; do
case "$arg" in
network.interface.*)
iface="${arg#network.interface.}"
state="$(cat "${TEST_UCI_DIR}/ubus_state_${iface}" 2>/dev/null || echo "up")"
if [ "$state" = "up" ]; then
echo '{"up": true, "pending": false, "available": true}'
else
echo '{"up": false, "pending": false, "available": false}'
fi
exit 0
;;
esac
done
echo '{}'
EOF
chmod +x "$BIN_DIR/ubus"
# Mock logger
cat > "$BIN_DIR/logger" << 'EOF'
#!/bin/sh
echo "$@" >> "${TEST_LOG_FILE}"
EOF
chmod +x "$BIN_DIR/logger"
# Mock tachyon init.d script
cat > "$BIN_DIR/initd_tachyon" << 'EOF'
#!/bin/sh
exit 0
EOF
chmod +x "$BIN_DIR/initd_tachyon"
# Mock tachyon cli (for send_telegram_notification)
cat > "$BIN_DIR/tachyon" << 'EOF'
#!/bin/sh
if [ "$1" = "telegram" ]; then
echo "$@" >> "${TEST_TELEGRAM_LOG}"
fi
exit 0
EOF
chmod +x "$BIN_DIR/tachyon"
export PATH="$BIN_DIR:$PATH"
export TEST_UCI_DIR="$TMP_DIR/uci"
export TEST_LOG_FILE="$TMP_DIR/logger.log"
export TEST_TELEGRAM_LOG="$TMP_DIR/telegram.log"
mkdir -p "$TEST_UCI_DIR"
# Patch monitor script temporarily to use our isolated /var/run/tachyon dir and fast debounce (sleep 0 instead of 2s)
TEST_MONITOR="$TMP_DIR/monitor_under_test.sh"
# shellcheck disable=SC2016
sed -e "s|/var/run/tachyon|${VAR_RUN}|g" \
-e "s|/usr/bin/tachyon|${BIN_DIR}/tachyon|g" \
-e "s|/etc/init.d/tachyon|${BIN_DIR}/initd_tachyon|g" \
-e 's|sleep "\$DELAY_SEC"|sleep 0|g' \
-e 's|sleep 2|sleep 0|g' \
"$MONITOR_SCRIPT" > "$TEST_MONITOR"
chmod +x "$TEST_MONITOR"
# Test 1: Monitoring disabled (enable_badwan = 0)
echo "0" > "$TEST_UCI_DIR/enable_badwan"
echo "1" > "$TEST_UCI_DIR/telegram_enabled"
echo "wan" > "$TEST_UCI_DIR/monitored_ifaces"
echo "down" > "$TEST_UCI_DIR/ubus_state_wan"
ACTION="ifdown" INTERFACE="wan" sh "$TEST_MONITOR"
[ ! -f "$TEST_TELEGRAM_LOG" ] || fail "Test 1 failed: notification sent when enable_badwan=0"
# Test 2: Telegram disabled (telegram_enabled = 0)
echo "1" > "$TEST_UCI_DIR/enable_badwan"
echo "0" > "$TEST_UCI_DIR/telegram_enabled"
ACTION="ifdown" INTERFACE="wan" sh "$TEST_MONITOR"
[ ! -f "$TEST_TELEGRAM_LOG" ] || fail "Test 2 failed: notification sent when telegram.enabled=0"
# Test 3: Unmonitored interface (wan6 triggered, only wan is monitored)
echo "1" > "$TEST_UCI_DIR/enable_badwan"
echo "1" > "$TEST_UCI_DIR/telegram_enabled"
echo "wan" > "$TEST_UCI_DIR/monitored_ifaces"
echo "down" > "$TEST_UCI_DIR/ubus_state_wan6"
ACTION="ifdown" INTERFACE="wan6" sh "$TEST_MONITOR"
[ ! -f "$TEST_TELEGRAM_LOG" ] || fail "Test 3 failed: notification sent for unmonitored interface wan6"
# Test 4: Monitored interface drops (wan ifdown, initial up -> down transition)
echo "1" > "$TEST_UCI_DIR/enable_badwan"
echo "1" > "$TEST_UCI_DIR/telegram_enabled"
echo "wan" > "$TEST_UCI_DIR/monitored_ifaces"
echo "up" > "$VAR_RUN/wan_state_wan" # interface was previously UP
echo "down" > "$TEST_UCI_DIR/ubus_state_wan"
ACTION="ifdown" INTERFACE="wan" sh "$TEST_MONITOR"
# Wait for background verification
sleep 1
[ -f "$TEST_TELEGRAM_LOG" ] || fail "Test 4 failed: no notification sent on UP -> DOWN transition"
grep -Fq "Интерфейс *wan* упал!" "$TEST_TELEGRAM_LOG" || fail "Test 4 failed: expected drop message not found in log"
[ "$(cat "$VAR_RUN/wan_state_wan")" = "down" ] || fail "Test 4 failed: state file not set to down"
# Test 5: Duplicate hotplug ifdown while state is already down (NO repeated spam)
rm -f "$TEST_TELEGRAM_LOG"
ACTION="ifdown" INTERFACE="wan" sh "$TEST_MONITOR"
sleep 1
[ ! -f "$TEST_TELEGRAM_LOG" ] || fail "Test 5 failed: duplicate notification sent on repeated ifdown without state change"
# Test 6: Interface recovers (wan ifup, transition down -> up)
rm -f "$TEST_TELEGRAM_LOG"
echo "up" > "$TEST_UCI_DIR/ubus_state_wan"
ACTION="ifup" INTERFACE="wan" sh "$TEST_MONITOR"
sleep 1
[ -f "$TEST_TELEGRAM_LOG" ] || fail "Test 6 failed: no notification sent on DOWN -> UP recovery"
grep -Fq "Интерфейс *wan* поднялся!" "$TEST_TELEGRAM_LOG" || fail "Test 6 failed: expected recovery message not found in log"
[ "$(cat "$VAR_RUN/wan_state_wan")" = "up" ] || fail "Test 6 failed: state file not set to up"
# Test 7: Duplicate hotplug ifup while state is already up (NO duplicate message)
rm -f "$TEST_TELEGRAM_LOG"
ACTION="ifup" INTERFACE="wan" sh "$TEST_MONITOR"
sleep 1
[ ! -f "$TEST_TELEGRAM_LOG" ] || fail "Test 7 failed: duplicate notification sent on repeated ifup without state change"
printf 'All 7 WAN monitor hotplug tests PASSED!\n'