Every piece of kill-switch runtime state has one owner. This page is the
table a maintainer should be able to read before touching any of the
scripts: who creates and removes each artefact, on which transition, under
which lock, and what happens when it is stale or missing. The mechanics
behind each row are in Firewall Backends; the rule text the
emergency and boot-time blocks install is defined once, in
nym-firewall/src/openwrt/boot_rules.rs, and rendered into
scripts/fw-rules.sh by the crate's build.rs (the build fails while the
committed fragment is stale).
Root-owned, mode 0700, under a root-owned 0755 parent, so nothing
unprivileged can pre-create anything in it. The daemon creates it
(ensure_runtime_dir) and refuses to run a policy unless it passes the
symlink/owner/mode checks; the includes and the init script apply the same
check (nym_runtime_dir_prepare / nym_runtime_dir_trusted) before they
trust any file inside. Cleared by reboot (tmpfs) and by prerm on removal.
never deleted while installed; a missing flock binary makes every include run take the fail-closed run_without_lock path
policy.nft
nobody today (reserved; the fw4 backend pipes to nft -f -)
prerm
fw4 include (optional hint)
none
ignored when absent
stopped
init (stop_service, explicit stop only); prerm (before its locked teardown)
init (start_service)
guard (boot-block decision), fw3 include (stop wins over persisted state), init
written before any teardown, so a racing include already sees it
present = administrator wants the network open: no boot block, persisted fw3 state discarded. Absent after a crash, so reloads keep the block
stop-pid
init (stop_service)
init (service_stopped)
init
none (init-private)
stale pid is ignored via the /proc/<pid>/cmdline check
The daemon and the include are the two legitimate writers of the fw3
rules/transition files, with disjoint responsibilities: the daemon
owns their content for as long as it lives, the include owns their removal
after an explicit stop. The init script no longer removes them itself; it
writes the marker and runs the include under the lock. prerm does the
same and falls back to an inline chain teardown only when the include is
already gone.
The rule is the same on every path, normal or failing: no fw3 mutation
without the fw3 state lock, and the stop marker is written before the
teardown it authorizes.
Writer
How it holds the lock
Failure path
daemon fw3 backend
flock on lock for the whole apply / forwarding-only / reset; runtime directory verified first
any error returns before the marker is cleared, so the include installs the transition block on its next run
fw3 include
exec 9>lock; flock 9 before main; skipped only when the caller exports NYM_FW_LOCKED=1 (init, prerm)
untrusted directory, no flock, or flock failure → run_without_lock: leave a hooked policy untouched, otherwise install the boot block if wanted, log CRITICAL, exit 1; re-check after installing
init service_stopped (explicit stop)
writes stopped first; takes the lock; runs the include with NYM_FW_LOCKED=1
cannot lock → logs and exits; the marker makes the include complete the teardown on its next locked run
prerm (removal)
writes stopped; takes the lock; runs the include with NYM_FW_LOCKED=1
include or guard missing, or the locked run fails → inline teardown of the same chains
daemon fw4 backend, fw4 include, init, prerm on fw4
none
each nft command is one atomic transaction; the include never touches inet nym, re-checks after installing inet nym_boot, and the stop marker stops it re-arming
transition stays → include installs the transition set on every reload until the daemon's next successful apply or an explicit stop
policy table stays as it was (single transaction); nothing to do
firewall reload
fw3 leaves foreign chains alone and re-renders the nym zone; include reconciles (re-hook, lift stale block)
fw4 rebuilds only its own table, the nym zone with it; include reconciles the boot block only
firewall restart
fw3 flushes everything, rebuilds, runs the include last, which restores from v4.rules/v6.rules. Known limit: fw3 itself runs with an ACCEPT policy between the flush and the include
inet nym survives; same as reload
daemon restart, upgrade, shutdown
init takes the keep path: nothing removed, policy stays live; the new daemon replaces it atomically
same
explicit stop
marker → include discards persisted state, removes chains, no block
marker → tables deleted
removal
prerm stops the daemon (as above), runs the include under the lock once more, removes the runtime directory, the UCI include and the nym zone, and reloads the firewall
prerm deletes the tables, the UCI include and the nym zone, and reloads the firewall