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

nordisk/gunnar

crates/gunnar-server/src/wg.rs

//! **WireGuard on the appliance**: a stable address that outlives the DHCP lease.
//!
//! The appliance's outer address is whatever a DHCP server felt like granting
//! (`appliance_pid1::dhcp`, `crate::boot::bring_up_network`), and it can change mid-life —
//! `appliance_pid1::runtime::dhcp_renewal` logs exactly that event and re-applies it. An address
//! nobody can predict is an address nobody can put in a config file, so this
//! module gives the machine a **second, declared** address on a tunnel: ssh and
//! the control plane already bind `0.0.0.0`, so the moment `wg0` exists with an
//! address on it they answer there too, and nobody has to read a lease table.
//!
//! # The lane: USERSPACE, compiled in
//!
//! The WireGuard data plane is **linked into this binary** (`boringtun`, through
//! `wg-appliance-core` — the one implementation the suite has, shared with
//! tunnr's `wg-appliance` PID-1 payload). The tunnel needs:
//!
//! ```text
//!   CONFIG_TUN         builtin (=y) on the kernel the ISO packs, MEASURED 2026-08-12
//!                      — and on every ordinary distribution kernel
//!   CONFIG_WIREGUARD   NOTHING. Not y, not m, not present.
//!   modules            NONE. No finit_module, no /lib/modules, no staged .ko.zst
//!   tooling            NONE. No wg(8), no wg-quick, no ip(8), no shell
//! ```
//!
//! This is the correction of the kernel lane that stood here until now
//! (8aa6103a). That lane worked, and it worked on **one box**: it needed
//! `CONFIG_WIREGUARD=m` plus four `.ko.zst` files matched to the exact kernel
//! release, staged into the image by hand. The files were declared nowhere and
//! shipped nowhere, so a RELEASED ISO had no tunnel — the capability existed
//! only for whoever rebuilt the image on the same machine that had the modules.
//! OS-LESS means everything is in the compiled heap; a tunnel that depends on
//! what the packed kernel was compiled with is not.
//!
//! The cost is honest and was paid on purpose: boringtun brings x25519-dalek,
//! chacha20poly1305, blake2 and ring into the appliance's dependency graph.
//! Measured, not estimated — see `.nornir/build-variants.toml`.
//!
//! # Seeding, and why not the kernel command line
//!
//! A WireGuard private key is a **secret**, and `/proc/cmdline` is world
//! readable — so `GUNNAR_WG=…` would publish the key to every process on the
//! machine, which is the opposite of what the tunnel is for. The configuration
//! is a file in the data root instead, arriving by the same route as every other
//! piece of appliance state:
//!
//! ```text
//!   ISO        assets/appliance-root/data/wg0.conf   → /data/wg0.conf   (RAM)
//!   container  the /data volume
//!   service    $GUNNAR_DATA_ROOT/wg0.conf on real disk
//! ```
//!
//! On the ISO that tree is `[[build.iso]].staging`, and it ships holding a
//! `.keep` and nothing else — deliberately, because a committed key is a key
//! everybody has. An operator who wants a tunnel builds their own ISO with their
//! own keys in it, which is also the only shape in which the private key is not
//! in this repository.
//!
//! # Strictly additive
//!
//! No `wg0.conf` ⇒ one INFO line and nothing else changes. A config that is
//! present but **malformed** is an error, matching how a malformed `GUNNAR_NET`
//! is fatal: a declaration that lies must stop the boot. A config that is well
//! formed but that the KERNEL refuses (no `/dev/net/tun`, no `CAP_NET_ADMIN`)
//! is a WARN and the boot goes on — the DHCP posture, because there the world
//! failed to provide, not the declaration.
//!
//! # What this does NOT do
//!
//! * **No routes beyond the address's own.** `Address = 10.99.0.2/24` gives the
//!   kernel a connected route for `10.99.0.0/24` when [`appliance_pid1::net::apply`]
//!   installs it, and that covers the ordinary case where `AllowedIPs` is inside
//!   the tunnel subnet. An `AllowedIPs` OUTSIDE it needs a route this module
//!   does not add, and says so at startup rather than silently blackholing.
//!   (The data plane's AllowedIPs table decides which PEER an outbound packet
//!   belongs to; it does not decide which packets the kernel hands to `wg0`.)
//! * **No endpoint re-resolution.** An `Endpoint` is parsed as `IP:port`, not a
//!   hostname: the appliance has no resolver configured at this point in the
//!   boot and a DNS lookup here would be a new failure mode before the network
//!   is proven.
//! * **No IPv6 transport.** The outer socket is `AF_INET`. Inner IPv6 would be
//!   carried, but nothing configures it and `Address`/`AllowedIPs` are IPv4.
//! * **Nothing about the outer address changing.** When DHCP renewal moves the
//!   appliance's outer address (`appliance_pid1::runtime::dhcp_renewal`'s `Renewed { changed:
//!   Some(..) }`), the tunnel keeps working in the direction the appliance
//!   initiates — WireGuard's own roaming updates the PEER's idea of the endpoint
//!   from the next authenticated packet. What breaks is the peer reaching the
//!   appliance FIRST after such a move, until the appliance sends something;
//!   `PersistentKeepalive` is the operator's answer and is why it is honoured.
//!   Not solved here, and not papered over.

use std::net::{Ipv4Addr, SocketAddrV4};
use std::path::Path;
use std::sync::Arc;

use anyhow::{bail, Context, Result};
use wg_appliance_core::dataplane::{self, PeerSpec, TunnelHandle, TunnelSpec};
use wg_appliance_core::ini;
use wg_appliance_core::keys;
use wg_appliance_core::tun::{TunDevice, DEFAULT_MTU};

/// A WireGuard key: 32 raw bytes, however it was spelled.
const KEY_LEN: usize = keys::KEY_LEN;

/// The file, relative to the data root, that turns the tunnel on by existing.
pub const CONFIG_FILE: &str = "wg0.conf";

/// The **private key alone**, relative to the data root: one base64 line.
///
/// The companion of [`BOOT_PARAM`]. The split exists because exactly one field
/// of a WireGuard configuration is a secret, and the other five are the ones an
/// operator wants to declare, version and repeat per site.
pub const KEY_FILE: &str = "wg0.key";

/// The boot parameter carrying the **non-secret half** of the tunnel.
///
/// `GUNNAR_WG=addr=10.99.0.2/24,peer=<base64>,endpoint=10.0.2.2:51820,allowed=10.99.0.0/24,keepalive=25`
///
/// Linux turns an unrecognised `key=value` boot parameter into an environment
/// variable for init (`init/main.c`, `unknown_bootoption`), which is how
/// `GUNNAR_NET` already arrives — so this rides a channel that is proven and
/// declared (`.nornir/build-variants.toml`, `[[build.iso]].boot_params`).
///
/// **What may and may not travel this way, and why the line is exactly here.**
/// `/proc/cmdline` is world readable. A hub's public key, an endpoint and an
/// AllowedIPs list are not secrets — they are published to every peer of the
/// network by definition, and WireGuard's security does not rest on hiding
/// them. A PRIVATE key is a secret, and putting it here would hand it to every
/// process on the machine, which is the opposite of what a tunnel is for. So
/// the private key comes from [`KEY_FILE`] in the data root and nothing else
/// does.
pub const BOOT_PARAM: &str = "GUNNAR_WG";

/// **The one read of [`BOOT_PARAM`]**, and the only line in this module that
/// touches the process environment.
///
/// Blank is absent: a boot parameter Linux passed through as `GUNNAR_WG=` (or
/// one an operator left empty) declares nothing, and treating it as a
/// declaration is how a machine ends up refusing to boot because two lanes are
/// "both armed" when one of them is an empty string.
///
/// Every decision that depends on it takes the answer as an argument —
/// [`WgConfig::resolve_with`], [`seeded_lane_conflict`] — so the rules are
/// testable without a process-wide mutation. See `resolve`'s note for the
/// flake that made this a rule.
pub fn boot_param() -> Option<String> {
    std::env::var(BOOT_PARAM)
        .ok()
        .filter(|s| !s.trim().is_empty())
}

// ── the configuration ───────────────────────────────────────────────────────

/// One peer of the tunnel.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Peer {
    /// The peer's public key, 32 raw bytes.
    pub public_key: [u8; KEY_LEN],
    /// An optional pre-shared key, 32 raw bytes, mixed in on top of the
    /// Curve25519 handshake.
    pub preshared_key: Option<[u8; KEY_LEN]>,
    /// What this peer is allowed to send from and be routed to: `(address,
    /// prefix)` pairs.
    pub allowed_ips: Vec<(Ipv4Addr, u8)>,
    /// Where to reach the peer, if this end initiates. `IP:port` only — see the
    /// module docs on why a hostname is refused.
    pub endpoint: Option<SocketAddrV4>,
    /// Seconds between keepalives, or `None` for off.
    pub persistent_keepalive: Option<u16>,
}

/// A whole `wg0.conf`, parsed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WgConfig {
    /// The interface to create, taken from the config FILE's stem so the name
    /// and the file cannot disagree.
    pub interface: String,
    /// This end's private key, 32 raw bytes.
    pub private_key: [u8; KEY_LEN],
    /// The tunnel address to put on the interface, and its prefix.
    pub address: Ipv4Addr,
    /// The tunnel address's prefix length.
    pub prefix: u8,
    /// The UDP port to listen on. `None` lets the kernel choose, which is right
    /// for a machine that only ever initiates.
    pub listen_port: Option<u16>,
    /// The peers. At least one, or there is no tunnel to speak of.
    pub peers: Vec<Peer>,
}

