fix(watchdog): не перезапускать WAN по одиночному сбою пробы (issue #31)

This commit is contained in:
Dushnilin
2026-08-13 21:24:43 +03:00
parent 305e14d44e
commit ac3fbc90fc
5 changed files with 75 additions and 9 deletions
@@ -29,6 +29,13 @@ const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
// ai_heal_dns() used to step that counter before probing moved to the fast tier.
const DNS_STREAK_INTERVAL = 120;
// Consecutive WAN probe misses required before WAN_DOWN is published. The old
// check fired on a single observation: one transient gap in `ip route` (a
// DHCP renew re-applying the route, a reload_firewall window) made the healer
// bounce the whole interface, which then really dropped the address and the
// route — a self-inflicted WAN flap every ~5 minutes (issue #31).
const WAN_FAIL_THRESHOLD = 3;
let as_string = common.as_string;
let shell_quote = common.shell_quote;
let command_from_args = common.command_from_args;
@@ -360,7 +367,8 @@ function controller(bus, opts) {
logread_pipe_fd: -1,
last_oom_time: 0,
healthy_streak: 0,
subscription_fail_streak: 0
subscription_fail_streak: 0,
wan_fail_streak: 0
};
let self = { EV: EV, state: state };
@@ -707,8 +715,17 @@ function controller(bus, opts) {
}
// ── Probe: WAN address and default route ──────────────────────────────────
// WAN_DOWN is published only after WAN_FAIL_THRESHOLD consecutive misses,
// mirroring the streak logic of the other probes. A reload restarts the
// firewall, which can briefly drop the default route; failures seen while
// that is in progress are not counted, and a healthy pass resets the
// streak so one transient gap cannot accumulate into a false trigger.
function probe_wan() {
if (is_reload_in_progress()) return;
if (is_reload_in_progress()) {
state.wan_fail_streak = 0;
return;
}
if (setting("recovery_bypass", "0") == "1") return;
let proto = uci_core.get("network", "wan", "proto") || "pppoe";
let device = trim(uci_core.get("network", "wan", "device") || "eth0");
@@ -720,10 +737,23 @@ function controller(bus, opts) {
let route_out = command_capture("ip route 2>/dev/null").output;
let no_gateway = index(route_out, "default") < 0;
if (no_address || no_gateway)
bus.emit(EV.WAN_DOWN, { iface: iface, no_address: no_address, no_gateway: no_gateway });
else
if (!no_address && !no_gateway) {
state.wan_fail_streak = 0;
bus.emit(EV.WAN_UP, { iface: iface });
return;
}
state.wan_fail_streak++;
log("Watchdog: WAN check failed (" + as_string(state.wan_fail_streak) + "/" +
as_string(WAN_FAIL_THRESHOLD) + " misses)", "warn");
if (state.wan_fail_streak < WAN_FAIL_THRESHOLD) return;
bus.emit(EV.WAN_DOWN, {
iface: iface,
no_address: no_address,
no_gateway: no_gateway,
streak: state.wan_fail_streak
});
}
// ── Probe: community subnet nft sets ──────────────────────────────────────
+14 -1
View File
@@ -858,6 +858,7 @@ function heal_tproxy_port(ev) {
}
function heal_wan_and_gateway(ev) {
if (settings().recovery_bypass == "1") return;
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (tcfg.notify_crash != "0") {
send_telegram_notification("⚠️ *Watchdog:* WAN/Gateway проблема. Перезапуск wan...");
@@ -866,7 +867,19 @@ function heal_wan_and_gateway(ev) {
// makes the proxy and DNS probes fail, and those facts arrive while ifup is
// still running.
wan_repair_until = time() + SUPPRESSION_DEADLINE;
bg_system("/sbin/ifdown wan >/dev/null 2>&1 && /sbin/ifup wan >/dev/null 2>&1");
// The address is still there but the default route vanished: a graceful
// renew re-applies the routes without dropping the interface (issue #31).
// Only when the address itself is gone does the interface get recycled,
// and even then only if the renew did not restore the route within 5s.
let cmd;
if (ev.payload.no_address) {
cmd = "/sbin/ifdown wan >/dev/null 2>&1 && /sbin/ifup wan >/dev/null 2>&1";
} else {
cmd = "/sbin/ubus call network.interface.wan renew >/dev/null 2>&1; sleep 5; " +
"ip route 2>/dev/null | grep -q default || " +
"(/sbin/ifdown wan >/dev/null 2>&1 && /sbin/ifup wan >/dev/null 2>&1)";
}
bg_system(cmd);
}
// The uplink answering again is what ends the WAN repair. probe_wan publishes