Functional requirements
S001 Functional Requirements
Bridge lifecycle
S001-FR-001 [P1] outcalld MUST create the bridge interface via netlink using the rtnetlink crate. It MUST NOT shell out to ip or brctl.
S001-FR-002 [P1] Bridge creation MUST be idempotent. If the bridge already exists, outcalld MUST attach to it and reuse its interface index.
S001-FR-003 [P1] After creation or attach, outcalld MUST bring the bridge interface up (link state UP).
nftables base ruleset
S001-FR-004 [P1] outcalld MUST apply a base nftables ruleset to the inet outcall table with a forward chain.
S001-FR-005 [P1] The base ruleset MUST enforce a default-BLOCK policy: all forwarded traffic entering or leaving the bridge interface MUST be dropped unless explicitly allowed.
S001-FR-006 [P1] The base ruleset MUST accept packets in established or related connection tracking states, so responses to allowed outbound connections can return.
S001-FR-007 [P1] The nftables chain policy MUST be accept (not drop) so that forwarded traffic on interfaces unrelated to the bridge is not affected.
S001-FR-008 [P1] Before applying rules, outcalld MUST delete any existing inet outcall table (clean-slate). Deletion failure (table does not exist) MUST be silently ignored.
Teardown
S001-FR-009 [P1] Teardown MUST execute in this order:
S001-FR-009.a Delete the inet outcall nftables table (warn on failure, do not abort).
S001-FR-009.b Bring the bridge interface down.
S001-FR-009.c Delete the bridge interface via netlink.
S001-FR-010 [P1] When outcalld receives SIGINT (Ctrl-C) or SIGTERM, it MUST execute the teardown sequence before exiting.
Status
S001-FR-011 [P1] The status query MUST perform a fresh check every call:
S001-FR-011.a Query netlink for the bridge interface existence.
S001-FR-011.b Run nft list table inet outcall to check if the nftables table is active.
S001-FR-011.c Return bridge name, up/down state, interface index, and nftables active flag.
Configuration
S001-FR-012 [P1] The bridge name MUST be configurable via outcalld --bridge <name>.
S001-FR-013 [P1] The default bridge name MUST be outcall0, defined as DEFAULT_BRIDGE_NAME in outcall-api.
Daemon integration
S001-FR-014 [P1] The bridge endpoints MUST be served on the host unix socket via axum:
S001-FR-014.a GET /api/v1/bridge — status
S001-FR-014.b POST /api/v1/bridge/up — initialize
S001-FR-014.c POST /api/v1/bridge/down — teardown
S001-FR-015 [P1] The outcall CLI MUST provide bridge status, bridge up, and bridge down subcommands that talk to the host API.
S001-FR-016 [P1] The bridge module and API module MUST be gated with #[cfg(target_os = "linux")]. On non-Linux platforms, outcalld MUST log a warning and skip bridge initialization.
S001-FR-017 [P1] Bridge management MUST run as a Tokio task within the outcalld process. The BridgeManager MUST be shared via Arc<Mutex<BridgeManager>> between the API handlers and the main task.
Observability
S001-FR-018 [P1] All bridge operations (create, attach, up, rules applied, teardown) MUST emit structured log events via the tracing crate.
S001-FR-019 [P1] Bridge errors MUST use typed error variants (BridgeError) via thiserror:
S001-FR-019.a Connection — netlink connection failure
S001-FR-019.b Operation — bridge link operations (create, up, down, delete)
S001-FR-019.c Nftables — nft command failures
Host socket lifecycle
S001-FR-020 [P1] outcalld MUST accept a --socket <path> flag for the host API socket path (default: /run/outcall/host.sock, defined as DEFAULT_HOST_SOCKET in outcall-api).
S001-FR-021 [P1] On startup, outcalld MUST create the parent directory of the socket path if it does not exist (create_dir_all).
S001-FR-022 [P1] On startup, outcalld MUST remove any stale socket file at the configured path before binding. Removal failure (file does not exist) MUST be silently ignored.
S001-FR-023 [P1] outcalld MUST bind a tokio::net::UnixListener at the socket path and serve the axum router over it.
S001-FR-024 [P1] On shutdown, outcalld MUST remove the socket file after teardown completes.
CLI transport
S001-FR-025 [P1] The outcall CLI MUST communicate with outcalld by sending raw HTTP/1.0 requests over the unix socket and reading the full response after the server closes the connection.
S001-FR-026 [P1] The CLI MUST use synchronous std::os::unix::net::UnixStream (not Tokio). The CLI does not require an async runtime.
S001-FR-027 [P1] The CLI MUST accept a global --socket <path> flag (default: DEFAULT_HOST_SOCKET) to locate the daemon socket.
S001-FR-028 [P1] If the socket is not reachable, the CLI MUST print cannot connect to outcalld at <path> — is it running? to stderr and exit with code 1.