impl WgConfig {
    /// Parse the `wg-quick(8)` INI subset gunnar understands.
    ///
    /// A **subset**, named exhaustively so an operator is never guessing which
    /// half of their file was read:
    ///
    /// ```text
    ///   [Interface]  PrivateKey  Address  ListenPort
    ///   [Peer]       PublicKey  PresharedKey  AllowedIPs  Endpoint  PersistentKeepalive
    /// ```
    ///
    /// Everything `wg-quick` itself implements with a shell — `DNS`, `MTU`,
    /// `Table`, `PreUp`, `PostUp`, `SaveConfig` — is **refused by name** rather
    /// than ignored. An ignored `PostUp` is a command the operator believes ran;
    /// The no-OS rule bans gunnar from running one at all, so the honest answer is to say so
    /// at parse time and not at 3 a.m.
    ///
    /// The line SCANNER is `wg_appliance_core::ini`, shared with tunnr's payload
    /// so there is one answer to "where does a comment end" — the POLICY below
    /// is gunnar's alone, because tunnr's payload deliberately accepts keys this
    /// one refuses.
    ///
    /// `interface` is the config file's stem, not a field: two sources for one
    /// name is a way for them to disagree.
    pub fn parse(interface: &str, text: &str) -> Result<WgConfig> {
        /// The keys `wg-quick` has and gunnar deliberately does not.
        const REFUSED: [&str; 8] = [
            "dns",
            "mtu",
            "table",
            "preup",
            "postup",
            "predown",
            "postdown",
            "saveconfig",
        ];

        #[derive(PartialEq)]
        enum Section {
            None,
            Interface,
            Peer,
        }

        let mut section = Section::None;
        let mut private_key: Option<[u8; KEY_LEN]> = None;
        let mut cidr: Option<(Ipv4Addr, u8)> = None;
        let mut listen_port: Option<u16> = None;
        let mut peers: Vec<Peer> = Vec::new();

        for item in ini::items(text) {
            let line = item.line();
            let (key, value) = match item {
                ini::Item::Section { name, raw_name, .. } => {
                    section = match name.as_str() {
                        "interface" => Section::Interface,
                        "peer" => {
                            peers.push(Peer {
                                public_key: [0; KEY_LEN],
                                preshared_key: None,
                                allowed_ips: Vec::new(),
                                endpoint: None,
                                persistent_keepalive: None,
                            });
                            Section::Peer
                        }
                        _ => bail!("{interface}.conf:{line}: unknown section [{raw_name}]"),
                    };
                    continue;
                }
                ini::Item::Malformed { text, .. } => {
                    bail!("{interface}.conf:{line}: {text:?} is not `Key = value`")
                }
                ini::Item::Pair { key, value, .. } => (key, value),
            };
            if REFUSED.contains(&key.as_str()) {
                bail!(
                    "{interface}.conf:{line}: `{key}` is a wg-quick key gunnar does NOT implement. \
                     It is refused rather than ignored, because an ignored key reads as one that \
                     was honoured. (PreUp/PostUp/PostDown run commands, and this server spawns \
                     nothing; DNS/MTU/Table are not configured here.)"
                );
            }
            match (&section, key.as_str()) {
                (Section::None, _) => {
                    bail!(
                        "{interface}.conf:{line}: `{key}` before any [Interface] or [Peer] section"
                    )
                }
                (Section::Interface, "privatekey") => {
                    private_key = Some(
                        parse_key(value)
                            .with_context(|| format!("{interface}.conf:{line}: PrivateKey"))?,
                    );
                }
                (Section::Interface, "address") => {
                    cidr = Some(
                        parse_cidr(value)
                            .with_context(|| format!("{interface}.conf:{line}: Address"))?,
                    );
                }
                (Section::Interface, "listenport") => {
                    listen_port = Some(value.parse::<u16>().with_context(|| {
                        format!("{interface}.conf:{line}: ListenPort {value:?} is not a port")
                    })?);
                }
                (Section::Peer, "publickey") => {
                    let k = parse_key(value)
                        .with_context(|| format!("{interface}.conf:{line}: PublicKey"))?;
                    peers
                        .last_mut()
                        .expect("a [Peer] section pushed one")
                        .public_key = k;
                }
                (Section::Peer, "presharedkey") => {
                    let k = parse_key(value)
                        .with_context(|| format!("{interface}.conf:{line}: PresharedKey"))?;
                    peers
                        .last_mut()
                        .expect("a [Peer] section pushed one")
                        .preshared_key = Some(k);
                }
                (Section::Peer, "allowedips") => {
                    let mut ips = Vec::new();
                    for one in value.split(',') {
                        let one = one.trim();
                        if one.is_empty() {
                            continue;
                        }
                        ips.push(parse_cidr(one).with_context(|| {
                            format!("{interface}.conf:{line}: AllowedIPs entry {one:?}")
                        })?);
                    }
                    if ips.is_empty() {
                        bail!("{interface}.conf:{line}: AllowedIPs is empty");
                    }
                    peers
                        .last_mut()
                        .expect("a [Peer] section pushed one")
                        .allowed_ips = ips;
                }
                (Section::Peer, "endpoint") => {
                    let addr: SocketAddrV4 = value.parse().with_context(|| {
                        format!(
                            "{interface}.conf:{line}: Endpoint {value:?} is not <IPv4>:<port>. A \
                             HOSTNAME is deliberately not accepted: the appliance has no resolver \
                             configured this early in the boot"
                        )
                    })?;
                    peers
                        .last_mut()
                        .expect("a [Peer] section pushed one")
                        .endpoint = Some(addr);
                }
                (Section::Peer, "persistentkeepalive") => {
                    let secs: u16 = value.parse().with_context(|| {
                        format!(
                            "{interface}.conf:{line}: PersistentKeepalive {value:?} is not a \
                             number of seconds"
                        )
                    })?;
                    peers
                        .last_mut()
                        .expect("a [Peer] section pushed one")
                        .persistent_keepalive = (secs != 0).then_some(secs);
                }
                (_, other) => {
                    bail!("{interface}.conf:{line}: unknown key `{other}` in this section")
                }
            }
        }

        let private_key = private_key
            .with_context(|| format!("{interface}.conf: [Interface] has no PrivateKey"))?;
        let (address, prefix) =
            cidr.with_context(|| format!("{interface}.conf: [Interface] has no Address"))?;
        if peers.is_empty() {
            bail!("{interface}.conf: no [Peer] section — a tunnel with no peer reaches nothing");
        }
        for (i, peer) in peers.iter().enumerate() {
            if peer.public_key == [0; KEY_LEN] {
                bail!("{interface}.conf: [Peer] #{} has no PublicKey", i + 1);
            }
            if peer.allowed_ips.is_empty() {
                bail!(
                    "{interface}.conf: [Peer] #{} has no AllowedIPs — nothing would be routed \
                     into the tunnel for it and nothing accepted from it",
                    i + 1
                );
            }
        }
        Ok(WgConfig {
            interface: interface.to_string(),
            private_key,
            address,
            prefix,
            listen_port,
            peers,
        })
    }

    /// Parse the [`BOOT_PARAM`] form: the non-secret half from the kernel
    /// command line, the private key from [`KEY_FILE`] in the data root.
    ///
    /// ```text
    ///   addr=10.99.0.2/24,peer=<base64>,endpoint=10.0.2.2:51820,allowed=10.99.0.0/24,keepalive=25
    /// ```
    ///
    /// Comma-separated because a WireGuard endpoint contains a colon and a
    /// base64 key contains `+`, `/` and `=` — a colon-separated grammar cannot
    /// hold both without quoting, and a kernel command line has no quoting.
    /// Each field splits on its FIRST `=` so a key's trailing padding survives.
    ///
    /// `addr`, `peer` and `allowed` are required; `endpoint` and `keepalive`
    /// are not. One peer only: this form is for the ordinary appliance shape —
    /// one machine dialling one hub — and a second peer wants the file.
    pub fn parse_boot_param(
        interface: &str,
        spec: &str,
        private_key: [u8; KEY_LEN],
    ) -> Result<WgConfig> {
        let (mut addr, mut peer, mut allowed, mut endpoint, mut keepalive) =
            (None, None, None, None, None);
        for field in spec.split(',') {
            let field = field.trim();
            if field.is_empty() {
                continue;
            }
            let (key, value) = field
                .split_once('=')
                .with_context(|| format!("{BOOT_PARAM}: {field:?} is not `key=value`"))?;
            match key.trim().to_ascii_lowercase().as_str() {
                "addr" => addr = Some(parse_cidr(value).context("addr=")?),
                "peer" => peer = Some(parse_key(value).context("peer=")?),
                "allowed" => {
                    let mut ips = Vec::new();
                    // `allowed=` may be repeated rather than list-separated,
                    // because the list separator is already the field separator.
                    for one in value.split_whitespace() {
                        ips.push(parse_cidr(one).context("allowed=")?);
                    }
                    let mut all: Vec<_> = allowed.unwrap_or_default();
                    all.extend(ips);
                    allowed = Some(all);
                }
                "endpoint" => {
                    endpoint = Some(value.parse::<SocketAddrV4>().with_context(|| {
                        format!(
                            "{BOOT_PARAM}: endpoint={value:?} is not <IPv4>:<port>; a hostname \
                             is deliberately not accepted (no resolver this early in the boot)"
                        )
                    })?)
                }
                "keepalive" => {
                    let secs: u16 = value.parse().with_context(|| {
                        format!("{BOOT_PARAM}: keepalive={value:?} is not a number of seconds")
                    })?;
                    keepalive = (secs != 0).then_some(secs);
                }
                other => bail!(
                    "{BOOT_PARAM}: unknown field `{other}`. This form takes addr, peer, allowed, \
                     endpoint and keepalive — and deliberately NOT a private key, which would be \
                     world-readable in /proc/cmdline"
                ),
            }
        }
        let (address, prefix) =
            addr.with_context(|| format!("{BOOT_PARAM}: no `addr=<ip>/<prefix>`"))?;
        let public_key =
            peer.with_context(|| format!("{BOOT_PARAM}: no `peer=<hub public key>`"))?;
        let allowed_ips = allowed
            .filter(|v: &Vec<_>| !v.is_empty())
            .with_context(|| format!("{BOOT_PARAM}: no `allowed=<cidr>`"))?;
        Ok(WgConfig {
            interface: interface.to_string(),
            private_key,
            address,
            prefix,
            // The kernel picks a port. A machine that only ever dials out does
            // not need a fixed one, and a fixed one is a thing to collide.
            listen_port: None,
            peers: vec![Peer {
                public_key,
                preshared_key: None,
                allowed_ips,
                endpoint,
                persistent_keepalive: keepalive,
            }],
        })
    }

    /// Read `<data_root>/wg0.conf`, if it is there.
    ///
    /// `Ok(None)` is "no tunnel was asked for" and is the shipped state.
    /// `Err` is "a tunnel was asked for and the request does not parse".
    pub fn from_data_root(data_root: &Path) -> Result<Option<WgConfig>> {
        let path = data_root.join(CONFIG_FILE);
        let text = match std::fs::read_to_string(&path) {
            Ok(t) => t,
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
            Err(err) => {
                return Err(err).with_context(|| format!("reading {}", path.display()));
            }
        };
        let interface = Path::new(CONFIG_FILE)
            .file_stem()
            .and_then(|s| s.to_str())
            .expect("CONFIG_FILE is a literal with a stem");
        Ok(Some(WgConfig::parse(interface, &text)?))
    }

