fix(dpi-bypass): TCP keepalive + conntrack tuning + nfqueue watchdog

- sysctl: tcp_keepalive=60/10/3, conntrack_established=600s (was 5 days)
- nfqws/nfqws2: --ctrack-timeouts=30,30,600,30,30,30,30,30 in default strategy
- watchdog: probe_nfqueue checks nfqws/nfqws2 process health every 300s
- watchdog: heal_nfqueue_stopped restarts tachyon if DPI bypass processes die

This eliminates the need for manual DNS flush + device restart when DPI
starts blocking connections. Dead connections are now detected in ~90s
instead of hours, and stale conntrack entries expire in 10 min instead
of 5 days.
This commit is contained in:
Dushnilin
2026-08-28 20:05:03 +03:00
parent 16ffc9f91f
commit 404db8ae05
4 changed files with 107 additions and 2 deletions
@@ -70,6 +70,8 @@ const EV = {
SECTIONS_EMPTY: "sections.empty",
ANOMALY_RECONNECTS: "anomaly.reconnects",
PAUSE_EXPIRED: "pause.expired",
NFQUEUE_DOWN: "nfqueue.down",
NFQUEUE_UP: "nfqueue.up",
TICK: "tick"
};
@@ -957,6 +959,46 @@ function controller(bus, opts) {
bus.emit(EV.OOM_RECOVERABLE, { last_oom: state.last_oom_time, scale: current_scale });
}
// ── Probe: nfqueue/zapret runtime health ──────────────────────────────────
// Checks if nfqws/nfqws2 supervisor processes are alive by counting PID
// files. A mismatch means the DPI bypass engine crashed or was killed.
function probe_nfqueue() {
if (!bus.has(EV.NFQUEUE_DOWN) && !bus.has(EV.NFQUEUE_UP)) return;
if (is_reload_in_progress()) return;
let pid_dirs = [
"/var/run/tachyon/zapret/child-pid",
"/var/run/tachyon/zapret2/child-pid"
];
for (let i = 0; i < length(pid_dirs); i++) {
let child_dir = pid_dirs[i];
let child_entries = fs.readdir(child_dir);
if (child_entries == null) continue;
let expected = length(child_entries);
if (expected == 0) continue;
let running = 0;
for (let entry in child_entries) {
let pid_data = fs.readfile(child_dir + "/" + entry);
if (pid_data == null) continue;
let pid = trim(as_string(pid_data));
if (pid == "") continue;
try { system("kill -0 " + pid + " 2>/dev/null"); running++; } catch (e) {}
}
if (running == expected) {
bus.emit(EV.NFQUEUE_UP, { running: running, expected: expected });
} else {
bus.emit(EV.NFQUEUE_DOWN, {
running: running,
expected: expected,
dir: child_dir
});
}
}
}
// ── Tier drivers ──────────────────────────────────────────────────────────
// Each probe is wrapped so one failing observation cannot stop the tier;
// this mirrors the safe_call() isolation the old per-check loop had.
@@ -1004,6 +1046,7 @@ function controller(bus, opts) {
run_probe(probe_empty_sections, "empty_sections");
run_probe(probe_anomalies, "anomalies");
run_probe(probe_oom_recovery, "oom_recovery");
run_probe(probe_nfqueue, "nfqueue");
};
// Health is graded by how long we go without publishing a fault: after a