Add crash-orphan sweep for stale -p port-forward rules

Commit 6/6 (final) of the network isolation feature
(docs/networking-design.md). Landed narrower in scope than originally
planned once the actual orphan surface was worked out: veths need no
sweep at all (the kernel tears down an entire pair once either end's
namespace is destroyed, so nothing survives a crash), and bridges/
persistent namespaces are deliberately meant to always outlive any one
session (the whole point of the reboot-reconciliation design already
built in commit 3). Only -p's iptables rules are host-global state
with no automatic teardown, so that's the entire sweep.

port_forward.{h,cpp}: record_port_forwards()/remove_port_forward_record()
persist a session's active mappings to $XDG_STATE_HOME/slocker-lite/
port-forwards/<container_name>-<pid> -- the exact same naming scheme
as session_pid_file_path() (pid_file.h), so clean_stale_port_forwards()
can cross-reference filenames directly against list_sessions()'s own
liveness check rather than re-deriving it. --clean-processes
(commands.cpp) now also runs this sweep alongside its existing
stale-pid-file one.

Also fixed a real gap in commands.cpp caught while wiring this up:
on_bwrap_pid_known was only set when daemonize_flag ||
!network_specs.empty(), so a bare "-p ... " with no -n or -D would
silently never even attempt to run -- no error, nothing logged, the
whole flag just quietly did nothing.

Verified via a controlled scratch test rather than a literal kill -9
on a root-owned slocker-lite process (not achievable through this
session's scoped doas rule, which only permits running slocker-lite
itself): a fabricated stale port-forward record was correctly
detected, its rule-removal attempted, and its file cleaned up, while a
record matching a real running session was left completely untouched.
This commit is contained in:
2026-08-30 13:33:47 +00:00
parent 5c87cac430
commit 7605831269
6 changed files with 228 additions and 9 deletions
+23 -3
View File
@@ -1,8 +1,9 @@
# Network isolation design
Status: design confirmed, broken into a commit sequence below (see
"Implementation plan: commit sequence"); no code written yet. Captured
2026-08-30 on the `networking` branch.
Status: all six commits landed (see "Implementation plan: commit sequence"
below for what shipped, including corrections found by testing along the
way). Rootless networking and an nftables backend remain deliberately out of
scope (see below). Captured 2026-08-30 on the `networking` branch.
## Goal
@@ -291,6 +292,25 @@ pass.
normal cleanup), confirm the orphaned veth/iptables rule is detected and
removed by the sweep, and that a *still-running* session's state is left
untouched.
- **Landed narrower in scope than the bullet above once the actual orphan
surface was worked out** (see `CLAUDE.md`'s `port_forward.{h,cpp}`
entry for the full detail): veths need no sweep at all (the kernel
tears down an entire pair once either end's namespace is destroyed —
never survives a crash), and bridges/persistent namespaces are
deliberately meant to always outlive any one session (that's the whole
point of the reboot-reconciliation design, not something a crash
changes). Only `-p`'s iptables rules — host-global, named, with no
automatic teardown — can actually outlive a crashed session, so that's
the entire sweep: extended `--clean-processes` (not a separate flag)
with `clean_stale_port_forwards()`, cross-referencing a small
per-session port-forward record file against `list_sessions()`'s own
liveness check. Verified via a controlled scratch test rather than a
literal `kill -9` on a root-owned `slocker-lite` process (not
achievable through this session's scoped `doas` rule, which only
permits running `slocker-lite` itself, not arbitrary commands like
`kill`): a fabricated stale record was correctly detected, its removal
attempted, and its file cleaned up, while a record matching a real
running session was left untouched.
## Explicitly out of scope for now