    /// **The one resolver**: whichever of the two declared sources is present.
    ///
    /// | source | non-secret half | private key |
    /// |---|---|---|
    /// | [`CONFIG_FILE`] | the file | the file |
    /// | [`BOOT_PARAM`] | the kernel command line | [`KEY_FILE`] |
    ///
    /// Both present is an **error**, not a precedence rule. Two configurations
    /// that disagree is the failure a precedence rule hides: the operator who
    /// edited the one that lost would see a working tunnel with the wrong
    /// settings and no way to tell from the console.
    ///
    /// Neither present is `Ok(None)` — the shipped state, and the only reason
    /// this is safe to call unconditionally.
    ///
    /// **The env read happens here and nowhere below.** This is the edge; the
    /// decision is [`WgConfig::resolve_with`], which takes the boot parameter as
    /// an argument. The split is the same one [`seeded_lane_conflict`] already
    /// makes and for the same measured reason: a rule that can only be
    /// exercised by `set_var` on the test process is a rule whose tests flake
    /// the moment `cargo test` runs them on two threads — `std::env::set_var`
    /// is process-wide, and two tests that both own `GUNNAR_WG` are one test
    /// run twice. It was red intermittently in exactly that shape.
    pub fn resolve(data_root: &Path) -> Result<Option<WgConfig>> {
        WgConfig::resolve_with(data_root, boot_param().as_deref())
    }

    /// [`WgConfig::resolve`] with the boot parameter handed in.
    ///
    /// `None` is "the parameter is not set"; `Some(spec)` is its value, already
    /// known to be non-blank. Nothing here reads the environment, so every rule
    /// below is testable on any number of threads at once.
    pub fn resolve_with(data_root: &Path, param: Option<&str>) -> Result<Option<WgConfig>> {
        let file = WgConfig::from_data_root(data_root)?;
        let param = param.map(str::to_owned);
        match (file, param) {
            (Some(_), Some(_)) => bail!(
                "both {} and {BOOT_PARAM} configure WireGuard. They are two ways to say the same \
                 thing and this refuses to guess which one you meant: delete the file to use the \
                 boot parameter, or drop {BOOT_PARAM} from the command line to use the file",
                data_root.join(CONFIG_FILE).display()
            ),
            (Some(config), None) => Ok(Some(config)),
            (None, Some(spec)) => {
                let path = data_root.join(KEY_FILE);
                let text = std::fs::read_to_string(&path).with_context(|| {
                    format!(
                        "{BOOT_PARAM} is set, so the private key must be seeded at {} — it is \
                         the ONE field that may not ride the world-readable kernel command line",
                        path.display()
                    )
                })?;
                let private_key = parse_key(text.trim())
                    .with_context(|| format!("reading the private key from {}", path.display()))?;
                let interface = Path::new(CONFIG_FILE)
                    .file_stem()
                    .and_then(|s| s.to_str())
                    .expect("CONFIG_FILE is a literal with a stem");
                Ok(Some(WgConfig::parse_boot_param(
                    interface,
                    spec.trim(),
                    private_key,
                )?))
            }
            (None, None) => Ok(None),
        }
    }

    /// The `AllowedIPs` entries that fall OUTSIDE the interface's own subnet,
    /// and therefore need a route nothing here installs.
    ///
    /// Returned rather than logged from inside, so the caller can state it once
    /// and a test can assert it without reading a log.
    pub fn unrouted_allowed_ips(&self) -> Vec<(Ipv4Addr, u8)> {
        let mine = masked(self.address, self.prefix);
        self.peers
            .iter()
            .flat_map(|p| p.allowed_ips.iter().copied())
            .filter(|(addr, prefix)| *prefix < self.prefix || masked(*addr, self.prefix) != mine)
            .collect()
    }

    /// **This appliance's WireGuard identity**, derived from the seeded private
    /// key.
    ///
    /// Computed here rather than read back from a kernel device, which is what
    /// the deleted kernel lane did: the curve is linked into this binary now, so
    /// there is nothing to ask. `wg pubkey` computes exactly this, and
    /// `keys::tests::derives_the_public_keys_wg_pubkey_prints` pins it against
    /// two vectors that came out of the tool.
    pub fn public_key(&self) -> [u8; KEY_LEN] {
        keys::public_from_private_bytes(&self.private_key)
    }

    /// The data plane's view of this config — the seam between gunnar's declared
    /// config surface and the shared userspace WireGuard implementation.
    fn tunnel_spec(&self) -> TunnelSpec {
        TunnelSpec {
            private_key: self.private_key,
            listen_port: self.listen_port,
            peers: self
                .peers
                .iter()
                .map(|p| PeerSpec {
                    public_key: p.public_key,
                    preshared_key: p.preshared_key,
                    endpoint: p.endpoint,
                    allowed_ips: p.allowed_ips.clone(),
                    persistent_keepalive: p.persistent_keepalive,
                })
                .collect(),
        }
    }
}

/// `addr` with everything below `prefix` cleared.
fn masked(addr: Ipv4Addr, prefix: u8) -> u32 {
    let bits = u32::from(addr);
    match prefix {
        0 => 0,
        p if p >= 32 => bits,
        p => bits & !(u32::MAX >> p),
    }
}

/// A base64 WireGuard key → 32 raw bytes, through the shared decoder.
///
/// Length is checked **after** decoding, not by counting characters: a 44-char
/// string that decodes to 33 bytes is a real thing base64 can express, and a
/// key of the wrong length handed to the crypto is a tunnel that never
/// handshakes with no error anywhere.
fn parse_key(text: &str) -> Result<[u8; KEY_LEN]> {
    keys::decode_key(text).map_err(|e| anyhow::anyhow!("{e}"))
}

/// `10.99.0.2/24` → `(10.99.0.2, 24)`.
fn parse_cidr(text: &str) -> Result<(Ipv4Addr, u8)> {
    let (addr, prefix) = text
        .split_once('/')
        .with_context(|| format!("{text:?} is not <address>/<prefix>"))?;
    let addr: Ipv4Addr = addr
        .trim()
        .parse()
        .with_context(|| format!("{addr:?} is not an IPv4 address"))?;
    let prefix: u8 = prefix
        .trim()
        .parse()
        .with_context(|| format!("{prefix:?} is not a prefix length"))?;
    if prefix > 32 {
        bail!("/{prefix} is not a legal IPv4 prefix length");
    }
    Ok((addr, prefix))
}

// ── minting an identity ─────────────────────────────────────────────────────

/// **Mint a WireGuard keypair.** `wg genkey | wg pubkey`, without a shell.
///
/// The curve is linked into this binary for the tunnel itself, so this costs
/// nothing that was not already there — and the alternative costs a shell, a
/// `wg(8)` binary and a pipe, none of which exist on an appliance or in the
/// image that builds one. The installer epic needs exactly this: seed a machine
/// with an identity before it has a filesystem worth speaking of.
///
/// Entropy is `getrandom(2)`, not a read of `/dev/urandom` — an appliance mints
/// its identity before `/dev` is necessarily populated.
pub fn generate_keypair() -> ([u8; KEY_LEN], [u8; KEY_LEN]) {
    keys::generate_keypair()
}

/// `gunnar wg keygen [--private <path>]`.
///
/// With `--private`, the secret goes to the file (mode 0600) and only the
/// public half is printed — the [`KEY_FILE`] shape. Without it, both are
/// printed, which is what a person pasting into two config files wants.
///
/// **An existing file is refused, never overwritten.** A regenerated identity
/// is a silent fleet-wide outage: every hub still allowlists the old public key
/// and a peer whose key is unknown gets silence, not an error.
pub fn keygen_verb(command: gunnar_common::cli::WgCommand) -> Result<()> {
    let gunnar_common::cli::WgCommand::Keygen { private } = command;
    let (secret, public) = generate_keypair();
    let Some(path) = private else {
        println!("private {}", base64_key(&secret));
        println!("public  {}", base64_key(&public));
        return Ok(());
    };
    if path.exists() {
        bail!(
            "{} already exists. This refuses to overwrite an identity: every hub that peers \
             with this machine allowlists its CURRENT public key, and a peer whose key is \
             unknown to the other end gets silence rather than an error. Delete the file \
             deliberately if you really mean to mint a new identity.",
            path.display()
        );
    }
    write_secret(&path, &format!("{}\n", base64_key(&secret)))
        .with_context(|| format!("writing the private key to {}", path.display()))?;
    println!("public  {}", base64_key(&public));
    Ok(())
}

/// Write a secret so it is unreadable to anyone but its owner FROM THE MOMENT
/// IT EXISTS — the mode goes in the `open(2)`, not in a `chmod` afterwards.
/// A create-then-chmod leaves a window in which the key is world readable, and
/// on the box this is used to provision a fleet that window is enough.
fn write_secret(path: &Path, body: &str) -> std::io::Result<()> {
    use std::io::Write as _;
    use std::os::unix::fs::OpenOptionsExt as _;
    let mut f = std::fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .mode(0o600)
        .open(path)?;
    f.write_all(body.as_bytes())
}

// ── the orchestrator ────────────────────────────────────────────────────────

/// A tunnel that was brought up, and what is needed to report on it later.
#[derive(Clone)]
pub struct Tunnel {
    /// The interface, e.g. `wg0`.
    pub interface: String,
    /// The tunnel address ssh and the control plane now also answer on.
    pub address: Ipv4Addr,
    /// The tunnel address's prefix length.
    pub prefix: u8,
    /// How many peers were configured.
    pub peers: usize,
    /// **This appliance's WireGuard public key**, derived from the seeded
    /// private key.
    ///
    /// It goes in the boot banner, and the reason is a property of WireGuard
    /// rather than a nicety: peering is mutual allowlisting BY PUBLIC KEY, and
    /// a key the hub does not know produces **silence**, not an error. So the
    /// one question an operator asks when a tunnel does not come up — "is the
    /// key on this box the key I put in the hub's `[Peer]` stanza?" — has to be
    /// answerable from the console, which on a shell-less appliance is the only
    /// channel there is.
    pub public_key: [u8; KEY_LEN],
    /// The UDP port the data plane bound.
    pub local_port: u16,
    /// The configuration the pump runs on — what a later [`raise_from_config`]
    /// compares the configuration on disk against. Shared by every clone, so
    /// a re-point is seen by all of them.
    config: Arc<std::sync::Mutex<WgConfig>>,
    /// The running pump. Shared rather than owned, because the handshake watch
    /// polls it from a task while `serve` holds it for the process lifetime —
    /// and dropping the last one deletes the interface.
    ///
    /// A SLOT since 2026-10-01 (recharge-front-new-key-deadlock part 2): a
    /// re-feed that names another front relay stops this pump and starts the
    /// next one in the same slot ([`Tunnel::repoint`]), and every clone — the
    /// one `serve` holds, the pairing loop's — runs on the new one. `None`
    /// while a re-point is between the two, or after one that failed.
    handle: Arc<std::sync::Mutex<Option<TunnelHandle>>>,
}

