fix: sweep stale bypass routes at service startup #108

Merged
mysticalsoap merged 1 commit from fix/bypass-route-residue into trunk 2026-08-20 21:16:03 -04:00
Owner

Problem

systemctl restart aqomui SIGTERMs openvpn and the python tunnel threads together; the route teardown that runs after openvpn's EOF loses that race. The table-11/main-table host-route pins and the table-11 default could survive the restart — state parked in table 11 outliving the process that put it there, the #66 failure shape again (#78).

Fix

Of the two shapes the issue offers, the startup sweep: it also covers SIGKILL and crashes, and a python SIGTERM handler under the C mainloop has signal-delivery problems of its own (the service resets SIGINT to SIG_DFL for exactly that reason).

  • The pins tunnel.py installs now carry proto 111 as an ownership tag. Table 11 is ours and flushes wholesale, but the main-table pin can't be found any other way after a restart — the server IP is gone with the process; the proto tag is the kernel-side handle on exactly our entries.
  • bypass.flush_stale_routes() in startup's clean-slate block (next to killall openvpn and the TEMPDIR sweep): flushes table 11 and table main proto 111 on both stacks, tolerating the flush-of-a-nonexistent-table error, then clears leftover fwmark rules — marked traffic from a still-populated bypass cgroup must not consult whatever lands in table 11 next.
  • Ordering is safe: the sweep runs before any bypass exists; create_cgroup reinstalls the table-11 default and rules when the bypass comes back up.

Verification

Two new tests: exact flush sequence across both stacks + rules clear; a failing table-11 flush (table absent since boot) doesn't stop the sweep. Verified ip route flush table main proto 111 selector syntax live; current live table 11 holds only the active bypass default, which the startup-ordering argument covers. Full suite: 187 passed.

Closes #78

🤖 Generated with Claude Code

**Problem** `systemctl restart aqomui` SIGTERMs openvpn and the python tunnel threads together; the route teardown that runs after openvpn's EOF loses that race. The table-11/main-table host-route pins and the table-11 default could survive the restart — state parked in table 11 outliving the process that put it there, the #66 failure shape again (#78). **Fix** Of the two shapes the issue offers, the startup sweep: it also covers SIGKILL and crashes, and a python SIGTERM handler under the C mainloop has signal-delivery problems of its own (the service resets SIGINT to SIG_DFL for exactly that reason). - The pins tunnel.py installs now carry **`proto 111`** as an ownership tag. Table 11 is ours and flushes wholesale, but the *main*-table pin can't be found any other way after a restart — the server IP is gone with the process; the proto tag is the kernel-side handle on exactly our entries. - `bypass.flush_stale_routes()` in startup's clean-slate block (next to `killall openvpn` and the TEMPDIR sweep): flushes `table 11` and `table main proto 111` on both stacks, tolerating the flush-of-a-nonexistent-table error, then clears leftover fwmark rules — marked traffic from a still-populated bypass cgroup must not consult whatever lands in table 11 next. - Ordering is safe: the sweep runs before any bypass exists; `create_cgroup` reinstalls the table-11 default and rules when the bypass comes back up. **Verification** Two new tests: exact flush sequence across both stacks + rules clear; a failing table-11 flush (table absent since boot) doesn't stop the sweep. Verified `ip route flush table main proto 111` selector syntax live; current live table 11 holds only the active bypass default, which the startup-ordering argument covers. Full suite: 187 passed. Closes #78 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: sweep stale bypass routes at service startup
All checks were successful
ci / test (pull_request) Successful in 31s
ci / test (push) Successful in 29s
ca883aa0ae
A service stop goes through SIGTERM, which kills openvpn and the
python threads together -- the route teardown that runs after
openvpn's EOF loses that race, so the host-route pins (main table and
table 11) and the table-11 default could outlive the restart (#78).

The pins now carry proto 111 as an ownership tag, and startup's
clean-slate block flushes table 11 wholesale plus the main table by
that tag -- the pinned server IP is unknown after a restart, so the
tag is the only handle on those entries. A startup sweep rather than a
SIGTERM handler: it also covers SIGKILL and crashes, and a python
signal handler under the C mainloop has delivery problems of its own
(SIGINT is reset to SIG_DFL for exactly that reason). The leftover
fwmark rules go too, or marked traffic from a still-populated bypass
cgroup would consult whatever lands in table 11 next.

Closes #78

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mysticalsoap deleted branch fix/bypass-route-residue 2026-08-20 21:16:03 -04:00
Sign in to join this conversation.
No description provided.