Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/wg-appliance-core/src/lib.rs

//! **wg-appliance-core** — the ONE userspace WireGuard implementation in the
//! nordisk suite.
//!
//! # Why userspace, and why this crate exists at all
//!
//! An OS-less appliance is a single statically linked binary and a kernel it did
//! not choose. Kernel WireGuard makes the tunnel a property of **that kernel**:
//! `CONFIG_WIREGUARD` has to be `y` or `m`, and when it is `m` the four `.ko`
//! files have to be staged somewhere, matched to the exact kernel release, and
//! loaded before the link kind exists. A released image cannot carry them —
//! they belong to a kernel the image is packed with at build time on somebody
//! else's box — so a kernel-WireGuard appliance ships with a tunnel that cannot
//! come up until an operator rebuilds it.
//!
//! boringtun links the whole data plane into the binary instead. The only kernel
//! feature needed is `CONFIG_TUN`, which is **built in** on every ordinary
//! distribution kernel (`=y` on the kernel gunnar's ISO packs, measured
//! 2026-08-12), so the tunnel works regardless of what the packed kernel thinks
//! about WireGuard. OS-LESS means everything is in the compiled heap.
//!
//! # What is here, and what is deliberately NOT
//!
//! Here — the parts that would otherwise be written twice:
//!
//! * [`keys`] — base64 ⇄ 32 raw bytes, x25519 public-key derivation, and
//!   **keygen** (`wg genkey` without a shell).
//! * [`ini`] — the `wg-quick(8)` INI **tokenizer**: comments, sections,
//!   `Key = value`, line numbers. Tokens only.
//! * [`tun`] — `/dev/net/tun` + `TUNSETIFF`, and the MTU.
//! * [`dataplane`] — the boringtun pump: one thread, `poll(2)` over the tun fd
//!   and one UDP socket, `encapsulate` out, `decapsulate` in, `update_timers`
//!   on a tick, and AllowedIPs longest-prefix routing to pick the peer.
//!
//! * [`ceremony`] — the appliance **identity ceremony**: load-or-mint the
//!   keypair at a path (idempotent, fail-closed), the ONE canonical identity
//!   line every product prints and every watcher greps, and the hub-file
//!   dialect (`hub.conf`: one `[Peer]`, public data only).
//!
//! NOT here, on purpose:
//!
//! * **A config schema.** tunnr's payload accepts `DNS`/`MTU`/IPv6/multiple
//!   addresses and infers a server-or-client role; gunnar refuses every
//!   wg-quick key it does not implement *by name* and types its addresses as
//!   `Ipv4Addr`. Those are two different declared products, not two copies of
//!   one — merging them would silently change both. What they genuinely shared
//!   was the tokenizer, and the tokenizer is what moved.
//!
//!   **Amendment (the ceremony's dialect is not an exception):** `hub.conf` is
//!   not gunnar's schema and not tunnr's — it is the ceremony's OWN, single,
//!   product-identical dialect (one `[Peer]`, `PublicKey`/`AllowedIPs`/
//!   `Endpoint`/`PersistentKeepalive`, everything else refused by name). It
//!   lives here precisely BECAUSE it must be byte-identical for holger, gunnar
//!   and korp across both the ISO and the container shape — a per-product copy
//!   is the drift this crate exists to prevent. The products' full wg-quick
//!   surfaces remain their own.
//! * **A route writer.** gunnar has a netlink writer with an acknowledged
//!   `NLMSG_ERROR` on every message and one `apply` every address on the
//!   machine goes through; that stays gunnar's own declared surface.
//!
//!   **Amendment (2026-08-13, the [`addr`] module):** the *ioctl address
//!   writer* — `SIOCSIFADDR`/`SIOCSIFNETMASK`/`IFF_UP`, LIFTED from tunnr's
//!   `linux.rs` exactly as `keys` was lifted — now lives here, because new
//!   consumers (holger first) had NO existing answer and each growing its own
//!   would be the third copy this crate exists to prevent. One writer for
//!   consumers without one; gunnar keeps its netlink writer by prior ruling;
//!   tunnr's fold-in is pending alongside its keys fold-in. Routes BEYOND the
//!   connected subnet (netlink `RTM_NEWROUTE`) remain deliberately absent —
//!   declare the appliance address with its real site prefix and the kernel's
//!   connected route answers the hub without one.
//!
//! # The pump's shape, stated once
//!
//! One OS thread, not a task pool: a WireGuard `Tunn` is a state machine that
//! must be stepped in order, and every packet touches it. Concurrency would buy
//! nothing and cost the ordering. (Suite law: concurrency is `gatling`, never
//! rayon; this crate is single-threaded and pulls neither.)

pub mod addr;
pub mod appliance;
pub mod ceremony;
pub mod dataplane;
/// The hub **test responder** — behind the `hubfixture` feature (default ON).
///
/// It mints a keypair at runtime and binds `0.0.0.0`, which is right for a
/// proof harness and wrong for an appliance image. Until 2026-09-05 it was an
/// unconditional module and the only thing keeping it out of `wg-init` was the
/// linker dropping code nothing called. That is an optimisation, not a
/// posture. A payload compiles it out with `default-features = false`.
#[cfg(feature = "hubfixture")]
pub mod hubfixture;
/// The N-peer RELAY beside the one-peer fixture — the same posture: a proof
/// harness's hub (binds `0.0.0.0`, holds a hub key), compiled out of an image.
#[cfg(feature = "hubfixture")]
pub mod hubrelay;
pub mod ini;
pub mod keys;
/// The podman/docker ceremony robot: a test tool, behind the `robot` feature
/// (not a default), so a runtime build never compiles a process spawner.
#[cfg(any(feature = "robot", test))]
pub mod robot;
pub mod tun;

pub use ceremony::{
    identity_line, load_or_mint_identity, minted_line, parse_hub_config, parse_identity_line,
    read_hub_config, CeremonyError, HubPeer, Identity, HUB_CONF_FILE, IDENTITY_KEY_FILE,
    IDENTITY_LINE_REGEX, IDENTITY_PUB_FILE,
};
pub use dataplane::{PeerSpec, PeerStats, Router, TunnelHandle, TunnelSpec};
#[cfg(feature = "hubfixture")]
pub use hubfixture::{HubFixture, HubVerdict};
pub use keys::{
    decode_key, encode_key, generate_keypair, public_from_private, KeyError, KEY_LEN, REDACTED,
};
#[cfg(any(feature = "robot", test))]
pub use robot::{ArmOutcome, ContainerCeremony};
pub use tun::TunDevice;