impl std::fmt::Debug for Tunnel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Tunnel")
            .field("interface", &self.interface)
            .field("address", &self.address)
            .field("prefix", &self.prefix)
            .field("peers", &self.peers)
            .field("public_key", &base64_key(&self.public_key))
            .field("local_port", &self.local_port)
            .finish()
    }
}

impl Tunnel {
    /// The banner line: what a person reads off the console to check the
    /// appliance against their hub.
    pub fn banner_line(&self) -> String {
        banner_line(
            &self.interface,
            self.address,
            self.prefix,
            &self.public_key,
            self.peers,
            self.local_port,
        )
    }

    /// True when any peer has completed a handshake — "the tunnel WORKS", as
    /// opposed to "the tunnel is configured".
    pub fn handshaken(&self) -> bool {
        self.handle
            .lock()
            .unwrap_or_else(|p| p.into_inner())
            .as_ref()
            .map(TunnelHandle::handshaken)
            .unwrap_or(false)
    }

    /// Every peer as the running pump last saw it (none while no pump runs).
    pub fn peer_stats(&self) -> Vec<dataplane::PeerStats> {
        self.handle
            .lock()
            .unwrap_or_else(|p| p.into_inner())
            .as_ref()
            .map(TunnelHandle::peers)
            .unwrap_or_default()
    }

    /// The configuration the pump runs on, or `None` when no pump runs.
    pub fn running_config(&self) -> Option<WgConfig> {
        let up = self
            .handle
            .lock()
            .unwrap_or_else(|p| p.into_inner())
            .is_some();
        up.then(|| {
            self.config
                .lock()
                .unwrap_or_else(|p| p.into_inner())
                .clone()
        })
    }

    /// **Re-point the tunnel at `wanted`, in this process** — the front's
    /// relay moved (a front recharged onto new addresses) or its key changed.
    /// The old pump is stopped first (its tun fd closes, which deletes the
    /// interface), then `wanted` is raised on the same interface; the address
    /// is this box's own and does not change. A failure leaves no pump: the
    /// next re-feed, the pairing loop or the next start raises it.
    pub fn repoint(&self, wanted: &WgConfig) -> Result<()> {
        let mut slot = self.handle.lock().unwrap_or_else(|p| p.into_inner());
        if let Some(mut old) = slot.take() {
            old.shutdown();
        }
        let handle = open_pump(wanted)?;
        *slot = Some(handle);
        *self.config.lock().unwrap_or_else(|p| p.into_inner()) = wanted.clone();
        crate::tunnel_addr::publish(Some(wanted.address));
        Ok(())
    }
}

/// The peers of a configuration, for a log line: `<key fingerprint>@<endpoint>`.
fn peers_said(c: &WgConfig) -> String {
    c.peers
        .iter()
        .map(|p| {
            format!(
                "{}@{}",
                &base64_key(&p.public_key)[..8],
                p.endpoint
                    .map(|e| e.to_string())
                    .unwrap_or_else(|| "-".into())
            )
        })
        .collect::<Vec<_>>()
        .join(", ")
}

/// **Bring the tunnel up**, if the data root asks for one.
///
/// Called from `serve::run` AFTER the outer address is applied, because a
/// tunnel whose endpoint is reached over the outer address cannot handshake
/// before that address exists.
///
/// The three outcomes, and they are deliberately different grades:
///
/// * **no config** ⇒ `Ok(None)` and one INFO line. The shipped state.
/// * **config that does not parse** ⇒ `Err`. A declaration that lies stops the
///   boot, exactly as a malformed `GUNNAR_NET` does.
/// * **config the kernel refuses** ⇒ `Ok(None)` and a WARN naming the reason.
///   The world failed to provide; the declaration did not lie, and an appliance
///   that refuses to serve git because a tunnel would not come up is worse than
///   one that serves on its outer address and says so.
///
/// # Two lanes, and only one of them may be armed
///
/// With `GUNNAR_APPLIANCE=1` (declared by the `[[build.installer]]` cell) the
/// **ceremony** owns the identity: [`crate::appliance::run`] mints or loads it
/// on the durable root, announces it, and hands back the config a declared
/// `hub.conf` implies. Without the gate the **seeded** lane below is what runs,
/// unchanged — `wg0.conf` or [`BOOT_PARAM`] + [`KEY_FILE`].
///
/// Both armed at once is REFUSED rather than resolved by precedence. It is the
/// same ruling [`WgConfig::resolve`] already makes between its own two sources
/// and for a sharper reason: the field that loses here is the machine's
/// IDENTITY, so the operator who registered the losing key with the hub would
/// watch a healthy boot present a key nobody trusts.
pub fn bring_up(data_root: &Path) -> Result<Option<Tunnel>> {
    bring_up_with(crate::appliance::gate_enabled(), data_root)
}

/// [`bring_up`] with the gate handed in (testable without the env).
pub fn bring_up_with(gate: bool, data_root: &Path) -> Result<Option<Tunnel>> {
    if boot_lane(gate, data_root) == BootLane::Ceremony {
        return ceremony_lane(data_root);
    }
    let Some(config) = WgConfig::resolve(data_root)? else {
        tracing::info!(
            path = %data_root.join(CONFIG_FILE).display(),
            "no WireGuard configuration: serving on the outer address only. Either drop a \
             wg-quick style {CONFIG_FILE} in the data root, or set {BOOT_PARAM} on the kernel \
             command line and seed {KEY_FILE} beside it. Nothing else is needed — the WireGuard \
             data plane is compiled into this binary and wants no kernel module."
        );
        return Ok(None);
    };
    match establish(&config) {
        Ok(tunnel) => {
            // Kept where the pairing loop's driver sees it: `raise_from_config`
            // answers "already up", and `config_tunnel_handshaken` can say
            // whether the front has answered.
            *LATE_TUNNEL.lock().unwrap_or_else(|p| p.into_inner()) = Some(tunnel.clone());
            Ok(Some(tunnel))
        }
        Err(err) => {
            tracing::warn!(
                interface = %config.interface,
                "the WireGuard configuration parsed and the tunnel would NOT come up: {err:#}. \
                 Serving on the outer address only — no tunnel address exists, so a client \
                 pointed at {} will not connect.",
                config.address
            );
            Ok(None)
        }
    }
}

/// Which lane raises the tunnel at boot.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootLane {
    /// The identity ceremony (`GUNNAR_APPLIANCE=1`, no pairing).
    Ceremony,
    /// `wg0.conf` / [`BOOT_PARAM`] — and the pairing loop's `wg0.conf`.
    Config,
}

/// **Does the pairing loop own this box's tunnel?** A pairing seed on the
/// data root, or a `wg0.conf` the pairing loop wrote.
pub fn pairing_owns_tunnel(data_root: &Path) -> bool {
    data_root.join(crate::pairing::SEED_REL).exists()
        || std::fs::read_to_string(data_root.join(CONFIG_FILE))
            .map(|t| t.starts_with(crate::pairing::CONF_MARK))
            .unwrap_or(false)
}

/// **The boot's lane, decided once** (2026-09-24). The appliance medium sets
/// `GUNNAR_APPLIANCE=1`, and a PAIRED box has a `wg0.conf` its pairing loop
/// wrote: taking the ceremony lane there refused the boot ("both lanes armed")
/// and PID 1 faulted — a reboot bricked every paired box. When the pairing
/// loop owns the tunnel ([`pairing_owns_tunnel`]) the CONFIG lane raises its
/// `wg0.conf` (or, before the first join, nothing — the loop raises it later)
/// and the ceremony stays out, whatever the gate says.
pub fn boot_lane(gate: bool, data_root: &Path) -> BootLane {
    if gate && !pairing_owns_tunnel(data_root) {
        BootLane::Ceremony
    } else {
        BootLane::Config
    }
}

/// [`pairing_tunnel`]'s answer: public key, address and prefix (if assigned),
/// and whether `wg0` carries that address now.
pub type PairingTunnel = (String, Option<(Ipv4Addr, u8)>, bool);

/// **The tunnel the pairing loop owns, as `tunnel show` reports it**: this
/// box's public key off `wg0.key`, the address off `wg0.conf` (none before the
/// door assigned one), and whether `wg0` carries that address NOW. `None` when
/// the pairing loop does not own the tunnel (the ceremony's answer stands).
pub fn pairing_tunnel(data_root: &Path) -> Option<PairingTunnel> {
    if !pairing_owns_tunnel(data_root) {
        return None;
    }
    let public_b64 = std::fs::read_to_string(data_root.join(KEY_FILE))
        .ok()
        .and_then(|k| wg_appliance_core::public_from_private(k.trim()).ok())
        .unwrap_or_default();
    let config = WgConfig::resolve_with(data_root, None).ok().flatten();
    let address = config.as_ref().map(|c| (c.address, c.prefix));
    let up = config
        .as_ref()
        .map(|c| {
            appliance_pid1::net::interface_ipv4(&c.interface)
                .ok()
                .flatten()
                == Some(c.address)
        })
        .unwrap_or(false);
    Some((public_b64, address, up))
}

/// **Are both lanes armed?** `Some(refusal)` when they are.
///
/// Pure — the two "is it present" answers arrive as booleans rather than being
/// read here — so the refusal's WORDING is testable without a process-wide env
/// mutation. A guard that can only be exercised by setting `GUNNAR_APPLIANCE`
/// on the test process is a guard that gets an `#[ignore]` the first time it
/// races another test, and then it guards nothing.
fn seeded_lane_conflict(
    data_root: &Path,
    seeded_file_present: bool,
    seeded_param_present: bool,
) -> Option<String> {
    if !seeded_file_present && !seeded_param_present {
        return None;
    }
    let named = if seeded_param_present {
        BOOT_PARAM.to_string()
    } else {
        data_root.join(CONFIG_FILE).display().to_string()
    };
    let gate = crate::appliance::GATE_ENV;
    Some(format!(
        "{gate}=1 arms the identity ceremony, and {named} is also declared. They are two \
         different answers to what this machine's identity IS, and this refuses to guess: \
         the ceremony MINTS a key on {} and announces it, the seeded lane takes one you \
         supplied. Drop the seeded configuration to use the ceremony, or unset {gate} to \
         keep seeding.",
        crate::appliance::wg_dir(data_root).display(),
    ))
}

/// **The ceremony lane** — the gate is on, so the identity is MINTED here and
/// announced, never seeded from outside.
///
/// The announcement goes to **stdout and the log**, formatted once through the
/// shared crate's writer and written twice (one implementation). stdout is load-bearing and
/// not a convenience: on the appliance it is `/dev/console`, the serial line
/// `installtest/src/iso.rs` greps its boot proof off, and the identity line is
/// the ONE thing an operator must read off a shell-less machine before the hub
/// can be configured at all. `tracing` alone would put it behind a filter.
fn ceremony_lane(data_root: &Path) -> Result<Option<Tunnel>> {
    // The seeded lane must be DISARMED, not merely out-ranked. Checked before
    // the ceremony runs so a misconfigured appliance refuses BEFORE minting a
    // key the operator might see and register.
    let seeded_param = boot_param();
    if let Some(refusal) = seeded_lane_conflict(
        data_root,
        data_root.join(CONFIG_FILE).exists(),
        seeded_param.is_some(),
    ) {
        bail!(refusal);
    }

    // The proof row is the ceremony's OWN verdict, both ways: `Ok` carries the
    // key it announced, `Err` carries the refusal text verbatim. Never a
    // hardcoded `true` — a row that cannot go red proves nothing, and the
    // rollup's whole discipline is that "no evidence" and "proven" must be
    // impossible to confuse.
    let outcome = match crate::appliance::run(data_root, &mut |line| {
        println!("{line}");
        tracing::info!("{line}");
    }) {
        Ok(outcome) => {
            crate::functional_status(
                "gunnar-appliance",
                "identity_ready",
                true,
                &format!(
                    "pubkey={} minted={} hub={}",
                    outcome.public_b64,
                    outcome.minted,
                    outcome.config.is_some()
                ),
            );
            outcome
        }
        Err(err) => {
            crate::functional_status(
                "gunnar-appliance",
                "identity_ready",
                false,
                &format!("{err:#}"),
            );
            return Err(err);
        }
    };

    let Some(config) = outcome.config else {
        return Ok(None);
    };
    match establish(&config) {
        Ok(tunnel) => {
            CEREMONY_TUNNEL_UP.store(true, std::sync::atomic::Ordering::SeqCst);
            Ok(Some(tunnel))
        }
        Err(err) => {
            tracing::warn!(
                interface = %config.interface,
                "the hub declaration parsed and the tunnel would NOT come up: {err:#}. \
                 Serving on the outer address only — the identity above is minted and \
                 durable, so registering it with the hub is still the right next move.",
            );
            Ok(None)
        }
    }
}

/// Set once the ceremony lane has a tunnel up — at start, or after the
/// address arrived at bootstrap.
static CEREMONY_TUNNEL_UP: std::sync::atomic::AtomicBool =
    std::sync::atomic::AtomicBool::new(false);

/// The tunnel raised AFTER start, by [`raise_after_assignment`]. `serve` holds
/// the start-up one for the process lifetime; this is the same lifetime for
/// the one that arrived later (dropping it deletes `wg0`).
static LATE_TUNNEL: std::sync::Mutex<Option<Tunnel>> = std::sync::Mutex::new(None);

/// The config lane's tunnel (raised at boot or by [`raise_from_config`]):
/// `None` when there is none in this process, else whether a peer has
/// completed a handshake.
pub fn config_tunnel_handshaken() -> Option<bool> {
    LATE_TUNNEL
        .lock()
        .unwrap_or_else(|p| p.into_inner())
        .as_ref()
        .map(Tunnel::handshaken)
}

/// Is the ceremony's tunnel up in this process?
pub fn ceremony_tunnel_up() -> bool {
    CEREMONY_TUNNEL_UP.load(std::sync::atomic::Ordering::SeqCst)
}

/// **The address just arrived** (`Bootstrap.AssignTunnelAddress`): raise the
/// tunnel NOW, without a restart. `Ok(true)` when it is up (already, or just
/// now); `Ok(false)` when the ceremony still has nothing to raise or the
/// kernel refused (said by [`ceremony_lane`]'s own WARN).
pub fn raise_after_assignment(data_root: &Path) -> Result<bool> {
    if ceremony_tunnel_up() {
        return Ok(true);
    }
    let Some(tunnel) = ceremony_lane(data_root)? else {
        return Ok(false);
    };
    if let Ok(rt) = tokio::runtime::Handle::try_current() {
        // A32-14, stays a task: a bounded watch (20 looks, 3 s apart) of one handshake, said at its end.
        rt.spawn(handshake_watch(
            tunnel.clone(),
            20,
            std::time::Duration::from_secs(3),
        ));
    }
    *LATE_TUNNEL.lock().unwrap_or_else(|p| p.into_inner()) = Some(tunnel);
    Ok(true)
}

/// **What [`raise_from_config`] does with the configuration on disk**, given
/// the tunnel this process already runs (if any). Pure, so the decision is
/// held without a tun device.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LateRaise {
    /// No configuration to raise.
    NoConfig,
    /// The tunnel is up as the configuration says.
    AlreadyUp,
    /// Raise it now.
    Raise,
    /// The tunnel runs, and the configuration names other peers (or another
    /// address): re-point it. `from`/`to` say the peers, for the log.
    Repoint { from: String, to: String },
}

/// [`LateRaise`], decided.
pub fn late_raise(
    running: Option<&WgConfig>,
    wanted: Option<&WgConfig>,
    interface_carries_address: bool,
) -> LateRaise {
    if let Some(running) = running {
        return match wanted {
            // A configuration that went away does not take a running tunnel down.
            None => LateRaise::AlreadyUp,
            Some(w)
                if w.peers == running.peers
                    && w.address == running.address
                    && w.prefix == running.prefix
                    && w.private_key == running.private_key =>
            {
                LateRaise::AlreadyUp
            }
            Some(w) => LateRaise::Repoint {
                from: peers_said(running),
                to: peers_said(w),
            },
        };
    }
    match wanted {
        None => LateRaise::NoConfig,
        Some(_) if interface_carries_address => LateRaise::AlreadyUp,
        Some(_) => LateRaise::Raise,
    }
}

/// **A `wg0.conf` that appeared after boot: raise it NOW.** The pairing loop
/// (`crate::pairing`) learns this box's tunnel address from the installer's
/// door, writes the configuration, and calls this — the same config lane
/// [`bring_up`] runs at start, without a restart. `Ok(true)` when the tunnel is
/// up (already — raised at boot from a configuration a previous boot wrote —
/// or just now).
pub fn raise_from_config(data_root: &Path) -> Result<bool> {
    let late = LATE_TUNNEL
        .lock()
        .unwrap_or_else(|p| p.into_inner())
        .clone();
    let running = late.as_ref().and_then(Tunnel::running_config);
    let wanted = WgConfig::resolve(data_root)?;
    let carries = wanted
        .as_ref()
        .map(|c| {
            appliance_pid1::net::interface_ipv4(&c.interface)
                .ok()
                .flatten()
                == Some(c.address)
        })
        .unwrap_or(false);
    match late_raise(running.as_ref(), wanted.as_ref(), carries) {
        LateRaise::NoConfig => return Ok(false),
        LateRaise::AlreadyUp => return Ok(true),
        LateRaise::Repoint { from, to } => {
            let tunnel = late.expect("Repoint is planned only for a running tunnel");
            let wanted = wanted.expect("Repoint is planned only for a configuration");
            tunnel.repoint(&wanted).with_context(|| {
                format!("re-pointing {} from [{from}] to [{to}]", wanted.interface)
            })?;
            tracing::info!(
                interface = %wanted.interface,
                from = %from,
                to = %to,
                "WireGuard RE-POINTED in this process: the configuration names another peer (the front's relay \
                 moved or was re-keyed — a front recharged onto new addresses); the old pump is stopped and \
                 the new one dials it now, without a restart"
            );
            if let Ok(rt) = tokio::runtime::Handle::try_current() {
                // A32-14, stays a task: a bounded watch (20 looks, 3 s apart) of one handshake, said at its end.
                rt.spawn(handshake_watch(
                    tunnel,
                    20,
                    std::time::Duration::from_secs(3),
                ));
            }
            return Ok(true);
        }
        LateRaise::Raise => {}
    }
    let config = wanted.expect("Raise is planned only for a configuration");
    let tunnel = establish(&config)?;
    if let Ok(rt) = tokio::runtime::Handle::try_current() {
        // A32-14, stays a task: a bounded watch (20 looks, 3 s apart) of one handshake, said at its end.
        rt.spawn(handshake_watch(
            tunnel.clone(),
            20,
            std::time::Duration::from_secs(3),
        ));
    }
    *LATE_TUNNEL.lock().unwrap_or_else(|p| p.into_inner()) = Some(tunnel);
    Ok(true)
}

/// The part that touches the machine: the tun device, the address, the pump.
fn open_pump(config: &WgConfig) -> Result<TunnelHandle> {
    // 1. The tun device. The ONE kernel feature this lane needs, and it is
    //    builtin on the appliance kernel — no module, no modprobe, no staged
    //    `.ko`. Held by the pump for the process lifetime: closing this fd is
    //    what deletes the interface.
    let tun = TunDevice::create(&config.interface, DEFAULT_MTU).with_context(|| {
        format!(
            "creating the tun device {}. This needs CAP_NET_ADMIN and /dev/net/tun \
             (CONFIG_TUN, builtin on the appliance kernel) — and nothing else: no wireguard \
             module, no wg(8)",
            config.interface
        )
    })?;

    // 2. The address and the link state, through the SAME writer every other
    //    address on this appliance goes through (one implementation). `gateway: None` — a
    //    tunnel is not a default route, and making it one would black-hole the
    //    machine the moment the peer went away.
    //
    //    Chosen over the ioctl path (`SIOCSIFADDR`/`SIOCSIFNETMASK`) tunnr's
    //    payload uses: netlink ACKNOWLEDGES every message, so "the address is
    //    on" is a fact read back from the kernel rather than a `send` that
    //    succeeded. gunnar already has that writer and every other address on
    //    the box goes through it.
    appliance_pid1::net::apply(&appliance_pid1::net::NetPlan {
        interface: config.interface.clone(),
        address: config.address,
        prefix: config.prefix,
        gateway: None,
    })
    .with_context(|| {
        format!(
            "putting {}/{} on {}",
            config.address, config.prefix, config.interface
        )
    })?;

    // 3. The data plane: boringtun, in this process, on one thread.
    // A32-14, stays a task: the userspace WireGuard pump, one thread; its handle is kept and stopped with the tunnel.
    let handle = dataplane::spawn(tun, &config.tunnel_spec()).with_context(|| {
        format!(
            "starting the userspace WireGuard pump on {}",
            config.interface
        )
    })?;
    Ok(handle)
}

fn establish(config: &WgConfig) -> Result<Tunnel> {
    let handle = open_pump(config)?;
    for (addr, prefix) in config.unrouted_allowed_ips() {
        tracing::warn!(
            allowed_ip = %format!("{addr}/{prefix}"),
            interface = %config.interface,
            tunnel_subnet = %format!("{}/{}", config.address, config.prefix),
            "an AllowedIPs entry falls outside this interface's own subnet, so the kernel has \
             no route to it. gunnar installs no routes beyond the address's connected one; \
             traffic to that prefix will not enter the tunnel"
        );
    }

    let tunnel = Tunnel {
        interface: config.interface.clone(),
        address: config.address,
        prefix: config.prefix,
        peers: config.peers.len(),
        public_key: config.public_key(),
        local_port: handle.local_port(),
        config: Arc::new(std::sync::Mutex::new(config.clone())),
        handle: Arc::new(std::sync::Mutex::new(Some(handle))),
    };
    tracing::info!(
        interface = %tunnel.interface,
        address = %tunnel.address,
        prefix = tunnel.prefix,
        public_key = base64_key(&tunnel.public_key),
        listen_port = tunnel.local_port,
        peers = tunnel.peers,
        mtu = DEFAULT_MTU,
        "WireGuard up in USERSPACE: boringtun compiled into this binary, no kernel module, no \
         wg(8), no wg-quick, no shell. This address is SEEDED state and does not move when the \
         DHCP lease does — but it is only reachable once a peer whose [Peer] stanza carries the \
         public key above completes a handshake"
    );
    // The listeners that follow the tunnel (the replication door) learn where it is.
    crate::tunnel_addr::publish(Some(tunnel.address));
    Ok(tunnel)
}

/// Watch for the first completed handshake and say so, once, on the console.
///
/// Spawned by `serve::run` beside the DHCP renewal task. It exists because
/// "the tunnel is configured" and "the tunnel WORKS" are different claims and
/// only a completed handshake settles the second: boringtun reports no
/// handshake time at all until a peer has actually answered, which is the same
/// fact `wg show latest-handshake` prints on the other end.
///
/// Gives up after `attempts` polls and says that too — an appliance that keeps
/// printing "still no handshake" forever is noise, and one that goes quiet
/// without a verdict is worse.
pub async fn handshake_watch(tunnel: Tunnel, attempts: usize, every: std::time::Duration) {
    for attempt in 1..=attempts {
        tokio::time::sleep(every).await;
        let peers = tunnel.peer_stats();
        if let Some(up) = peers.iter().find(|p| p.handshaken()) {
            tracing::info!(
                interface = %tunnel.interface,
                address = %tunnel.address,
                peer = %base64_key(&up.public_key),
                handshake_age_secs = up.last_handshake_ago.map(|d| d.as_secs()),
                rx_bytes = up.rx_bytes,
                tx_bytes = up.tx_bytes,
                endpoint = ?up.endpoint,
                "WireGuard HANDSHAKE COMPLETED: this appliance is reachable at {} \
                 over the tunnel, and that address does not change when the DHCP \
                 lease does",
                tunnel.address
            );
            return;
        }
        tracing::debug!(
            interface = %tunnel.interface,
            attempt,
            peers = peers.len(),
            "no WireGuard handshake yet"
        );
    }
    tracing::warn!(
        interface = %tunnel.interface,
        peers = tunnel.peers,
        "WireGuard is configured and NO peer has completed a handshake. The tunnel address {} \
         is on the interface but nothing is on the other end of it — check the peer's endpoint, \
         its AllowedIPs, and that UDP reaches this machine",
        tunnel.address
    );
}

/// A key as `wg(8)` prints it, for a log line a human can compare by eye.
pub(crate) fn base64_key(key: &[u8; KEY_LEN]) -> String {
    keys::encode_key(key)
}

/// The banner sentence, as a pure function of what it says.
///
/// Split out from [`Tunnel::banner_line`] so the ONE line an operator actually
/// reads off a shell-less console can be asserted without creating a network
/// device — a banner that only exists inside a live tunnel is a banner nobody
/// checks.
fn banner_line(
    interface: &str,
    address: Ipv4Addr,
    prefix: u8,
    public_key: &[u8; KEY_LEN],
    peers: usize,
    local_port: u16,
) -> String {
    format!(
        "wg    {address}/{prefix} on {interface}  key {}  ({peers} peer{}, udp {local_port})",
        base64_key(public_key),
        if peers == 1 { "" } else { "s" },
    )
}

#[cfg(test)]
mod tests {

    /// **A re-feed naming another front relay re-points the running tunnel**
    /// (stinkies recharge-front-new-key-deadlock part 2 / R3). MEASURED d-life
    /// lf10 R3 2026-10-01: after `estate recharge front` laid the front again
    /// on NEW addresses, main and the twin kept dialling the dead relay until
    /// they restarted ("the mesh did not carry within 120 s") — this process's
    /// tunnel answered "already up" to any configuration.
    #[test]
    fn a_refeed_naming_another_front_relay_repoints_the_running_tunnel() {
        let conf = |endpoint: &str, key: &str| {
            WgConfig::parse(
                "wg0",
                &format!(
                    "[Interface]\nPrivateKey = {}\nAddress = 10.44.0.2/24\n\n[Peer]\nPublicKey = {key}\nAllowedIPs = 10.44.0.0/24\nEndpoint = {endpoint}\nPersistentKeepalive = 25\n",
                    base64_key(&[7u8; KEY_LEN])
                ),
            )
            .unwrap()
        };
        let old = conf("127.86.33.51:51821", &base64_key(&[1u8; KEY_LEN]));
        let moved = conf("127.86.33.10:51821", &base64_key(&[1u8; KEY_LEN]));
        let rekeyed = conf("127.86.33.10:51821", &base64_key(&[2u8; KEY_LEN]));
        assert_eq!(
            late_raise(Some(&old), Some(&old), true),
            LateRaise::AlreadyUp,
            "the same front: nothing to do"
        );
        assert!(
            matches!(
                late_raise(Some(&old), Some(&moved), true),
                LateRaise::Repoint { .. }
            ),
            "the front moved: re-point, not 'already up'"
        );
        assert!(
            matches!(
                late_raise(Some(&old), Some(&rekeyed), true),
                LateRaise::Repoint { .. }
            ),
            "the relay's key changed: re-point"
        );
        assert_eq!(late_raise(None, Some(&old), false), LateRaise::Raise);
        assert_eq!(late_raise(None, Some(&old), true), LateRaise::AlreadyUp);
        assert_eq!(late_raise(None, None, false), LateRaise::NoConfig);
        assert_eq!(
            late_raise(Some(&old), None, true),
            LateRaise::AlreadyUp,
            "a configuration that went away does not take the running tunnel down"
        );
    }

    use super::*;

    /// A data root as a paired box leaves it: the seed, `wg0.key`, and the
    /// `wg0.conf` the pairing loop wrote for the door's address.
    fn paired_root() -> tempfile::TempDir {
        let dir = tempfile::tempdir().unwrap();
        let root = dir.path();
        std::fs::create_dir_all(root.join("pairing")).unwrap();
        std::fs::write(root.join(crate::pairing::SEED_REL), "# the medium's seed\n").unwrap();
        let private = crate::pairing::ensure_tunnel_key(root).unwrap();
        let conf = format!(
            "{}: test\n[Interface]\nPrivateKey = {private}\nAddress = 10.44.0.2/24\n\n[Peer]\nPublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=\nAllowedIPs = 10.44.0.0/24\nEndpoint = 95.111.211.189:51820\nPersistentKeepalive = 25\n",
            crate::pairing::CONF_MARK
        );
        std::fs::write(root.join(CONFIG_FILE), conf).unwrap();
        dir
    }

    /// Fix 2026-09-24: the appliance gate + the pairing's `wg0.conf` boots the
    /// CONFIG lane — never the ceremony, which refused "both lanes armed" and
    /// faulted PID 1 on every reboot of a paired box.
    #[test]
    fn a_paired_data_root_boots_the_config_lane_and_never_the_ceremony() {
        let paired = paired_root();
        assert_eq!(boot_lane(true, paired.path()), BootLane::Config);
        // The pairing's conf alone (no seed) is enough; so is the seed alone
        // (before the first join: nothing to raise yet, the loop raises it).
        std::fs::remove_file(paired.path().join(crate::pairing::SEED_REL)).unwrap();
        assert_eq!(boot_lane(true, paired.path()), BootLane::Config);
        let seed_only = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(seed_only.path().join("pairing")).unwrap();
        std::fs::write(seed_only.path().join(crate::pairing::SEED_REL), "x").unwrap();
        assert_eq!(boot_lane(true, seed_only.path()), BootLane::Config);
        // Unchanged: no pairing ⇒ the gate means the ceremony, and an
        // OPERATOR's wg0.conf beside the gate is still the refused conflict.
        let bare = tempfile::tempdir().unwrap();
        assert_eq!(boot_lane(true, bare.path()), BootLane::Ceremony);
        assert_eq!(boot_lane(false, bare.path()), BootLane::Config);
        std::fs::write(bare.path().join(CONFIG_FILE), "[Interface]\n").unwrap();
        assert_eq!(boot_lane(true, bare.path()), BootLane::Ceremony);
    }

    /// The real boot entry, gate ON, over a paired root: it does not refuse
    /// (the old fault), and the ceremony never minted its identity.
    #[test]
    fn booting_a_paired_root_with_the_gate_on_does_not_refuse_and_mints_no_ceremony_identity() {
        if rustix::process::geteuid().is_root() {
            return; // would create a real wg0 on the test host
        }
        let paired = paired_root();
        let got = bring_up_with(true, paired.path());
        assert!(got.is_ok(), "a paired box must boot: {:?}", got.err());
        assert!(
            !crate::appliance::wg_dir(paired.path()).exists(),
            "the ceremony must stay out"
        );
    }

    /// `tunnel show` on a paired box reports the pairing loop's tunnel, and an
    /// address handed over beside the door's is refused by name.
    #[test]
    fn tunnel_show_on_a_paired_box_reports_the_pairing_tunnel() {
        let paired = paired_root();
        let root = paired.path();
        let private = crate::pairing::ensure_tunnel_key(root).unwrap();
        let want = wg_appliance_core::public_from_private(&private).unwrap();
        let shown = crate::appliance::tunnel_address_answer(root, "", false).expect("show answers");
        assert_eq!(shown.wg_public_key, want);
        assert_eq!(shown.address, "10.44.0.2/24");
        assert!(!shown.changed);
        let refused = crate::appliance::tunnel_address_answer(root, "10.44.0.9/24", true)
            .expect_err("refused");
        assert!(
            format!("{refused:?}").contains("tunnel-owned-by-pairing"),
            "{refused:?}"
        );
    }

    /// **The two lanes may not both be armed** — and the refusal names which
    /// seeded source it found, plus the ceremony directory it would otherwise
    /// have minted into.
    ///
    /// The field that loses a precedence rule here is the machine's IDENTITY,
    /// so a silent winner means the operator registers one key with the hub and
    /// the appliance presents another, on a console that looks healthy.
    ///
    /// **RED-when-broken**: the "neither present" case is asserted too, because
    /// a guard that refuses unconditionally would break every ordinary
    /// gate-on boot and a guard that never refuses is the defect itself.
    #[test]
    fn arming_both_lanes_is_refused_and_the_refusal_names_the_source() {
        let root = Path::new("/srv/gunnar-data");

        assert_eq!(
            seeded_lane_conflict(root, false, false),
            None,
            "the ordinary ceremony boot — no seeded source — must NOT refuse"
        );

        let by_file = seeded_lane_conflict(root, true, false).expect("wg0.conf conflicts");
        assert!(
            by_file.contains("/srv/gunnar-data/wg0.conf"),
            "name the file the operator has to remove: {by_file}"
        );
        assert!(
            by_file.contains("/srv/gunnar-data/wg"),
            "and the ceremony directory it would have minted into: {by_file}"
        );
        assert!(
            by_file.contains(crate::appliance::GATE_ENV),
            "and the gate to unset instead: {by_file}"
        );

        let by_param = seeded_lane_conflict(root, false, true).expect("the boot param conflicts");
        assert!(
            by_param.contains(BOOT_PARAM),
            "name the boot parameter, not the absent file: {by_param}"
        );
        assert!(
            !by_param.contains("wg0.conf"),
            "a file that is not there must not be blamed: {by_param}"
        );

        // Both at once still names something actionable rather than panicking
        // or producing an empty complaint.
        let both = seeded_lane_conflict(root, true, true).expect("both conflict");
        assert!(both.contains(BOOT_PARAM), "{both}");
    }

    /// A whole, ordinary config — the one in the guide.
    const GOOD: &str = "\
[Interface]
PrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=
Address = 10.99.0.2/24
ListenPort = 51820

# the box on the other end
[Peer]
PublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=
AllowedIPs = 10.99.0.1/32, 10.99.0.0/24
Endpoint = 10.0.2.2:51820
PersistentKeepalive = 25
";

    #[test]
    fn a_full_config_parses_into_every_field() {
        let cfg = WgConfig::parse("wg0", GOOD).expect("a legal config");
        assert_eq!(cfg.interface, "wg0");
        assert_eq!(cfg.address, Ipv4Addr::new(10, 99, 0, 2));
        assert_eq!(cfg.prefix, 24);
        assert_eq!(cfg.listen_port, Some(51820));
        assert_eq!(cfg.peers.len(), 1);
        let peer = &cfg.peers[0];
        assert_eq!(
            peer.allowed_ips,
            vec![
                (Ipv4Addr::new(10, 99, 0, 1), 32),
                (Ipv4Addr::new(10, 99, 0, 0), 24),
            ]
        );
        assert_eq!(peer.endpoint, Some("10.0.2.2:51820".parse().unwrap()));
        assert_eq!(peer.persistent_keepalive, Some(25));
        assert_eq!(peer.preshared_key, None);
        // Both keys really are 32 bytes, not "44 characters that looked right".
        assert_eq!(cfg.private_key.len(), KEY_LEN);
        assert_ne!(cfg.private_key, [0; KEY_LEN]);
    }

    /// Measure, never assert on the parser: it must REFUSE, or "parsed" means nothing.
    #[test]
    fn malformed_configs_are_refused_rather_than_defaulted() {
        for (why, bad) in [
            ("no PrivateKey", "[Interface]\nAddress = 10.0.0.1/24\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\n"),
            ("no Address", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\n"),
            ("no peer at all", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\nAddress = 10.0.0.1/24\n"),
            ("a peer with no PublicKey", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\nAddress = 10.0.0.1/24\n[Peer]\nAllowedIPs = 10.0.0.2/32\n"),
            ("a peer with no AllowedIPs", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\nAddress = 10.0.0.1/24\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\n"),
            ("a key that is not 32 bytes", "[Interface]\nPrivateKey = c2hvcnQ=\nAddress = 10.0.0.1/24\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\n"),
            ("a key that is not base64", "[Interface]\nPrivateKey = ****\nAddress = 10.0.0.1/24\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\n"),
            ("a /33 address", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\nAddress = 10.0.0.1/33\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\n"),
            ("a hostname endpoint", "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\nAddress = 10.0.0.1/24\n[Peer]\nPublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\nAllowedIPs = 10.0.0.2/32\nEndpoint = peer.example.com:51820\n"),
            ("a key before any section", "PrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\n"),
            ("a line that is not Key = value", "[Interface]\nPrivateKey\n"),
            ("an unknown section", "[Tunnel]\nPrivateKey = x\n"),
            ("an unknown key", "[Interface]\nWibble = 3\n"),
        ] {
            assert!(
                WgConfig::parse("wg0", bad).is_err(),
                "WgConfig::parse accepted a config with {why}; a parser that accepts everything \
                 configures nothing and reports success"
            );
        }
    }

    /// A wg-quick key gunnar does not implement must be REFUSED, not skipped.
    ///
    /// This is the difference between an operator whose `PostUp` never ran and
    /// an operator who was told it never ran.
    #[test]
    fn wg_quick_keys_gunnar_does_not_implement_are_refused_by_name() {
        for key in [
            "DNS = 1.1.1.1",
            "MTU = 1380",
            "Table = off",
            "PostUp = /bin/true",
        ] {
            let text = format!(
                "[Interface]\nPrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\n\
                 Address = 10.0.0.1/24\n{key}\n[Peer]\n\
                 PublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\n\
                 AllowedIPs = 10.0.0.2/32\n"
            );
            let err = WgConfig::parse("wg0", &text).expect_err("must be refused");
            let msg = format!("{err:#}");
            assert!(
                msg.contains("does NOT implement"),
                "{key} was refused for the wrong reason: {msg}"
            );
        }
    }

    /// The shared tokenizer must not have changed what a comment is, what a
    /// trailing `=` means, or which line a message names. Three rules, on one
    /// config that exercises all of them.
    #[test]
    fn comments_padding_and_line_numbers_survive_the_shared_tokenizer() {
        let cfg = WgConfig::parse(
            "wg0",
            "# a leading comment\n\
             [Interface]   ; and a trailing one\n\
             PrivateKey = gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\n\
             Address = 10.0.0.1/24\n\
             \n\
             [Peer]\n\
             PublicKey = aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=\n\
             AllowedIPs = 10.0.0.2/32\n\
             PersistentKeepalive = 25  # keep the NAT open\n",
        )
        .expect("comments and blank lines are not errors");
        assert_eq!(
            cfg.private_key,
            parse_key("gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=").unwrap(),
            "a base64 key's trailing `=` padding must survive the key/value split"
        );
        assert_eq!(cfg.peers[0].persistent_keepalive, Some(25));

        // And the line number in an error names the offending line, not the
        // count of lines the tokenizer chose to emit.
        let err = WgConfig::parse("wg0", "# one\n\n[Interface]\nWibble = 3\n").unwrap_err();
        assert!(format!("{err:#}").contains("wg0.conf:4:"), "{err:#}");
    }

    #[test]
    fn allowed_ips_outside_the_tunnel_subnet_are_reported_as_unrouted() {
        let cfg = WgConfig::parse("wg0", GOOD).expect("a legal config");
        // 10.99.0.1/32 and 10.99.0.0/24 are both inside 10.99.0.0/24.
        assert!(
            cfg.unrouted_allowed_ips().is_empty(),
            "the ordinary case must be silent, or the warning is noise: {:?}",
            cfg.unrouted_allowed_ips()
        );
        // A prefix somewhere else entirely gets no route from the address.
        let wide = GOOD.replace(
            "AllowedIPs = 10.99.0.1/32, 10.99.0.0/24",
            "AllowedIPs = 192.168.7.0/24",
        );
        let cfg = WgConfig::parse("wg0", &wide).expect("a legal config");
        assert_eq!(
            cfg.unrouted_allowed_ips(),
            vec![(Ipv4Addr::new(192, 168, 7, 0), 24)]
        );
    }

    // ── the boot-parameter form ─────────────────────────────────────────────

    const HUB_PUB: &str = "aD8IQp7Jf8KgM5RqJUP1NaPJu6SQ1EUkPEqk118XIRo=";

    #[test]
    fn the_boot_param_carries_the_whole_non_secret_half() {
        let spec = format!(
            "addr=10.99.0.2/24,peer={HUB_PUB},endpoint=10.0.2.2:51820,\
             allowed=10.99.0.0/24,keepalive=25"
        );
        let cfg = WgConfig::parse_boot_param("wg0", &spec, [0x11; KEY_LEN]).expect("a legal spec");
        assert_eq!(cfg.address, Ipv4Addr::new(10, 99, 0, 2));
        assert_eq!(cfg.prefix, 24);
        assert_eq!(
            cfg.private_key, [0x11; KEY_LEN],
            "the key comes from the FILE, not the spec"
        );
        assert_eq!(
            cfg.listen_port, None,
            "a dial-out appliance needs no fixed port"
        );
        assert_eq!(cfg.peers.len(), 1);
        assert_eq!(cfg.peers[0].public_key, parse_key(HUB_PUB).unwrap());
        assert_eq!(
            cfg.peers[0].endpoint,
            Some("10.0.2.2:51820".parse().unwrap())
        );
        assert_eq!(cfg.peers[0].persistent_keepalive, Some(25));
        assert_eq!(
            cfg.peers[0].allowed_ips,
            vec![(Ipv4Addr::new(10, 99, 0, 0), 24)]
        );
    }

    #[test]
    fn the_boot_param_needs_only_addr_peer_and_allowed() {
        let spec = format!("addr=10.99.0.2/24,peer={HUB_PUB},allowed=10.99.0.0/24");
        let cfg = WgConfig::parse_boot_param("wg0", &spec, [0x11; KEY_LEN]).expect("a legal spec");
        assert_eq!(
            cfg.peers[0].endpoint, None,
            "a hub that dials US needs no endpoint here"
        );
        assert_eq!(cfg.peers[0].persistent_keepalive, None);
    }

    /// Measure, never assert, and the security-relevant half: the boot parameter must REFUSE a
    /// private key rather than quietly accept one, because `/proc/cmdline` is
    /// world readable and an accepted `key=` would publish it.
    #[test]
    fn the_boot_param_refuses_garbage_and_refuses_to_carry_a_private_key() {
        let good = format!("addr=10.99.0.2/24,peer={HUB_PUB},allowed=10.99.0.0/24");
        for (why, bad) in [
            ("no addr", format!("peer={HUB_PUB},allowed=10.99.0.0/24")),
            (
                "no peer",
                "addr=10.99.0.2/24,allowed=10.99.0.0/24".to_string(),
            ),
            ("no allowed", format!("addr=10.99.0.2/24,peer={HUB_PUB}")),
            ("a field that is not key=value", format!("{good},nonsense")),
            (
                "a bad endpoint",
                format!("{good},endpoint=hub.example.com:51820"),
            ),
            (
                "a bad prefix",
                format!("addr=10.99.0.2/33,peer={HUB_PUB},allowed=10.0.0.0/8"),
            ),
            (
                "a peer key that is not 32 bytes",
                "addr=10.99.0.2/24,peer=c2hvcnQ=,allowed=10.0.0.0/8".to_string(),
            ),
        ] {
            assert!(
                WgConfig::parse_boot_param("wg0", &bad, [0x11; KEY_LEN]).is_err(),
                "the boot parameter accepted a spec with {why}"
            );
        }
        // The one that matters most: a private key offered on the command line
        // must be refused BY NAME, not ignored.
        for secret in ["privatekey", "private", "key", "PrivateKey"] {
            let bad = format!("{good},{secret}=gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=");
            let err = WgConfig::parse_boot_param("wg0", &bad, [0x11; KEY_LEN])
                .expect_err("a private key on the cmdline must be refused");
            assert!(
                format!("{err:#}").contains("world-readable"),
                "the refusal must say WHY: {err:#}"
            );
        }
    }

    /// Two sources that disagree is the failure a precedence rule hides.
    ///
    /// The boot parameter is HANDED IN. It used to be `set_var`'d and removed
    /// again around the call, which made this test and the one below two
    /// writers of one process-wide variable: on a threaded `cargo test` either
    /// could clear `GUNNAR_WG` while the other was inside `resolve`, and the
    /// refusal it exists to prove would simply not happen. That is the flake,
    /// and `resolve_with` is why it cannot come back.
    #[test]
    fn a_file_and_a_boot_param_together_are_refused_rather_than_ranked() {
        let dir = tempfile::tempdir().expect("a scratch directory");
        let dir = dir.path();
        std::fs::write(dir.join(CONFIG_FILE), GOOD).expect("write a config");
        let spec = format!("addr=10.99.0.2/24,peer={HUB_PUB},allowed=10.99.0.0/24");
        let err = WgConfig::resolve_with(dir, Some(&spec))
            .expect_err("two configurations must not be ranked");
        assert!(format!("{err:#}").contains("refuses to guess"), "{err:#}");
    }

    /// The boot-param path without a seeded key must name the file it wants —
    /// this is the whole stable-identity story, and a vague error here is an
    /// operator minting a new identity by accident.
    #[test]
    fn the_boot_param_without_a_seeded_key_says_which_file_is_missing() {
        let dir = tempfile::tempdir().expect("a scratch directory");
        let dir = dir.path();
        let spec = format!("addr=10.99.0.2/24,peer={HUB_PUB},allowed=10.99.0.0/24");
        let err =
            WgConfig::resolve_with(dir, Some(&spec)).expect_err("no key file means no tunnel");
        assert!(
            format!("{err:#}").contains(KEY_FILE),
            "the error must name the file: {err:#}"
        );
        // And with the key seeded it resolves.
        std::fs::write(
            dir.join(KEY_FILE),
            "gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=\n",
        )
        .expect("seed the key");
        let ok = WgConfig::resolve_with(dir, Some(&spec))
            .expect("a seeded key completes the boot-param form")
            .expect("a configuration");
        assert_eq!(
            ok.private_key,
            parse_key("gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=").unwrap()
        );
        assert_eq!(ok.address, Ipv4Addr::new(10, 99, 0, 2));
    }

    /// **Both other arms of the resolver**, so the two refusals above are not
    /// the only thing this function is seen to do: neither source is
    /// `Ok(None)` (the shipped state), and the file alone resolves.
    ///
    /// Without this a `resolve_with` that returned an error for everything
    /// would pass the two tests above.
    #[test]
    fn neither_source_is_none_and_the_file_alone_resolves() {
        let dir = tempfile::tempdir().expect("a scratch directory");
        let dir = dir.path();
        assert!(
            WgConfig::resolve_with(dir, None)
                .expect("no sources is not a failure")
                .is_none(),
            "an unconfigured data root must mean no tunnel, not a broken boot"
        );
        std::fs::write(dir.join(CONFIG_FILE), GOOD).expect("write a config");
        let cfg = WgConfig::resolve_with(dir, None)
            .expect("a legal config file")
            .expect("present");
        assert_eq!(cfg.interface, "wg0");
        assert_eq!(cfg.address, Ipv4Addr::new(10, 99, 0, 2));
    }

    #[test]
    fn a_missing_config_file_is_none_and_not_an_error() {
        let dir = std::env::temp_dir().join(format!("gunnar-wg-none-{}", std::process::id()));
        std::fs::create_dir_all(&dir).expect("a scratch directory");
        assert!(
            WgConfig::from_data_root(&dir)
                .expect("absence is not a failure")
                .is_none(),
            "no wg0.conf must mean no tunnel, not a broken boot"
        );
        std::fs::write(dir.join(CONFIG_FILE), GOOD).expect("write a config");
        let cfg = WgConfig::from_data_root(&dir)
            .expect("a legal config")
            .expect("present");
        assert_eq!(
            cfg.interface, "wg0",
            "the interface name comes from the file's stem"
        );
        std::fs::remove_dir_all(&dir).ok();
    }

    // ── the identity ────────────────────────────────────────────────────────

    /// **The public key in the banner is the key `wg pubkey` prints.**
    ///
    /// This is the assertion the deleted kernel lane could only make by asking
    /// a live kernel to derive the key, in a root-only `#[ignore]` probe. The
    /// curve is linked in now, so it is an ordinary unit test that runs on every
    /// `cargo test` — and `R/1Rs…` is what the `wg` tool prints for `gEi6…`,
    /// recorded from the tool rather than computed here.
    #[test]
    fn the_banner_prints_the_public_key_wg_pubkey_derives() {
        const ITS_PUBLIC: &str = "R/1RsV4yjvIeEUApgRu3u584R5/WCKgKaPqHieam4TU=";
        let cfg = WgConfig::parse("wg0", GOOD).expect("a legal config");
        assert_eq!(
            base64_key(&cfg.public_key()),
            ITS_PUBLIC,
            "the identity gunnar prints is not the one wg derives from the same private key"
        );
        // A DIFFERENT private key must derive a different identity — the
        // control that keeps the equality above from passing on a constant.
        let other = GOOD.replace(
            "gEi6mvh6b5rKDg7UkleGpsJLlA9ueOKyWhjqJqFjWHg=",
            "cGVlcktleVRoYXRJc0RpZmZlcmVudEZyb21UaGVPbjA=",
        );
        let other = WgConfig::parse("wg0", &other).expect("a legal config");
        assert_ne!(other.public_key(), cfg.public_key());
    }

    /// The console line an operator compares against their hub, asserted for
    /// every field it is supposed to carry. Two peer counts, because the plural
    /// is the sort of thing that only ever reads right in the singular case the
    /// author tested.
    #[test]
    fn the_banner_line_carries_address_key_port_and_peer_count() {
        let cfg = WgConfig::parse("wg0", GOOD).expect("a legal config");
        let line = banner_line("wg0", cfg.address, cfg.prefix, &cfg.public_key(), 1, 51820);
        assert!(line.contains("10.99.0.2/24"), "{line}");
        assert!(line.contains("on wg0"), "{line}");
        assert!(
            line.contains("R/1RsV4yjvIeEUApgRu3u584R5/WCKgKaPqHieam4TU="),
            "the banner must print the identity a hub allowlists: {line}"
        );
        assert!(line.contains("(1 peer,"), "singular: {line}");
        assert!(line.contains("udp 51820"), "{line}");
        let two = banner_line("wg0", cfg.address, cfg.prefix, &cfg.public_key(), 2, 40000);
        assert!(two.contains("(2 peers,"), "plural: {two}");
    }

    #[test]
    fn base64_round_trips_the_way_wg_prints_it() {
        let key = [0x22u8; KEY_LEN];
        let text = base64_key(&key);
        assert_eq!(
            text.len(),
            44,
            "a 32-byte key is 44 padded base64 characters"
        );
        assert_eq!(parse_key(&text).expect("its own output"), key);
    }

    /// The spec handed to the shared data plane must carry every field the
    /// config declared. A translation that dropped `AllowedIPs` would produce a
    /// tunnel that comes up and routes nothing, with no error anywhere.
    #[test]
    fn the_data_plane_spec_carries_every_configured_field() {
        let cfg = WgConfig::parse("wg0", GOOD).expect("a legal config");
        let spec = cfg.tunnel_spec();
        assert_eq!(spec.private_key, cfg.private_key);
        assert_eq!(spec.listen_port, Some(51820));
        assert_eq!(spec.peers.len(), 1);
        assert_eq!(spec.peers[0].public_key, cfg.peers[0].public_key);
        assert_eq!(
            spec.peers[0].endpoint,
            Some("10.0.2.2:51820".parse().unwrap())
        );
        assert_eq!(spec.peers[0].persistent_keepalive, Some(25));
        assert_eq!(
            spec.peers[0].allowed_ips,
            vec![
                (Ipv4Addr::new(10, 99, 0, 1), 32),
                (Ipv4Addr::new(10, 99, 0, 0), 24)
            ],
            "every AllowedIPs entry must reach the router, or the tunnel carries nothing"
        );
        // And the router built from it really claims the tunnel subnet.
        let router = dataplane::Router::new(&spec.peers);
        assert_eq!(router.select("10.99.0.1".parse().unwrap()), Some(0));
        assert_eq!(router.select("10.99.0.77".parse().unwrap()), Some(0));
        assert_eq!(
            router.select("8.8.8.8".parse().unwrap()),
            None,
            "a config with no default route must not silently acquire one"
        );
    }
}