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

nordisk/edda

crates/appliance-pid1/src/net.rs

//! **The network, because nothing else brings the link up** (feature `net`).
//!
//! Moved AS IS from gunnar-server's `pid1.rs` (gunnar `gunnar-live` 18a44e3,
//! 2026-09-27), where it was written, measured and proven on the appliance and
//! on UpCloud; holger's PID 1 needs the same, and one writer of addresses per
//! suite is the rule. What was gunnar's by name is now the caller's: the
//! environment variable ([`NetConfig::from_env`]), the host-name prefix, the
//! name the resolver is proven on and where the "network up" line goes
//! ([`Bringup`]).
//!
//! Measured on the reference desktop, 2026-08-03: `CONFIG_IP_PNP is not set` in
//! `/boot/config-7.0.0-28-generic`, and the same in `-22`. The comfortable plan
//! — hand the kernel `ip=10.0.2.15::10.0.2.2:255.255.255.0::eth0:off` and let
//! it configure the interface before `init` runs — **does not work on this
//! kernel**, and it would have been a plan that failed only once there was an
//! ISO to boot. So the appliance configures its own interface, over
//! `AF_NETLINK`, in process:
//!
//! ```text
//!   RTM_NEWLINK  lo    IFF_UP                       (the kernel then adds 127.0.0.1/8 itself)
//!   RTM_NEWLINK  eth0  IFF_UP
//!   RTM_NEWADDR  eth0  10.0.2.15/24
//!   RTM_NEWROUTE default via 10.0.2.2 dev eth0
//! ```
//!
//! No `ip`, no `ifconfig`, no `busybox`: those are subprocesses, and the no-OS
//! rule bans them. `rustix` gives the socket calls without `unsafe` and
//! without a libc: on `x86_64-linux` it is the `linux_raw` backend.
//!
//! ## Why the message builders are pure functions
//!
//! [`link_up_message`], [`address_message`] and [`default_route_message`]
//! return `Vec<u8>` and touch no socket. A netlink bug is a **byte-layout**
//! bug: a `u16` where the kernel wants a `u8`, a `rtattr` that forgot its
//! four-byte padding. The only honest assertion is against the bytes.
//!
//! # What is configured, and from where
//!
//! A net spec, from the kernel command line through the environment: Linux
//! passes every unrecognised `key=value` boot parameter to `init` as an
//! environment variable (`init/main.c`, `unknown_bootoption`). Two forms per
//! entry, comma-separated, the FIRST owning the default route:
//!
//! * `eth0:10.0.2.15/24:10.0.2.2` — static, applied directly over netlink;
//! * `eth0:dhcp` — the [`crate::dhcp`] client runs the DISCOVER→ACK handshake
//!   and the lease is applied through the **same** [`apply`] the static form
//!   uses. One writer puts addresses on interfaces, whichever way the address
//!   was learned.
//!
//! `eth0:dhcp,eth1:dhcp` is the cloud shape: the public NIC and the utility one.

use std::net::Ipv4Addr;

use anyhow::{bail, Context, Result};

// ── netlink wire constants (uapi/linux/{netlink,rtnetlink,if_addr}.h) ────────
//
// Written out rather than pulled from a binding crate: there are eighteen of
// them, they are frozen ABI, and a binding crate would be a dependency in the
// appliance graph for eighteen integers.

pub(crate) const NLMSG_ERROR: u16 = 2;
pub(crate) const NLM_F_REQUEST: u16 = 0x001;
pub(crate) const NLM_F_ACK: u16 = 0x004;
/// `NLM_F_DUMP` = `NLM_F_ROOT | NLM_F_MATCH` — ask for the whole table rather
/// than one row. The only request in this file that wants many answers.
const NLM_F_DUMP: u16 = 0x100 | 0x200;
const NLM_F_EXCL: u16 = 0x200;
const NLM_F_REPLACE: u16 = 0x100;
const NLM_F_CREATE: u16 = 0x400;

const RTM_NEWLINK: u16 = 16;
const RTM_GETLINK: u16 = 18;
const RTM_NEWADDR: u16 = 20;
const RTM_GETADDR: u16 = 22;
const RTM_NEWROUTE: u16 = 24;

/// `IFLA_ADDRESS`, the link-layer address attribute on an `RTM_NEWLINK`.
const IFLA_ADDRESS: u16 = 1;

const AF_INET: u8 = 2;
const IFF_UP: u32 = 1;

const IFA_ADDRESS: u16 = 1;
const IFA_LOCAL: u16 = 2;
const IFA_BROADCAST: u16 = 4;

const RTA_DST: u16 = 1;
const RTA_OIF: u16 = 4;
const RTA_GATEWAY: u16 = 5;

const RT_TABLE_MAIN: u8 = 254;
const RTPROT_BOOT: u8 = 3;
const RT_SCOPE_UNIVERSE: u8 = 0;
/// `RT_SCOPE_LINK` — the destination is reachable on this link with no next
/// hop. What an option-121 route with a `0.0.0.0` gateway means.
const RT_SCOPE_LINK: u8 = 253;
const RTN_UNICAST: u8 = 1;

/// `struct nlmsghdr`, 16 bytes; the four `rtnetlink` payloads below are sized
/// against it in the golden tests.
const NLMSGHDR_LEN: usize = 16;
/// Where an `NLMSG_ERROR`'s errno sits: right after the 16-byte header.
const NLMSG_ERROR_ERRNO_AT: usize = NLMSGHDR_LEN;
/// `NLMSG_DONE` — the last message of a dump. A reader that stops at the end of
/// a *datagram* instead of at this type reads whatever the first `recv` happened
/// to hold, which on a box with many interfaces is a subset that looks complete.
const NLMSG_DONE: u16 = 3;

// ── is this actually PID 1 ──────────────────────────────────────────────────

/// True when this process is the init of its PID namespace.
///
/// Used only to decide how *loud* to be. The signal handling and the reaper are
/// installed either way: they are correct for an ordinary process too, and a
/// behaviour that only exists at pid 1 is a behaviour no test on a developer
/// machine ever exercises.
pub fn is_init() -> bool {
    std::process::id() == 1
}

/// The appliance gate: true when this kernel was booted by korp-start.
///
/// `korp.entry=` in `/proc/cmdline` is korp-start's boot contract — only the
/// appliance's own kernel carries it, while a container reads the HOST
/// kernel's cmdline, which does not. The gate for behaviour that must exist
/// only on the appliance (the DHCP T1 test seam, `crate::runtime`).
pub fn booted_by_korp_start() -> bool {
    std::fs::read_to_string("/proc/cmdline")
        .map(|cmdline| cmdline.contains("korp.entry="))
        .unwrap_or(false)
}

// ── 3. the network ──────────────────────────────────────────────────────────

/// One interface, one address, one optional default route.
///
/// Deliberately not a general network model. An appliance has one link and one
/// route; anything richer is a configuration language, and a configuration
/// language is a parser, a file and a place for a mistake to hide.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetPlan {
    /// The interface name, e.g. `eth0`.
    pub interface: String,
    /// The address to assign.
    pub address: Ipv4Addr,
    /// Its prefix length, 0..=32.
    pub prefix: u8,
    /// The default gateway, if there is to be one.
    pub gateway: Option<Ipv4Addr>,
}

impl NetPlan {
    /// Parse `iface:addr/prefix[:gateway]`, e.g. `eth0:10.0.2.15/24:10.0.2.2`.
    ///
    /// Colon-separated and not JSON or TOML because this value's only transport
    /// is a kernel command line, where a space ends the parameter.
    pub fn parse(spec: &str) -> Result<NetPlan> {
        let mut parts = spec.split(':');
        let interface = parts.next().unwrap_or_default().trim().to_string();
        if interface.is_empty() {
            bail!("net spec {spec:?}: no interface name before the first `:`");
        }
        let cidr = parts
            .next()
            .with_context(|| format!("net spec {spec:?}: expected <iface>:<addr>/<prefix>"))?;
        let (addr, prefix) = cidr
            .split_once('/')
            .with_context(|| format!("net spec {spec:?}: {cidr:?} is not <addr>/<prefix>"))?;
        let address: Ipv4Addr = addr
            .parse()
            .with_context(|| format!("net spec {spec:?}: {addr:?} is not an IPv4 address"))?;
        let prefix: u8 = prefix
            .parse()
            .with_context(|| format!("net spec {spec:?}: {prefix:?} is not a prefix length"))?;
        if prefix > 32 {
            bail!("net spec {spec:?}: /{prefix} is not a legal IPv4 prefix length");
        }
        let gateway = match parts.next().map(str::trim).filter(|g| !g.is_empty()) {
            Some(g) => Some(g.parse::<Ipv4Addr>().with_context(|| {
                format!("net spec {spec:?}: {g:?} is not an IPv4 gateway address")
            })?),
            None => None,
        };
        if parts.next().is_some() {
            bail!("net spec {spec:?}: more than three `:`-separated fields");
        }
        Ok(NetPlan {
            interface,
            address,
            prefix,
            gateway,
        })
    }
}

/// What the net spec asks for: a fixed address, or a DHCP-learned one.
///
/// Two variants and no third, mirroring the two things an appliance can
/// honestly be told at boot: "be this address" or "ask the network". Everything
/// downstream of the *answer* is shared — both variants end in [`apply`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NetConfig {
    /// `iface:addr/prefix[:gateway]`, applied verbatim.
    Static(NetPlan),
    /// `iface:dhcp` — run the `crate::dhcp` handshake on this interface.
    ///
    /// Behind `dhcp`: with the feature off there is no handshake to
    /// run, so there is no state for this variant to hold. The `:dhcp`
    /// SPELLING is still recognised by [`NetConfig::parse`] — and refused by
    /// name there.
    #[cfg(feature = "dhcp")]
    Dhcp {
        /// The interface to configure, e.g. `eth0`.
        interface: String,
    },
}

impl NetConfig {
    /// **Parse the whole of the net spec: a comma-separated list of specs.**
    ///
    /// One spec is the historical spelling and parses to a one-element list,
    /// byte-for-byte what it always did. Two are the cloud shape:
    /// `eth0:dhcp,eth1:dhcp` is a public NIC and a private one, which is what
    /// every UpCloud server with a utility network has and what the appliance
    /// could not previously configure — measured 2026-08-31, the box holds
    /// UpCloud's `10.20.6.152` on interface index 2 and nothing on the box had
    /// ever brought it up, so the address did not answer a ping from the front
    /// on the same /22.
    ///
    /// **Order is load-bearing.** The FIRST spec owns the default route; the
    /// rest are address-only. A second default route through a utility network
    /// with no way out is not a richer configuration, it is a coin flip over
    /// which one the kernel picks for egress.
    pub fn parse_list(spec: &str) -> Result<Vec<NetConfig>> {
        let mut out = Vec::new();
        let mut seen: Vec<String> = Vec::new();
        for part in spec.split(',') {
            let part = part.trim();
            if part.is_empty() {
                bail!(
                    "net spec {spec:?}: an empty entry between commas. Every entry must be                      <iface>:dhcp or <iface>:<addr>/<prefix>[:<gateway>]."
                );
            }
            let config = NetConfig::parse(part)?;
            let name = config.interface().to_owned();
            if seen.contains(&name) {
                bail!(
                    "net spec {spec:?}: {name:?} appears twice. Two plans for one interface                      is two answers to one question; the second would silently win or the                      kernel would refuse the duplicate address."
                );
            }
            seen.push(name);
            out.push(config);
        }
        if out.is_empty() {
            bail!("net spec {spec:?}: no interface named");
        }
        Ok(out)
    }

    /// The interface this plan configures, whichever variant it is.
    #[must_use]
    pub fn interface(&self) -> &str {
        match self {
            NetConfig::Static(plan) => &plan.interface,
            #[cfg(feature = "dhcp")]
            NetConfig::Dhcp { interface } => interface,
        }
    }

    /// Parse the net spec: `iface:dhcp` (case-insensitive on the keyword) or
    /// the static form, byte-compatible with what every existing declaration
    /// says. Anything else is refused, never defaulted.
    pub fn parse(spec: &str) -> Result<NetConfig> {
        if let Some((interface, rest)) = spec.split_once(':') {
            let interface = interface.trim();
            if rest.trim().eq_ignore_ascii_case("dhcp") {
                if interface.is_empty() {
                    bail!("net spec {spec:?}: no interface name before `:dhcp`");
                }
                #[cfg(feature = "dhcp")]
                return Ok(NetConfig::Dhcp {
                    interface: interface.to_string(),
                });
                // **The honest refusal.** The keyword is still RECOGNISED with
                // `dhcp` off — deliberately, because the alternative is
                // worse in both available directions: falling through to
                // `NetPlan::parse` would report "`\"dhcp\"` is not
                // <addr>/<prefix>", which blames the operator's spelling for a
                // decision the BUILD made; and treating it as "no network"
                // would boot an appliance dark, unreachable, with nothing in
                // the log naming the cause. So it names the missing feature and
                // stops the boot — the same grade every other lying declaration
                // gets here.
                #[cfg(not(feature = "dhcp"))]
                bail!(
                    "net spec {spec:?} asks for DHCP on {interface:?}, but this binary was \
                     built WITHOUT the `dhcp` feature — it contains no DHCP client and \
                     cannot obtain a lease. Give a STATIC address instead \
                     (<NET>={interface}:<addr>/<prefix>[:<gateway>]), or use a build with \
                     --features dhcp."
                );
            }
        }
        Ok(NetConfig::Static(NetPlan::parse(spec)?))
    }

    /// The configuration named by the environment variable `var` (gunnar's
    /// `GUNNAR_NET`, holger's `HOLGER_NET` — the product names its own), if
    /// the environment names one. Linux hands every unrecognised `key=value`
    /// boot parameter to `init` as an environment variable, so the kernel
    /// command line is the channel.
    pub fn from_env(var: &str) -> Result<Vec<NetConfig>> {
        match std::env::var(var) {
            Ok(spec) if !spec.trim().is_empty() => NetConfig::parse_list(spec.trim()),
            _ => Ok(Vec::new()),
        }
    }
}

// ── the encoders ────────────────────────────────────────────────────────────
//
// `netlink_message`, `rtattr`, `align4` and `netlink_socket` are public (lane G6): a
// product that READS the route table itself (Gunnar's dashboard probe, its `RTM_GETADDR`
// dump) frames its requests with these instead of keeping a copy.

/// A netlink message: a 16-byte `nlmsghdr` with `nlmsg_len` filled in from the
/// finished body, then the family payload, then the attributes.
///
/// `nlmsg_pid` is left 0: that field is the *destination* port for a request,
/// and 0 is the kernel.
pub fn netlink_message(kind: u16, flags: u16, seq: u32, body: &[u8]) -> Vec<u8> {
    let len = NLMSGHDR_LEN + body.len();
    let mut msg = Vec::with_capacity(align4(len));
    msg.extend_from_slice(&(len as u32).to_ne_bytes());
    msg.extend_from_slice(&kind.to_ne_bytes());
    msg.extend_from_slice(&flags.to_ne_bytes());
    msg.extend_from_slice(&seq.to_ne_bytes());
    msg.extend_from_slice(&0u32.to_ne_bytes());
    msg.extend_from_slice(body);
    // The header's length field counts the unpadded message; the *buffer* is
    // padded to four so a following message would be aligned. The kernel
    // tolerates either for a single message and this keeps the two rules
    // separate rather than conflated.
    msg.resize(align4(len), 0);
    msg
}

/// `struct rtattr`: `{ u16 len, u16 type }` then the payload, padded to four.
///
/// `rta_len` counts the header and the payload but **not** the padding, which
/// is the single most common way to get netlink subtly wrong: the kernel reads
/// `rta_len` for the value and `RTA_ALIGN(rta_len)` for the step to the next
/// attribute.
pub fn rtattr(kind: u16, payload: &[u8]) -> Vec<u8> {
    let len = 4 + payload.len();
    let mut out = Vec::with_capacity(align4(len));
    out.extend_from_slice(&(len as u16).to_ne_bytes());
    out.extend_from_slice(&kind.to_ne_bytes());
    out.extend_from_slice(payload);
    out.resize(align4(len), 0);
    out
}

/// `NLMSG_ALIGN` / `RTA_ALIGN`: `n` rounded up to a multiple of four.
pub const fn align4(n: usize) -> usize {
    (n + 3) & !3
}

/// `ip link set <index> up`, as `RTM_NEWLINK` with `ifi_flags = ifi_change = IFF_UP`.
///
/// `ifi_change` is the mask of which flag bits this message is allowed to
/// touch. A message with `ifi_flags = IFF_UP` and `ifi_change = 0` changes
/// **nothing** and still returns success, which is the shape of a green that
/// has never been red.
pub fn link_up_message(seq: u32, ifindex: u32) -> Vec<u8> {
    let mut body = Vec::with_capacity(16);
    body.push(0); // ifi_family, AF_UNSPEC for a link
    body.push(0); // __ifi_pad
    body.extend_from_slice(&0u16.to_ne_bytes()); // ifi_type, ignored on set
    body.extend_from_slice(&(ifindex as i32).to_ne_bytes());
    body.extend_from_slice(&IFF_UP.to_ne_bytes()); // ifi_flags
    body.extend_from_slice(&IFF_UP.to_ne_bytes()); // ifi_change
    netlink_message(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, seq, &body)
}

/// `RTM_NEWADDR`: `ip addr add <addr>/<prefix> dev <index>`.
///
/// Both `IFA_LOCAL` and `IFA_ADDRESS` are sent with the same value. On a
/// broadcast link they are the same thing; on a point-to-point link `IFA_LOCAL`
/// is this end and `IFA_ADDRESS` is the peer. Sending only one of them is
/// accepted and then means something different from what was intended.
pub fn address_message(seq: u32, ifindex: u32, address: Ipv4Addr, prefix: u8) -> Vec<u8> {
    let mut body = Vec::with_capacity(8 + 3 * 8);
    body.push(AF_INET);
    body.push(prefix);
    body.push(0); // ifa_flags
    body.push(RT_SCOPE_UNIVERSE); // ifa_scope
    body.extend_from_slice(&ifindex.to_ne_bytes());
    body.extend_from_slice(&rtattr(IFA_LOCAL, &address.octets()));
    body.extend_from_slice(&rtattr(IFA_ADDRESS, &address.octets()));
    // The broadcast address, computed rather than assumed: without it the
    // kernel leaves the interface with none, and a subnet-directed broadcast
    // silently goes nowhere.
    if prefix <= 30 {
        let host_bits = 32 - u32::from(prefix);
        let broadcast = Ipv4Addr::from(
            u32::from(address) | (u32::MAX.checked_shr(u32::from(prefix)).unwrap_or(0)),
        );
        let _ = host_bits;
        body.extend_from_slice(&rtattr(IFA_BROADCAST, &broadcast.octets()));
    }
    netlink_message(
        RTM_NEWADDR,
        NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
        seq,
        &body,
    )
}

/// `ip route add default via <gw> dev <index>` — `RTM_NEWROUTE`.
///
/// `rtm_dst_len = 0` with no `RTA_DST` **is** the default route; there is no
/// separate "default" flag.
pub fn default_route_message(seq: u32, ifindex: u32, gateway: Ipv4Addr) -> Vec<u8> {
    route_message(seq, ifindex, Ipv4Addr::UNSPECIFIED, 0, gateway)
}

/// `ip route add <dst>/<len> via <gw> dev <index>` — `RTM_NEWROUTE`, for ANY
/// destination, which is what option 121 hands out.
///
/// The only differences from the default route are `rtm_dst_len` and an
/// `RTA_DST` attribute, and that attribute must be ABSENT for `/0`: a
/// zero-length destination with a four-byte `RTA_DST` of zeros is a different
/// message, and the kernel is within its rights to read it differently. An
/// unspecified gateway means the destination is on-link (RFC 3442 permits it),
/// and then `RTA_GATEWAY` is left out and the scope is `RT_SCOPE_LINK` —
/// `via 0.0.0.0` is not a next hop, it is the absence of one.
pub fn route_message(
    seq: u32,
    ifindex: u32,
    destination: Ipv4Addr,
    prefix: u8,
    gateway: Ipv4Addr,
) -> Vec<u8> {
    let on_link = gateway.is_unspecified();
    let mut body = Vec::with_capacity(12 + 24);
    body.push(AF_INET);
    body.push(prefix); // rtm_dst_len
    body.push(0); // rtm_src_len
    body.push(0); // rtm_tos
    body.push(RT_TABLE_MAIN);
    body.push(RTPROT_BOOT);
    body.push(if on_link {
        RT_SCOPE_LINK
    } else {
        RT_SCOPE_UNIVERSE
    });
    body.push(RTN_UNICAST);
    body.extend_from_slice(&0u32.to_ne_bytes()); // rtm_flags
    if prefix > 0 {
        body.extend_from_slice(&rtattr(RTA_DST, &destination.octets()));
    }
    if !on_link {
        body.extend_from_slice(&rtattr(RTA_GATEWAY, &gateway.octets()));
    }
    body.extend_from_slice(&rtattr(RTA_OIF, &ifindex.to_ne_bytes()));
    netlink_message(
        RTM_NEWROUTE,
        NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
        seq,
        &body,
    )
}

/// `ip link show <index>`, as `RTM_GETLINK` for one interface.
///
/// Not a dump: the index is already known (SIOCGIFNAME found it), so the
/// kernel answers with a single `RTM_NEWLINK` for exactly that link rather than
/// a multipart walk of every interface in the namespace.
pub fn get_link_message(seq: u32, ifindex: u32) -> Vec<u8> {
    let mut body = Vec::with_capacity(16);
    body.push(0); // ifi_family
    body.push(0); // __ifi_pad
    body.extend_from_slice(&0u16.to_ne_bytes()); // ifi_type
    body.extend_from_slice(&(ifindex as i32).to_ne_bytes());
    body.extend_from_slice(&0u32.to_ne_bytes()); // ifi_flags
    body.extend_from_slice(&0u32.to_ne_bytes()); // ifi_change
    netlink_message(RTM_GETLINK, NLM_F_REQUEST, seq, &body)
}

/// The `IFLA_ADDRESS` out of an `RTM_GETLINK` answer: the interface's MAC.
///
/// Pure over the reply bytes for the same reason the encoders are pure over
/// theirs: the attribute walk (`rta_len` for the value, `RTA_ALIGN` for the
/// step) is exactly where netlink parsing goes wrong, and a MAC is six bytes —
/// `RTA_ALIGN(4+6) = 12 ≠ 10`, so unlike the all-IPv4 messages above, THIS walk
/// actually exercises the padding rule and a test can see it red.
pub fn mac_from_getlink_reply(buf: &[u8]) -> Result<[u8; 6]> {
    if buf.len() < NLMSGHDR_LEN {
        bail!(
            "netlink answered {} bytes, shorter than a header",
            buf.len()
        );
    }
    let kind = u16::from_ne_bytes([buf[4], buf[5]]);
    if kind == NLMSG_ERROR {
        // Found red before it was green: the first version indexed 16..20
        // unconditionally and a truncated error message panicked the test —
        // which at runtime is PID 1 panicking on a short kernel answer.
        let Some(e) = buf.get(NLMSG_ERROR_ERRNO_AT..NLMSG_ERROR_ERRNO_AT + 4) else {
            bail!(
                "a truncated NLMSG_ERROR ({} bytes) answered RTM_GETLINK",
                buf.len()
            );
        };
        let errno = i32::from_ne_bytes([e[0], e[1], e[2], e[3]]);
        bail!(
            "RTM_GETLINK refused by the kernel: errno {} ({})",
            -errno,
            std::io::Error::from_raw_os_error(-errno)
        );
    }
    if kind != RTM_NEWLINK {
        bail!("expected an RTM_NEWLINK answer, got message type {kind}");
    }
    let msg_len = (u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize).min(buf.len());
    // nlmsghdr (16) + ifinfomsg (16), then attributes until the declared end.
    let mut at = NLMSGHDR_LEN + 16;
    while at + 4 <= msg_len {
        let rta_len = u16::from_ne_bytes([buf[at], buf[at + 1]]) as usize;
        let rta_type = u16::from_ne_bytes([buf[at + 2], buf[at + 3]]);
        if rta_len < 4 || at + rta_len > msg_len {
            bail!("malformed rtattr at offset {at}: rta_len {rta_len}");
        }
        if rta_type == IFLA_ADDRESS {
            let payload = &buf[at + 4..at + rta_len];
            let mac: [u8; 6] = payload.try_into().with_context(|| {
                format!("IFLA_ADDRESS is {} bytes, not a 6-byte MAC", payload.len())
            })?;
            return Ok(mac);
        }
        at += align4(rta_len);
    }
    bail!("the RTM_NEWLINK answer carried no IFLA_ADDRESS attribute");
}

// ── the socket ──────────────────────────────────────────────────────────────

/// Send one request and read its `NLMSG_ERROR` acknowledgement.
///
/// Every message carries `NLM_F_ACK`, so the kernel answers *every* one, with
/// `errno == 0` for success. A netlink caller that does not read the ack
/// cannot distinguish "applied" from "rejected", because `send` succeeds
/// either way. That is the whole reason this returns a `Result` at all.
pub(crate) fn request(sock: &rustix::fd::OwnedFd, message: &[u8], what: &str) -> Result<()> {
    use rustix::net::{recv, send, RecvFlags, SendFlags};

    send(sock, message, SendFlags::empty()).with_context(|| format!("netlink send ({what})"))?;

    let mut buf = [0u8; 4096];
    let (n, _truncated) = recv(sock, &mut buf[..], RecvFlags::empty())
        .with_context(|| format!("netlink recv ({what})"))?;
    if n < NLMSGHDR_LEN + 4 {
        bail!("netlink ({what}): the kernel answered {n} bytes, too short for an acknowledgement");
    }
    let kind = u16::from_ne_bytes([buf[4], buf[5]]);
    if kind != NLMSG_ERROR {
        bail!("netlink ({what}): expected NLMSG_ERROR (2), got message type {kind}");
    }
    let errno = i32::from_ne_bytes([buf[16], buf[17], buf[18], buf[19]]);
    if errno != 0 {
        bail!(
            "netlink ({what}) refused by the kernel: errno {} ({})",
            -errno,
            std::io::Error::from_raw_os_error(-errno)
        );
    }
    Ok(())
}

/// What an `NLMSG_ERROR` acknowledgement means for a request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ack {
    /// Applied.
    Applied,
    /// `EEXIST` on a request that tolerates it: the address or route is there already (a
    /// live update's re-exec: the kernel kept the network the old process laid).
    AlreadyThere,
}

/// Pure: **the acknowledgement's errno, judged** — 0 is applied; `EEXIST` (17) is "already
/// there" where `exists_ok`; anything else is the kernel's refusal (Gunnar's `ack_verdict`).
pub fn ack_verdict(errno: i32, exists_ok: bool) -> std::result::Result<Ack, i32> {
    match -errno {
        0 => Ok(Ack::Applied),
        17 if exists_ok => Ok(Ack::AlreadyThere),
        e => Err(e),
    }
}

/// [`request`], with `EEXIST` read as "already there" ([`ack_verdict`]).
pub fn request_or_there(sock: &rustix::fd::OwnedFd, message: &[u8], what: &str) -> Result<Ack> {
    match request(sock, message, what) {
        Ok(()) => Ok(Ack::Applied),
        Err(e) if is_eexist(&e) => Ok(Ack::AlreadyThere),
        Err(e) => Err(e),
    }
}

/// An `AF_NETLINK`/`NETLINK_ROUTE` socket.
///
/// Not `bind`-ed: an unbound netlink socket is auto-bound by the kernel on
/// first send, with a port id the kernel picks. Binding by hand would mean
/// choosing that id, and two gunnars on one host choosing the same one is a
/// failure mode invented for no gain.
pub fn netlink_socket() -> Result<rustix::fd::OwnedFd> {
    use rustix::net::{socket, AddressFamily, SocketType};
    // `None` for the protocol is NETLINK_ROUTE (0); rustix models the protocol
    // as a `NonZero`, so the default cannot be spelled any other way.
    socket(AddressFamily::NETLINK, SocketType::RAW, None)
        .context("opening an AF_NETLINK/NETLINK_ROUTE socket")
}

/// The index of `name`, via `SIOCGIFNAME` on a throwaway datagram socket.
pub(crate) fn interface_index(name: &str) -> Result<u32> {
    use rustix::net::{netdevice, socket, AddressFamily, SocketType};
    let probe = socket(AddressFamily::INET, SocketType::DGRAM, None)
        .context("opening a socket to look an interface index up")?;
    netdevice::name_to_index(&probe, name)
        .with_context(|| format!("no interface named {name:?}; is the driver in the kernel?"))
}

/// Send one request and read back a **data** answer (not an ack).
///
/// The twin of [`request`] for the one query this file makes: `RTM_GETLINK`
/// carries no `NLM_F_ACK`, so the kernel's answer *is* the payload. Kept
/// separate rather than parameterising `request`, because "expects
/// `NLMSG_ERROR`" and "expects data" are opposite assertions and a function
/// that accepts either verifies neither.
pub(crate) fn query(sock: &rustix::fd::OwnedFd, message: &[u8], what: &str) -> Result<Vec<u8>> {
    use rustix::net::{recv, send, RecvFlags, SendFlags};

    send(sock, message, SendFlags::empty()).with_context(|| format!("netlink send ({what})"))?;
    let mut buf = vec![0u8; 8192];
    let (n, _truncated) = recv(sock, &mut buf[..], RecvFlags::empty())
        .with_context(|| format!("netlink recv ({what})"))?;
    buf.truncate(n);
    Ok(buf)
}

/// The MAC of `name`, over the same `NETLINK_ROUTE` socket family everything
/// else here uses. The DHCP client needs it for `chaddr`: on the appliance
/// `/sys` is a mount point with nothing mounted on it (korp-start mounts only
/// `/proc`), so the sysfs shortcut does not exist and netlink is the front
/// door anyway.
pub fn interface_mac(name: &str) -> Result<[u8; 6]> {
    let index = interface_index(name)?;
    let sock = netlink_socket()?;
    let answer = query(
        &sock,
        &get_link_message(1, index),
        &format!("getlink {name}"),
    )?;
    mac_from_getlink_reply(&answer)
        .with_context(|| format!("reading the MAC address of {name} over netlink"))
}

/// **`RTM_GETADDR` for the whole IPv4 address table**, as a dump request.
///
/// No `NLM_F_ACK`: like `RTM_GETLINK`, the kernel's answer IS the payload, and
/// asking for an ack as well would mean reading two different shapes off one
/// socket.
pub fn get_addr_dump_message(seq: u32) -> Vec<u8> {
    // `struct ifaddrmsg` — family, prefixlen, flags, scope, index. Only the
    // family is a filter on a dump; the kernel ignores the rest and we send
    // zeros rather than a plausible-looking index that does nothing.
    let mut body = Vec::with_capacity(8);
    body.push(AF_INET);
    body.push(0); // ifa_prefixlen
    body.push(0); // ifa_flags
    body.push(0); // ifa_scope
    body.extend_from_slice(&0u32.to_ne_bytes()); // ifa_index
    netlink_message(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP, seq, &body)
}

/// **The IPv4 address `ifindex` holds, out of an `RTM_GETADDR` dump.**
///
/// Split from the socket so the parser is testable against bytes: every dump
/// this function has ever been wrong about can be replayed as a `&[u8]`.
///
/// `IFA_LOCAL` is preferred over `IFA_ADDRESS` and that is not a style choice:
/// on a point-to-point link `IFA_ADDRESS` is the PEER's address, so a reader
/// that took whichever came first would bind a listener to the other end of
/// the tunnel. On a broadcast link they are equal and the preference is inert.
///
/// The first match wins, and `None` means the table carried no IPv4 for that
/// index — never `0.0.0.0`, never a guess.
pub fn ipv4_from_addr_dump(buf: &[u8], ifindex: u32) -> Option<Ipv4Addr> {
    let mut off = 0usize;
    while off + NLMSGHDR_LEN <= buf.len() {
        let len = u32::from_ne_bytes(buf[off..off + 4].try_into().ok()?) as usize;
        let kind = u16::from_ne_bytes(buf[off + 4..off + 6].try_into().ok()?);
        if len < NLMSGHDR_LEN || off + len > buf.len() {
            return None;
        }
        if kind == NLMSG_DONE || kind == NLMSG_ERROR {
            return None;
        }
        if kind == RTM_NEWADDR {
            let body = &buf[off + NLMSGHDR_LEN..off + len];
            // `struct ifaddrmsg` is 8 bytes: family, prefixlen, flags, scope,
            // index(u32).
            if body.len() >= 8 && body[0] == AF_INET {
                let index = u32::from_ne_bytes(body[4..8].try_into().ok()?);
                if index == ifindex {
                    let mut attrs = &body[8..];
                    let mut fallback: Option<Ipv4Addr> = None;
                    while attrs.len() >= 4 {
                        let rta_len =
                            u16::from_ne_bytes(attrs[0..2].try_into().ok()?) as usize;
                        let rta_type = u16::from_ne_bytes(attrs[2..4].try_into().ok()?);
                        if rta_len < 4 || rta_len > attrs.len() {
                            break;
                        }
                        let payload = &attrs[4..rta_len];
                        if payload.len() == 4 {
                            let octets: [u8; 4] = payload.try_into().ok()?;
                            match rta_type {
                                IFA_LOCAL => return Some(Ipv4Addr::from(octets)),
                                IFA_ADDRESS => fallback = Some(Ipv4Addr::from(octets)),
                                _ => {}
                            }
                        }
                        let step = align4(rta_len);
                        if step == 0 || step > attrs.len() {
                            break;
                        }
                        attrs = &attrs[step..];
                    }
                    if let Some(addr) = fallback {
                        return Some(addr);
                    }
                }
            }
        }
        let step = align4(len);
        if step == 0 {
            return None;
        }
        off += step;
    }
    None
}

/// **What IPv4 address does this interface hold right now?**
///
/// The lookup behind `--grpc-bind <iface>:<port>`
/// ([`gunnar_common::bindspec::BindSpec`]). Over the same `NETLINK_ROUTE`
/// socket everything else in this file uses, because on the appliance `/sys`
/// is a mount point with nothing mounted on it — there is no sysfs shortcut
/// and there is no `ip(8)`.
///
/// `Ok(None)` is the honest answer for "the interface is there and has no
/// address" — which is what a DHCP handshake that has not completed looks
/// like. `Err` is "there is no such interface", or netlink refused. The caller
/// tells the two apart in its refusal sentence; neither is ever an address.
pub fn interface_ipv4(name: &str) -> Result<Option<Ipv4Addr>> {
    let index = interface_index(name)?;
    let sock = netlink_socket()?;
    let dump = query_dump(&sock, &get_addr_dump_message(1), &format!("getaddr {name}"))?;
    Ok(ipv4_from_addr_dump(&dump, index))
}

/// Send one dump request and read every answer until `NLMSG_DONE`.
///
/// [`query`] reads ONE datagram, which is right for `RTM_GETLINK` (one answer)
/// and wrong for a dump: the kernel splits a table across as many datagrams as
/// it needs, and a reader that took the first would silently see a prefix of
/// the table. On a two-NIC appliance the missing row is exactly the one being
/// looked for.
pub(crate) fn query_dump(
    sock: &rustix::fd::OwnedFd,
    message: &[u8],
    what: &str,
) -> Result<Vec<u8>> {
    use rustix::net::{recv, send, RecvFlags, SendFlags};

    send(sock, message, SendFlags::empty()).with_context(|| format!("netlink send ({what})"))?;
    let mut out = Vec::new();
    // A dump of a machine's address table is small; this cap exists so a
    // misparse can never spin forever on a socket that keeps answering.
    for _ in 0..64 {
        let mut buf = vec![0u8; 32 * 1024];
        let (n, _truncated) = recv(sock, &mut buf[..], RecvFlags::empty())
            .with_context(|| format!("netlink recv ({what})"))?;
        buf.truncate(n);
        let done = dump_is_done(&buf);
        out.extend_from_slice(&buf);
        if done || n == 0 {
            return Ok(out);
        }
    }
    Ok(out)
}

/// Does this datagram carry the `NLMSG_DONE` that ends a dump?
fn dump_is_done(buf: &[u8]) -> bool {
    let mut off = 0usize;
    while off + NLMSGHDR_LEN <= buf.len() {
        let Ok(len_bytes) = buf[off..off + 4].try_into() else {
            return true;
        };
        let len = u32::from_ne_bytes(len_bytes) as usize;
        let Ok(kind_bytes) = buf[off + 4..off + 6].try_into() else {
            return true;
        };
        let kind = u16::from_ne_bytes(kind_bytes);
        if kind == NLMSG_DONE || kind == NLMSG_ERROR {
            return true;
        }
        if len < NLMSGHDR_LEN {
            return true;
        }
        off += align4(len);
    }
    false
}

pub fn bring_up_loopback() -> Result<()> {
    let index = interface_index("lo")?;
    let sock = netlink_socket()?;
    request(&sock, &link_up_message(1, index), "lo up")?;
    Ok(())
}

/// Bring `interface` up and return its index.
///
/// The half of [`apply`] the DHCP path needs *before* it has an address: a
/// DISCOVER cannot leave a downed link. Factored out rather than duplicated so
/// "who sets IFF_UP" has one answer; `apply` re-sends it, which is idempotent
/// (`ifi_change` masks only the UP bit and up-to-up is a no-op).
pub fn bring_link_up(interface: &str) -> Result<u32> {
    let index = interface_index(interface)?;
    let sock = netlink_socket()?;
    request(
        &sock,
        &link_up_message(1, index),
        &format!("{interface} up"),
    )?;
    Ok(index)
}

/// Apply a [`NetPlan`]: link up, address, default route.
///
/// The ONE writer of addresses and routes, whichever way the plan was learned:
/// the static the net spec form arrives here parsed, and the DHCP lease arrives
/// here converted ([`bring_up_network`]'s Dhcp arm). There is deliberately no
/// second "apply the lease" path to drift against this one.
pub fn apply(plan: &NetPlan) -> Result<()> {
    let index = bring_link_up(&plan.interface)?;
    let sock = netlink_socket()?;
    // A live-update RE-EXEC starts the new PID 1 while the kernel still holds the lease
    // (the ONE box 2026-10-17: EEXIST, "eth0 NOT up", no renewal loop). The kernel says
    // EEXIST for an address only when THIS address is already on the interface: applied.
    let what = format!("{}/{} on {}", plan.address, plan.prefix, plan.interface);
    if request_or_there(&sock, &address_message(2, index, plan.address, plan.prefix), &what)? == Ack::AlreadyThere {
        tracing::info!(interface = %plan.interface, address = %what, "the address was already on the interface (a re-exec keeps the kernel's network): applied");
    }
    if let Some(gateway) = plan.gateway {
        // A default route is EEXIST whatever its gateway, so it is REPLACED, never assumed:
        // the plan's gateway is the one that stands.
        let what = format!("default via {gateway}");
        if request_or_there(&sock, &default_route_message(3, index, gateway), &what)? == Ack::AlreadyThere {
            request(&sock, &with_replace(default_route_message(4, index, gateway)), &what)?;
            tracing::info!(interface = %plan.interface, route = %what, "a default route was already there (a re-exec): replaced by the plan's");
        }
    }
    Ok(())
}

/// `EEXIST` from [`request`] (its errors carry the kernel's errno as text).
pub(crate) fn is_eexist(e: &anyhow::Error) -> bool {
    let text = format!("{e:#}");
    text.contains("File exists") || text.contains("EEXIST")
}

/// The same message with `NLM_F_REPLACE` in place of `NLM_F_EXCL`.
pub(crate) fn with_replace(mut msg: Vec<u8>) -> Vec<u8> {
    let flags = u16::from_ne_bytes([msg[6], msg[7]]);
    msg[6..8].copy_from_slice(&((flags & !NLM_F_EXCL) | NLM_F_REPLACE).to_ne_bytes());
    msg
}

/// A DHCP lease in hand, and what is needed to keep it alive: the caller
/// spawns [`dhcp_renewal`] with this once the runtime is up.
#[cfg(feature = "dhcp")]
#[derive(Debug, Clone)]
pub struct DhcpSession {
    /// The interface the lease is applied to.
    pub interface: String,
    /// Its MAC — `chaddr` for every renewal REQUEST.
    pub mac: [u8; 6],
    /// The lease as granted at boot.
    pub lease: crate::dhcp::Lease,
    /// Whether this is the FIRST entry of the net spec, and so the one that
    /// owns the box-wide singletons: the default route, the hostname — and now
    /// `/etc/resolv.conf`.
    ///
    /// Carried on the session because the renewal loop needs the same answer
    /// the boot path had, and there is exactly one resolver file for however
    /// many interfaces the declaration names. `eth0:dhcp,eth1:dhcp` is a public
    /// NIC and a utility one; letting the utility network's lease overwrite the
    /// resolver on its own T1 would be the same coin flip over egress that
    /// `NetConfig::parse_list` refuses for the default route, except it would
    /// happen hours after boot with nobody watching.
    pub owns_default_route: bool,
}

/// **With `dhcp` off there is no such thing as a lease**, and this type
/// says so in the type system rather than in a comment: it is UNINHABITED.
///
/// It exists so [`bring_up_network`] keeps ONE signature across both builds
/// (one implementation — the alternative is two copies of a forty-line function that differ
/// by one arm). Its `Option<DhcpSession>` is then provably `None`, and every
/// downstream `Some(session)` branch is a `match session {}` the compiler
/// proves unreachable — a by-construction guarantee, not a runtime check.
#[cfg(not(feature = "dhcp"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DhcpSession {}

/// **What the product says about itself to the bring-up** — the three
/// things gunnar-server hard-wired (its host-name prefix, the name it proves
/// the resolver on, and where the "network up" line goes), as values.
pub struct Bringup<'a> {
    /// A box with no lease name is called `<prefix>-<last three MAC bytes>`.
    pub hostname_prefix: &'a str,
    /// One `getaddrinfo` of this name once the resolver is published, to prove
    /// it answers, asked again for about 30 s when the first try misses
    /// ([`prove_with`], [`RESOLVER_RETRY_WAITS`]); `None`: no proof asked.
    pub resolver_probe: Option<&'a str>,
    /// Handed each "network up" / "network NOT up" line — the moment the
    /// boot narration can leave ([`crate::narration::ship_boot_narration`]).
    pub narrate: &'a dyn Fn(&str),
}

/// **What the product says about a lease that comes AFTER the boot** (lane G6): a
/// boot DHCP that failed is asked again on a thread of its own (lane D12), long after
/// [`bring_up_network`] returned and its [`Bringup`] borrow ended. So what that thread
/// needs is `'static`: where the "network up" line goes, and the product's T1 test
/// seam. The host-name prefix and the resolver probe are copied off the [`Bringup`].
#[derive(Debug, Clone, Copy)]
pub struct LateLease {
    /// Handed the late lease's "network up" line — the boot lease's own line
    /// ([`Bringup::narrate`]'s), with the retry's count after it.
    pub narrate: fn(&str),
    /// The T1 test seam's variable (`crate::runtime::dhcp_t1_override`), read when the
    /// late lease lands, as the boot's sessions are; `None`: no seam.
    pub t1_override_var: Option<&'static str>,
}

impl LateLease {
    /// A late lease said nowhere and renewed on the server's clock: what
    /// [`bring_up_network`] (no [`LateLease`] given) does.
    pub const QUIET: LateLease = LateLease { narrate: say_nothing, t1_override_var: None };
}

fn say_nothing(_: &str) {}

/// Everything PID 1 owes the system before the listeners bind.
///
/// Called by the product's PID 1 before its listeners bind. The failure grades are deliberate and
/// different:
///
/// * a **malformed** the net spec, or a static plan the kernel refuses, is an
///   error — a declaration that lies must stop the boot, the same treatment
///   `Readiness::RefuseToServe` gives a broken identity;
/// * a **DHCP timeout** is a WARN and the boot goes on unreachable, exactly
///   the posture of a missing the net spec: the world failed to answer, the
///   declaration did not lie, and no address is ever invented to paper over
///   the difference.
///
/// Returns `Some` exactly when a DHCP lease was taken — the one case with a
/// follow-up duty (renewal). Static and unconfigured paths return `None` and
/// stay exactly what they were.
pub fn bring_up_network(plans: Vec<NetConfig>, how: &Bringup<'_>) -> Result<Vec<DhcpSession>> {
    bring_up_network_with(plans, how, LateLease::QUIET)
}

/// [`bring_up_network`], and a boot DHCP that failed and is asked again in the
/// background lands as `late` says (lane G6): its "network up" line said through
/// `late.narrate`, its renewal on the product's T1 seam, its resolver proven with
/// [`Bringup::resolver_probe`] — as the boot's own lease is.
pub fn bring_up_network_with(plans: Vec<NetConfig>, how: &Bringup<'_>, late: LateLease) -> Result<Vec<DhcpSession>> {
    if plans.is_empty() {
        if is_init() {
            tracing::warn!(
                "running as pid 1 with no net spec: no interface will be \
                 configured, and unless something else already did it this server is \
                 unreachable. Pass <NET>=<iface>:<addr>/<prefix>[:<gateway>] or \
                 <NET>=<iface>:dhcp on the kernel command line."
            );
        }
        return Ok(Vec::new());
    }
    bring_up_loopback().context("bringing the loopback interface up")?;
    // A box laid with fewer NICs than the spec names (the lean estate's main and twin: a
    // utility interface alone): the NICs it has, the first of them owning the default route.
    let (plans, skipped) = present_only(plans, interface_exists);
    for name in &skipped {
        tracing::info!(interface = %name, "the net spec names {name}, which this box was not laid with: skipped (a box behind its front has its utility NIC alone)");
        (how.narrate)(&format!("network: {name} is not on this box, skipped"));
    }
    let mut sessions = Vec::new();
    for (position, config) in plans.into_iter().enumerate() {
        // **Only the first plan may install a default route.** See
        // `NetConfig::parse_list`: a second default through a utility network
        // with no way out is a coin flip over egress, not a richer setup.
        let owns_default_route = position == 0;
        if let Some(session) = bring_up_one(config, owns_default_route, how, late)? {
            sessions.push(session);
        }
    }
    Ok(sessions)
}

/// Pure: **the net spec's entries for the NICs this box has** (owner 2026-10-23, the lean
/// estate: a box laid with a utility interface alone boots the same two-NIC spec as every other
/// box). `exists` says whether an interface is there; the entries it denies are returned by
/// name, to be said once. A spec none of whose interfaces exist is kept whole: the boot goes on
/// as before and says what failed.
pub fn present_only(plans: Vec<NetConfig>, exists: impl Fn(&str) -> bool) -> (Vec<NetConfig>, Vec<String>) {
    if !plans.iter().any(|c| exists(c.interface())) {
        return (plans, Vec::new());
    }
    let (kept, gone): (Vec<NetConfig>, Vec<NetConfig>) = plans.into_iter().partition(|c| exists(c.interface()));
    (kept, gone.iter().map(|c| c.interface().to_string()).collect())
}

/// Whether the kernel has a network interface of this name (`/sys/class/net/<name>`), asked a
/// few times over two seconds: a virtio NIC is probed before init runs, so one that is not
/// there by then is a NIC the box was not laid with.
fn interface_exists(name: &str) -> bool {
    let at = std::path::Path::new("/sys/class/net").join(name);
    for _ in 0..10 {
        if at.exists() {
            return true;
        }
        std::thread::sleep(std::time::Duration::from_millis(200));
    }
    false
}

/// One entry of the net spec. Split out of [`bring_up_network`] so the loop
/// over entries and the per-entry failure grades stay separate things.
fn bring_up_one(config: NetConfig, owns_default_route: bool, how: &Bringup<'_>, late: LateLease) -> Result<Option<DhcpSession>> {
    #[cfg(not(feature = "dhcp"))]
    let _ = late;
    match config {
        NetConfig::Static(mut plan) => {
            if !owns_default_route && plan.gateway.take().is_some() {
                tracing::info!(
                    interface = %plan.interface,
                    "the net spec names a gateway on a non-first interface; the address is \
                     applied and the route is NOT — the first entry owns the default route"
                );
            }
            apply(&plan).with_context(|| format!("configuring {}", plan.interface))?;
            tracing::info!(
                interface = %plan.interface,
                address = %plan.address,
                prefix = plan.prefix,
                gateway = ?plan.gateway,
                "network configured from the net spec over netlink; no ip(8), no shell"
            );
            (how.narrate)(&format!(
                "network up: {} {}/{} gw {:?} (static)",
                plan.interface, plan.address, plan.prefix, plan.gateway
            ));
            Ok(None)
        }
        #[cfg(feature = "dhcp")]
        NetConfig::Dhcp { interface } => match configure_via_dhcp(&interface, owns_default_route, how, false) {
            Ok(session) => {
                tracing::info!(
                    interface = %interface,
                    address = %session.lease.address,
                    prefix = session.lease.prefix,
                    gateway = ?session.lease.gateway,
                    server = %session.lease.server,
                    lease_secs = session.lease.lease_secs,
                    "network configured from the net spec over DHCP + netlink; no ip(8), \
                     no shell, no dhclient"
                );
                // The box gets its name here, from the lease when the server
                // named it (option 12) and from its own MAC otherwise — the
                // kernel boots as "(none)" and nothing else ever sets it.
                if owns_default_route {
                    apply_hostname(&hostname_for(
                        how.hostname_prefix,
                        session.lease.host_name.as_deref(),
                        session.mac,
                    ));
                }
                // THE moment the debug channel exists: the lease is on the
                // interface, so korp-start's spooled narration can leave now.
                (how.narrate)(&lease_note(&session));
                Ok(Some(session))
            }
            Err(err) => {
                tracing::warn!(
                    interface = %interface,
                    "<NET>={interface}:dhcp and the handshake FAILED at boot: {err:#}. No \
                     address is configured yet — a made-up one would be worse — and PID 1 \
                     asks again in the background ({} s doubling to {} s, until a lease \
                     comes); a step that needs {interface}'s address waits for it \
                     (dhcp_pending).",
                    DHCP_RETRY_FIRST.as_secs(),
                    DHCP_RETRY_CEILING.as_secs()
                );
                retry_dhcp_in_background(&interface, owns_default_route, how.hostname_prefix, how.resolver_probe, late);
                // Attempted anyway: without an address the sends fail and are
                // swallowed, but a link something else addressed still carries it.
                (how.narrate)(&format!(
                    "network NOT up: DHCP on {interface} failed: {err:#}"
                ));
                Ok(None)
            }
        },
    }
}

/// The first wait before a failed boot DHCP is asked again (lane D12; Gunnar's
/// `pid1::DHCP_RETRY_FIRST`, chaos g7): doubling to [`DHCP_RETRY_CEILING`], forever.
pub const DHCP_RETRY_FIRST: std::time::Duration = std::time::Duration::from_secs(2);
/// The longest wait between two asks.
pub const DHCP_RETRY_CEILING: std::time::Duration = std::time::Duration::from_secs(30);

/// **Ask until it answers** — the retry of a DHCP that failed at boot (Gunnar's
/// `keep_asking`): `ask` after a wait of [`DHCP_RETRY_FIRST`] doubling to
/// [`DHCP_RETRY_CEILING`], forever; a failure said at INFO on the 1st, 2nd, 4th, 8th … ask,
/// the lease said once. Returns the lease and how many asks it took.
pub fn keep_asking<T, E: std::fmt::Display>(mut ask: impl FnMut() -> Result<T, E>, mut sleep: impl FnMut(std::time::Duration), mut say: impl FnMut(bool, String)) -> (T, u32) {
    let mut wait = DHCP_RETRY_FIRST;
    let mut failed = 0u32;
    let mut waited = std::time::Duration::ZERO;
    loop {
        sleep(wait);
        waited += wait;
        match ask() {
            Ok(v) => {
                say(false, format!("lease taken after {} ask(s) over {} s — the boot's DHCP had failed", failed + 1, waited.as_secs()));
                return (v, failed + 1);
            }
            Err(e) => {
                failed += 1;
                if failed.is_power_of_two() {
                    say(false, format!("still no lease after {failed} ask(s) over {} s ({e}) — asking again, every {} s at most", waited.as_secs(), DHCP_RETRY_CEILING.as_secs()));
                }
            }
        }
        wait = (wait * 2).min(DHCP_RETRY_CEILING);
    }
}

/// The interfaces whose boot DHCP failed and that PID 1 still asks for in the background.
static DHCP_PENDING: std::sync::Mutex<std::collections::BTreeSet<String>> = std::sync::Mutex::new(std::collections::BTreeSet::new());

/// **Is `interface`'s lease still being asked for in the background?** A step that needs
/// that interface's address waits while this is true (Gunnar's `dhcp_pending`).
pub fn dhcp_pending(interface: &str) -> bool {
    DHCP_PENDING.lock().unwrap_or_else(|p| p.into_inner()).contains(interface)
}

/// **Keep asking for `interface`'s lease on a thread of its own** (lane D12): `ask` through
/// [`keep_asking`] with `sleep`, `interface` [`dhcp_pending`] until the lease comes, then
/// `took` with it (the caller keeps it alive). The seam the real retry runs through.
pub fn retry_in_background<T: Send + 'static, E: std::fmt::Display + 'static>(
    interface: &str,
    ask: impl FnMut() -> Result<T, E> + Send + 'static,
    sleep: impl FnMut(std::time::Duration) + Send + 'static,
    took: impl FnOnce(T, u32) + Send + 'static,
) -> std::io::Result<std::thread::JoinHandle<()>> {
    let name = interface.to_string();
    DHCP_PENDING.lock().unwrap_or_else(|p| p.into_inner()).insert(name.clone());
    // A32-14, stays a thread: PID 1's DHCP retry for one interface, ended by its lease; it runs before any job ledger exists.
    let spawned = std::thread::Builder::new().name(format!("dhcp-retry-{interface}")).spawn(move || {
        let (lease, tries) = keep_asking(ask, sleep, |_, line| tracing::info!(interface = %name, "DHCP: {line}"));
        DHCP_PENDING.lock().unwrap_or_else(|p| p.into_inner()).remove(&name);
        took(lease, tries);
    });
    if spawned.is_err() {
        DHCP_PENDING.lock().unwrap_or_else(|p| p.into_inner()).remove(interface);
    }
    spawned
}

/// **The real retry of a boot DHCP that failed** (lane D12): [`retry_in_background`] over
/// [`configure_via_dhcp`] on the wire, the late lease applied as the boot's would have been
/// (address, the lease's routes, the resolver), then landed as the boot's lease lands
/// ([`late_landing`], lane G6): the hostname when this NIC owns the default route, the
/// resolver proven, the boot's "network up" line said through `late.narrate`, and the
/// lease kept alive by [`crate::runtime::dhcp_renewal`] on the product's T1 seam.
#[cfg(feature = "dhcp")]
fn retry_dhcp_in_background(interface: &str, owns_default_route: bool, hostname_prefix: &str, resolver_probe: Option<&str>, late: LateLease) {
    let (iface, prefix, probe) = (interface.to_string(), hostname_prefix.to_string(), resolver_probe.map(str::to_string));
    let spawned = retry_in_background(
        interface,
        move || {
            let quiet = |_: &str| {};
            // The probe runs once the lease LANDS (below), not inside every ask.
            let how = Bringup { hostname_prefix: &prefix, resolver_probe: None, narrate: &quiet };
            // Quiet (row 28): the boot said its failure once at WARN; keep_asking says
            // its own bounded lines, each attempt only at debug.
            configure_via_dhcp(&iface, owns_default_route, &how, true).map(|session| (session, prefix.clone(), probe.clone())).map_err(|e| format!("{e:#}"))
        },
        std::thread::sleep,
        move |(session, prefix, probe): (DhcpSession, String, Option<String>), tries| {
            #[cfg(feature = "runtime")]
            let t1 = |var: &str| crate::runtime::dhcp_t1_override(var);
            #[cfg(not(feature = "runtime"))]
            let t1 = |_: &str| None;
            let landing = late_landing(&session, tries, &prefix, probe.as_deref(), &late, t1);
            if let Some(name) = landing.resolver_probe.as_deref() {
                prove_resolver(name);
            }
            if let Some(name) = landing.hostname.as_deref() {
                apply_hostname(name);
            }
            tracing::info!(
                interface = %session.interface,
                address = %session.lease.address,
                prefix = session.lease.prefix,
                gateway = ?session.lease.gateway,
                server = %session.lease.server,
                lease_secs = session.lease.lease_secs,
                asks = tries,
                "network up: configured over DHCP after the boot's failure; no ip(8), no shell, no dhclient"
            );
            (late.narrate)(&landing.note);
            #[cfg(feature = "runtime")]
            match tokio::runtime::Builder::new_current_thread().enable_all().build() {
                Ok(rt) => rt.block_on(crate::runtime::dhcp_renewal(session, landing.t1_override)),
                Err(e) => tracing::error!(error = %e, "DHCP: no runtime to renew the late lease on — it lapses at its expiry"),
            }
            #[cfg(not(feature = "runtime"))]
            tracing::warn!(interface = %session.interface, "DHCP: this build has no renewal runtime — the late lease lapses at its expiry");
        },
    );
    if let Err(e) = spawned {
        tracing::error!(interface = %interface, error = %e, "DHCP: the background retry could not start — this interface stays without an address");
    }
}

/// **The "network up" line of a DHCP lease**, said once it is on the interface — one
/// wording for the boot's lease and a late one ([`late_landing`] adds the count).
#[cfg(feature = "dhcp")]
fn lease_note(session: &DhcpSession) -> String {
    format!(
        "network up: {} {}/{} gw {:?} via DHCP from {} (lease {}s)",
        session.interface, session.lease.address, session.lease.prefix, session.lease.gateway, session.lease.server, session.lease.lease_secs
    )
}

/// What a late lease does once it lands (pure; [`retry_dhcp_in_background`] does it).
#[cfg(feature = "dhcp")]
#[derive(Debug, Clone, PartialEq, Eq)]
struct LateLanding {
    /// The boot lease's line, with the retry's count after it.
    note: String,
    /// The box's name, when this NIC owns the default route.
    hostname: Option<String>,
    /// The name the published resolver is proven on, when this NIC published it.
    resolver_probe: Option<String>,
    /// The renewal's T1, from the product's seam.
    t1_override: Option<u32>,
}

/// **How a late lease lands** (lane G6), the way a boot lease does: said with the boot's
/// line, the box named and the resolver proven when this NIC owns the default route (only
/// that one publishes the resolver), and renewed on the product's T1 seam (`t1` reads it:
/// `crate::runtime::dhcp_t1_override` on the wire).
#[cfg(feature = "dhcp")]
fn late_landing(session: &DhcpSession, asks: u32, hostname_prefix: &str, resolver_probe: Option<&str>, late: &LateLease, t1: impl FnOnce(&str) -> Option<u32>) -> LateLanding {
    let owns = session.owns_default_route;
    LateLanding {
        note: format!("{} — after the boot's failure, {asks} asks", lease_note(session)),
        hostname: owns.then(|| hostname_for(hostname_prefix, session.lease.host_name.as_deref(), session.mac)),
        resolver_probe: resolver_probe.filter(|_| owns).map(str::to_string),
        t1_override: late.t1_override_var.and_then(t1),
    }
}

/// The Dhcp arm of [`bring_up_network`]: link up, ask, apply what was granted.
///
/// The lease is turned into a plain [`NetPlan`] and pushed through [`apply`] —
/// the same netlink writer the static path uses, so DHCP adds a *source* of
/// addresses, not a second way of setting them.
#[cfg(feature = "dhcp")]
fn configure_via_dhcp(interface: &str, owns_default_route: bool, how: &Bringup<'_>, quiet: bool) -> Result<DhcpSession> {
    bring_link_up(interface).with_context(|| format!("bringing {interface} up for DHCP"))?;
    let mac = interface_mac(interface)?;
    let lease = crate::dhcp::obtain_with(interface, mac, quiet)?;
    let plan = NetPlan {
        interface: interface.to_string(),
        address: lease.address,
        prefix: lease.prefix,
        // A utility network's DHCP server offers a router option like any
        // other. Taking it would give the box a second default route through
        // a network that does not reach the internet.
        gateway: if owns_default_route {
            lease.gateway
        } else {
            None
        },
    };
    apply(&plan).with_context(|| format!("applying the DHCP lease to {interface}"))?;
    // THE ROUTES THE LEASE NAMED. Option 3 and option 1 together describe a
    // flat network; a cloud's private network is not one. Without this the
    // appliance's utility NIC knew only its own /22 and sent everything else
    // out the PUBLIC interface, where a private destination dies — which is
    // exactly how it came up two hours out of time with nothing to say about
    // it. See `dhcp::Lease::routes` for the measurement.
    apply_leased_routes(interface, &lease.routes, owns_default_route);
    // The resolver comes AFTER the address is on the interface and is NEVER
    // allowed to undo it. A box with an address and no name service still
    // serves git over IP:port; a box that refused its lease because a file
    // write failed serves nothing at all. So this is a separate statement with
    // its own failure grade, not a `?` in the chain above.
    if owns_default_route {
        publish_resolvers(interface, &lease.dns);
        // ★ Prove it, once, on the console: one lookup through the same
        // libc every dial site uses. A resolver file that exists and a
        // resolver that answers are two different facts, and the 2026-09-08
        // re-image to add the file could not tell which one it was still
        // missing. The name is gunnar's own forge; nothing waits on this
        // beyond the 2 s x 2 the file itself sets.
        if let Some(name) = how.resolver_probe {
            prove_resolver(name);
        }
    }
    Ok(DhcpSession {
        interface: interface.to_string(),
        mac,
        lease,
        owns_default_route,
    })
}

/// **The option-121 routes, onto the interface that leased them.**
///
/// Never returns an error, on the same grading as [`publish_resolvers`]: the
/// address is already on the interface and taking the network away because one
/// route out of four was refused would be worse than the missing route. Every
/// outcome gets a line instead, because a silently skipped route is precisely
/// the shape of the defect this function closes.
///
/// Two rules, and both are about not letting a private network decide egress:
///
/// * a `/0` from option 121 is a DEFAULT route, and only the interface that
///   owns the default route may install one. The utility network's DHCP server
///   is as free to offer one as it is to offer option 3, and
///   `configure_via_dhcp` already refuses option 3 on that interface for the
///   same reason: it does not reach the internet.
/// * a route is installed on the interface whose lease named it, never
///   box-wide, so two `dhcp` entries in the net spec cannot fight.
///
/// `EEXIST` is success: the route is there, which is the whole ask, and a
/// renewal that re-applies an unchanged lease must not turn red.
#[cfg(feature = "dhcp")]
fn apply_leased_routes(interface: &str, routes: &[crate::dhcp::Route], owns_default_route: bool) {
    if routes.is_empty() {
        tracing::info!(
            interface,
            "the lease named no classless static routes (option 121); this interface reaches only \
             its own prefix"
        );
        return;
    }
    let index = match bring_link_up(interface) {
        Ok(i) => i,
        Err(e) => {
            tracing::warn!(interface, error = %e, "cannot install the leased routes: no interface index");
            return;
        }
    };
    let sock = match netlink_socket() {
        Ok(s) => s,
        Err(e) => {
            tracing::warn!(interface, error = %e, "cannot install the leased routes: no netlink socket");
            return;
        }
    };
    // Sequence numbers start well past the ones `apply` used on its own socket,
    // so a reply is never matched to the wrong request while reading a log.
    let mut seq = 100u32;
    for r in routes {
        if r.is_default() && !owns_default_route {
            tracing::info!(
                interface,
                gateway = %r.gateway,
                "option 121 offered a DEFAULT route on an interface that does not own egress — not taken"
            );
            continue;
        }
        seq += 1;
        let msg = route_message(seq, index, r.destination, r.prefix, r.gateway);
        let what = if r.gateway.is_unspecified() {
            format!("{}/{} on-link via {interface}", r.destination, r.prefix)
        } else {
            format!("{}/{} via {} dev {interface}", r.destination, r.prefix, r.gateway)
        };
        match request(&sock, &msg, &what) {
            Ok(()) => tracing::info!(interface, route = %what, "route from the lease (option 121)"),
            Err(e) => {
                if is_eexist(&e) {
                    tracing::info!(interface, route = %what, "route from the lease was already there");
                } else {
                    tracing::warn!(interface, route = %what, error = %format!("{e:#}"), "a leased route could not be installed");
                }
            }
        }
    }
}

/// Put the lease's option-6 servers where libc looks, and **say what happened**.
///
/// Never returns an error, and that is the whole design of the function: it is
/// called with a working lease already on the interface, and there is no
/// failure here that is worth taking the network away for. What there is
/// instead is a log line for every outcome, because the alternative — a silent
/// skip — is precisely the shape of the bug this fix exists to close: an
/// appliance that boots, looks healthy, and cannot say `github.com`.
///
/// `boot` splits "say it once" from "say it on every renewal": at boot every
/// outcome is narrated, including the two quiet ones, because that is the
/// operator's one chance to learn that this box has no resolver. On a renewal
/// only a *changed* file is news; an unchanged one fires twice a day for the
/// life of the appliance.
#[cfg(feature = "dhcp")]
pub(crate) fn publish_resolvers_at(interface: &str, servers: &[std::net::Ipv4Addr], boot: bool) {
    use crate::resolver::Outcome;
    match crate::resolver::publish(servers) {
        Ok(Outcome::Wrote { path, servers }) => {
            let list = servers
                .iter()
                .map(|s| s.to_string())
                .collect::<Vec<_>>()
                .join(", ");
            tracing::info!(
                interface,
                path = %path.display(),
                nameservers = %list,
                "resolver published from the DHCP lease (option 6): every \
                 getaddrinfo in this process now has a server to ask"
            );
        }
        Ok(Outcome::Unchanged) => {
            if boot {
                tracing::info!(
                    interface,
                    path = crate::resolver::RESOLV_CONF,
                    "the resolver file already named exactly this lease's servers; \
                     nothing rewritten"
                );
            }
        }
        Ok(Outcome::NoServers) => {
            if boot {
                tracing::warn!(
                    interface,
                    "the DHCP lease on {interface} carried NO domain name servers \
                     (option 6). The address and route are applied and this server \
                     is reachable by IP, but nothing in it can resolve a hostname: \
                     `mirror sync` from an https:// or ssh:// URL will fail with a \
                     DNS error until the DHCP server sends option 6. No resolver \
                     was invented and no existing one was taken away."
                );
            }
        }
        Err(err) => tracing::error!(
            interface,
            path = crate::resolver::RESOLV_CONF,
            error = %format!("{err:#}"),
            "FAILED to write the resolver file on the RAM root. The DHCP lease \
             IS applied and this server is reachable by IP — but no hostname \
             will resolve in this process until this write succeeds."
        ),
    }
}

/// The boot call. See [`publish_resolvers_at`].
#[cfg(feature = "dhcp")]
fn publish_resolvers(interface: &str, servers: &[std::net::Ipv4Addr]) {
    publish_resolvers_at(interface, servers, true);
}

/// **The appliance's host name.** The DHCP server's option 12 when it sent
/// one (trimmed to what a host name may hold), else `<prefix>-<last three MAC
/// bytes>` — stable across reboots, unique per NIC, never a fabricated
/// "localhost". Pure: the caller decides whether to apply it.
pub fn hostname_for(prefix: &str, lease_name: Option<&str>, mac: [u8; 6]) -> String {
    let clean: String = lease_name
        .unwrap_or("")
        .chars()
        .take(63)
        .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '.')
        .collect();
    let clean = clean.trim_matches(|c| c == '-' || c == '.').to_owned();
    if clean.is_empty() {
        format!("{prefix}-{:02x}{:02x}{:02x}", mac[3], mac[4], mac[5])
    } else {
        clean
    }
}

/// Write the name into the kernel — PID 1 only, and only while the kernel
/// still says "(none)": a developer box or a container keeps the name it has.
/// `/proc/sys/kernel/hostname` is `sethostname(2)` through procfs, so no new
/// syscall binding is needed. Never fatal: a name is a convenience, the
/// dashboard shows "(none)" honestly if this fails.
pub fn apply_hostname(name: &str) {
    if !is_init() {
        return;
    }
    let current = std::fs::read_to_string("/proc/sys/kernel/hostname").unwrap_or_default();
    let current = current.trim();
    if !current.is_empty() && current != "(none)" && current != "localhost" {
        return;
    }
    match std::fs::write("/proc/sys/kernel/hostname", format!("{name}\n")) {
        Ok(()) => tracing::info!(hostname = name, "the box is named"),
        Err(e) => tracing::warn!(hostname = name, error = %e, "could not set the host name"),
    }
}

#[cfg(test)]
mod hostname_tests {
    use super::hostname_for;

    #[test]
    fn the_lease_name_wins_and_is_cleaned() {
        assert_eq!(hostname_for("box", Some("gitgunnar"), [0; 6]), "gitgunnar");
        assert_eq!(
            hostname_for("box", Some("  git gunnar.cloud\0"), [0; 6]),
            "gitgunnar.cloud"
        );
        assert_eq!(hostname_for("box", Some("-bad-"), [0; 6]), "bad");
    }

    #[test]
    fn no_name_means_the_mac_names_the_box_never_localhost() {
        let mac = [0x52, 0x54, 0x00, 0xab, 0xcd, 0xef];
        assert_eq!(hostname_for("box", None, mac), "box-abcdef");
        assert_eq!(hostname_for("box", Some(""), mac), "box-abcdef");
        assert_eq!(hostname_for("box", Some("---"), mac), "box-abcdef");
    }
}

/// One `getaddrinfo` through libc, narrated: the boot-time proof that the
/// resolver `publish_resolvers` wrote is a resolver something answers on. The
/// error text is libc's own — "Name does not resolve" is a file nobody wrote or
/// a name nobody has; "Temporary failure in name resolution" is a server that
/// did not answer, i.e. UDP 53 not coming back — and that distinction is the
/// whole reason this exists.
///
/// Public, and on `net` alone (lane G6): a product that publishes or inherits its
/// resolver without edda's DHCP client proves it the same way.
pub fn prove_resolver(name: &str) {
    let started = std::time::Instant::now();
    match lookup_once(name) {
        Ok(list) => tracing::info!(
            name,
            answered_in_ms = started.elapsed().as_millis() as u64,
            addresses = %list,
            "resolver proven: libc resolved a name through the published file"
        ),
        // A first query can miss (a resolver not warm yet, a lost datagram): MEASURED on
        // test2's twin (Gunnar cf05cce1) — `Try again` 2 s after the provider's resolvers
        // were published, the front proving the same ones in 1 ms. Not an ERROR yet: asked
        // again in the background for about 30 s, and only the whole window failing is one.
        Err(err) => {
            tracing::info!(name, after_ms = started.elapsed().as_millis() as u64, error = %err, "resolver not proven on the first try; asking again for about 30 s");
            let owned = name.to_owned();
            // A32-14, stays a thread: PID 1's resolver proof, asked again for about 30 s, before any job ledger exists.
            let spawned = std::thread::Builder::new().name("resolver-proof".into()).spawn(move || {
                let name = owned;
                let t0 = std::time::Instant::now();
                match prove_with(|| lookup_once(&name), std::thread::sleep, RESOLVER_RETRY_WAITS) {
                    Ok((tries, list)) => tracing::info!(
                        name = %name,
                        tries = tries + 1,
                        after_ms = t0.elapsed().as_millis() as u64,
                        addresses = %list,
                        "resolver proven after {} tries: libc resolved a name through the published file",
                        tries + 1
                    ),
                    Err((tries, err)) => tracing::error!(
                        name = %name,
                        tries = tries + 1,
                        after_ms = t0.elapsed().as_millis() as u64,
                        error = %err,
                        "resolver NOT proven: libc could not resolve a name in {} tries over about 30 s; mirrors, webhooks and \
                         every outbound https call will fail the same way until this is fixed",
                        tries + 1
                    ),
                }
            });
            if let Err(e) = spawned {
                tracing::error!(name, error = %e, first = %err, "resolver NOT proven, and the retry could not start");
            }
        }
    }
}

/// One `getaddrinfo`: the addresses, joined, or libc's own words.
fn lookup_once(name: &str) -> Result<String, String> {
    use std::net::ToSocketAddrs;
    (name, 443u16).to_socket_addrs().map(|a| a.map(|a| a.ip().to_string()).collect::<Vec<_>>().join(", ")).map_err(|e| e.to_string())
}

/// The waits between the resolver proof's tries: about 30 s in all (Gunnar cf05cce1).
pub const RESOLVER_RETRY_WAITS: &[std::time::Duration] = &[
    std::time::Duration::from_secs(1),
    std::time::Duration::from_secs(2),
    std::time::Duration::from_secs(4),
    std::time::Duration::from_secs(8),
    std::time::Duration::from_secs(15),
];

/// **Try `lookup` until it answers**, waiting `waits` between tries (pure over its
/// inputs). `Ok((tries, answer))` / `Err((tries, last error))`, `tries` counted from 1.
pub fn prove_with(mut lookup: impl FnMut() -> Result<String, String>, mut sleep: impl FnMut(std::time::Duration), waits: &[std::time::Duration]) -> Result<(u32, String), (u32, String)> {
    let mut tries = 0u32;
    let mut last = String::new();
    for i in 0..=waits.len() {
        tries += 1;
        match lookup() {
            Ok(a) => return Ok((tries, a)),
            Err(e) => last = e,
        }
        if let Some(w) = waits.get(i) {
            sleep(*w);
        }
    }
    Err((tries, last))
}

#[cfg(test)]
mod tests {
    use super::*;

    // ── the lean estate: a box with one NIC (2026-10-23) ───────────────────

    /// ★ **A box laid with one NIC runs the two-NIC spec** (owner 2026-10-23, the lean estate:
    /// main and the twin sit behind the front with a utility address only; the medium is the
    /// same for every box and says `eth0:dhcp,eth1:dhcp`). The NIC the box has is configured —
    /// and, being first, it owns the default route — and the NIC it does not have is skipped,
    /// said once, never asked for in the background for ever. A spec none of whose NICs exist
    /// is kept whole (something is wrong; the boot says so as before).
    #[test]
    fn a_nic_the_box_does_not_have_is_skipped_and_the_first_present_owns_the_route() {
        let spec = NetConfig::parse_list("eth0:10.0.2.15/24:10.0.2.2,eth1:10.6.0.2/22").unwrap();
        let (kept, skipped) = present_only(spec.clone(), |i| i == "eth0");
        assert_eq!(kept.iter().map(|c| c.interface()).collect::<Vec<_>>(), ["eth0"]);
        assert_eq!(skipped, ["eth1"]);
        let (kept, skipped) = present_only(spec.clone(), |_| true);
        assert_eq!((kept, skipped.len()), (spec.clone(), 0), "a two-NIC box: unchanged");
        let (kept, skipped) = present_only(spec.clone(), |_| false);
        assert_eq!((kept, skipped.len()), (spec, 0), "no NIC at all: kept whole");
    }

    // ── the plan parser ─────────────────────────────────────────────────────

    #[test]
    fn a_full_spec_parses_into_every_field() {
        let plan = NetPlan::parse("eth0:10.0.2.15/24:10.0.2.2").expect("a legal spec");
        assert_eq!(
            plan,
            NetPlan {
                interface: "eth0".into(),
                address: Ipv4Addr::new(10, 0, 2, 15),
                prefix: 24,
                gateway: Some(Ipv4Addr::new(10, 0, 2, 2)),
            }
        );
    }

    #[test]
    fn the_gateway_is_optional_and_its_absence_is_not_an_error() {
        let plan = NetPlan::parse("eth0:192.168.5.9/16").expect("a legal spec");
        assert_eq!(plan.gateway, None);
        assert_eq!(plan.prefix, 16);
    }

    /// Measure, never assert on the parser itself: it must reject, or "parsed" means nothing.
    #[test]
    fn a_malformed_spec_is_refused_rather_than_defaulted() {
        for bad in [
            "",
            "eth0",
            "eth0:10.0.2.15",
            "eth0:10.0.2.15/33:10.0.2.2",
            "eth0:not-an-address/24",
            "eth0:10.0.2.15/24:not-a-gateway",
            "eth0:10.0.2.15/24:10.0.2.2:extra",
        ] {
            assert!(
                NetPlan::parse(bad).is_err(),
                "NetPlan::parse({bad:?}) was accepted; a parser that accepts everything \
                 configures nothing and reports success"
            );
        }
    }

    // ── the two-form the net spec parser ──────────────────────────────────────

    #[cfg(feature = "dhcp")]
    #[test]
    fn the_dhcp_form_parses_and_names_its_interface() {
        assert_eq!(
            NetConfig::parse("eth0:dhcp").expect("the declared form"),
            NetConfig::Dhcp {
                interface: "eth0".into()
            }
        );
        // Case on the keyword is operator input, not identity.
        assert_eq!(
            NetConfig::parse("ens3:DHCP").expect("case-insensitive keyword"),
            NetConfig::Dhcp {
                interface: "ens3".into()
            }
        );
    }

    #[test]
    fn the_static_form_is_byte_compatible_through_netconfig() {
        // The exact string every declaration carried before the dhcp form
        // existed must keep meaning exactly what it meant.
        assert_eq!(
            NetConfig::parse("eth0:10.0.2.15/24:10.0.2.2").expect("the historical form"),
            NetConfig::Static(NetPlan::parse("eth0:10.0.2.15/24:10.0.2.2").unwrap())
        );
    }

    /// **The other half of `dhcp`: an appliance that cannot DHCP says
    /// so, loudly, instead of booting dark.**
    ///
    /// A binary built without the DHCP client still has to READ
    /// `<NET>=eth0:dhcp` — the string is baked into media and boot
    /// entries by declarations written before anyone unticked the box. The two
    /// silent outcomes were both available and both wrong: fall through to
    /// `NetPlan::parse` and blame the operator's spelling ("`\"dhcp\"` is not
    /// <addr>/<prefix>") for a decision the BUILD made, or return "no network"
    /// and serve on an interface nobody configured. This asserts the third
    /// one: refuse, name the feature, and name the way out.
    ///
    /// Compiled only in the OFF build, which is the only build in which the
    /// refusal exists. `cargo test -p gunnar-server --no-default-features
    /// --features server,engine-znippy` is where it runs.
    ///
    /// PROVED RED 2026-08-22 by deleting the refusal and letting `:dhcp` fall
    /// through to the static parser — the exact defect above. The failure is
    /// `the refusal must NAME the missing feature ...: the net spec "eth0:dhcp":
    /// "dhcp" is not <addr>/<prefix>`, which is both a red guard and a
    /// measurement of what the silent path would have told the operator.
    #[cfg(not(feature = "dhcp"))]
    #[test]
    fn a_dhcp_spec_is_refused_by_name_when_the_client_is_not_compiled_in() {
        let err = NetConfig::parse("eth0:dhcp").expect_err(
            "a binary with no DHCP client must REFUSE eth0:dhcp — treating it as \
             'no network' boots an appliance that is unreachable and says nothing",
        );
        let text = format!("{err:#}");
        assert!(
            text.contains("`dhcp`"),
            "the refusal must NAME the missing feature, or an operator reading a \
             serial console cannot tell a build decision from a typo: {text}"
        );
        assert!(
            text.contains("eth0"),
            "the refusal must name the interface it was asked about: {text}"
        );
        assert!(
            text.contains("<addr>/<prefix>"),
            "the refusal must name the way OUT — the static spelling this build \
             does support: {text}"
        );
        // Case on the keyword is operator input, not identity: the refusal has
        // to cover the same spellings the acceptance did.
        assert!(
            NetConfig::parse("ens3:DHCP").is_err(),
            "the case-insensitive keyword must be refused too"
        );
        // ...and the STATIC form is untouched by the gate. Without this the
        // test above would pass just as well on a parser that refused
        // everything, which is a guard that cannot report red.
        assert_eq!(
            NetConfig::parse("eth0:10.0.2.15/24:10.0.2.2").expect("the static form still parses"),
            NetConfig::Static(NetPlan::parse("eth0:10.0.2.15/24:10.0.2.2").unwrap())
        );
    }

    /// Measure, never assert on the extended parser: `dhcp` must not have widened what is
    /// accepted anywhere else.
    #[test]
    fn dhcp_shaped_garbage_is_refused_rather_than_defaulted() {
        for bad in [
            "dhcp",            // no interface at all
            ":dhcp",           // an empty interface name
            "eth0:dhcp:extra", // trailing field the dhcp form does not have
            "eth0:dhcp/24",    // half static, half dhcp
            "eth0:dhc",        // a typo must not silently become static-parse noise that
                               // reads like a dhcp attempt — it errors either way
        ] {
            assert!(
                NetConfig::parse(bad).is_err(),
                "NetConfig::parse({bad:?}) was accepted"
            );
        }
    }

    // ── the golden vectors ──────────────────────────────────────────────────
    //
    // Little-endian, which is what `to_ne_bytes` produces on the only
    // architecture this appliance is built for. The tests assert the whole
    // buffer, not a length and a field: a `rtattr` that forgot its padding has
    // the right length in its header and the wrong length on the wire.

    #[test]
    fn link_up_is_thirty_two_bytes_and_sets_the_change_mask() {
        let msg = link_up_message(1, 2);
        assert_eq!(
            msg,
            vec![
                // nlmsghdr: len=32, type=RTM_NEWLINK(16), flags=REQUEST|ACK(5), seq=1, pid=0
                32, 0, 0, 0, 16, 0, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0,
                // ifinfomsg: family=0, pad=0, type=0, index=2, flags=IFF_UP, change=IFF_UP
                0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
            ],
            "the RTM_NEWLINK encoding moved"
        );
        // The bug this vector exists to catch, stated as its own assertion:
        // ifi_change is the last four bytes and a zero there is a message that
        // changes nothing and still returns success.
        assert_eq!(
            &msg[28..32],
            &IFF_UP.to_ne_bytes(),
            "ifi_change is zero, so this message would be acknowledged without bringing the \
             link up"
        );
    }

    #[test]
    fn an_address_carries_local_address_and_broadcast() {
        let msg = address_message(2, 2, Ipv4Addr::new(10, 0, 2, 15), 24);
        assert_eq!(
            msg,
            vec![
                // nlmsghdr: len=48, type=RTM_NEWADDR(20), flags=REQUEST|ACK|EXCL|CREATE(0x605)
                48, 0, 0, 0, 20, 0, 5, 6, 2, 0, 0, 0, 0, 0, 0, 0,
                // ifaddrmsg: AF_INET, /24, flags=0, scope=UNIVERSE, index=2
                2, 24, 0, 0, 2, 0, 0, 0, // IFA_LOCAL 10.0.2.15
                8, 0, 2, 0, 10, 0, 2, 15, // IFA_ADDRESS 10.0.2.15
                8, 0, 1, 0, 10, 0, 2, 15, // IFA_BROADCAST 10.0.2.255
                8, 0, 4, 0, 10, 0, 2, 255,
            ],
            "the RTM_NEWADDR encoding moved"
        );
    }

    #[test]
    fn the_broadcast_address_is_computed_from_the_prefix_not_assumed() {
        // A /16 must produce x.x.255.255, not x.x.x.255. An implementation that
        // hardcoded the last octet passes the /24 vector above and fails here.
        let msg = address_message(2, 2, Ipv4Addr::new(172, 16, 4, 9), 16);
        let tail = &msg[msg.len() - 4..];
        assert_eq!(
            tail,
            &[172, 16, 255, 255],
            "the broadcast for 172.16.4.9/16 is 172.16.255.255"
        );
    }

    #[test]
    fn a_thirty_two_prefix_gets_no_broadcast_attribute() {
        let msg = address_message(2, 2, Ipv4Addr::new(10, 0, 2, 15), 32);
        assert_eq!(
            msg.len(),
            40,
            "a /32 is a single host: a broadcast attribute there is meaningless"
        );
    }

    #[test]
    fn the_default_route_has_a_zero_destination_length() {
        let msg = default_route_message(3, 2, Ipv4Addr::new(10, 0, 2, 2));
        assert_eq!(
            msg,
            vec![
                // nlmsghdr: len=44, type=RTM_NEWROUTE(24), flags=REQUEST|ACK|EXCL|CREATE
                44, 0, 0, 0, 24, 0, 5, 6, 3, 0, 0, 0, 0, 0, 0, 0,
                // rtmsg: AF_INET, dst_len=0, src_len=0, tos=0,
                //        table=MAIN(254), proto=BOOT(3), scope=UNIVERSE(0), type=UNICAST(1)
                2, 0, 0, 0, 254, 3, 0, 1, 0, 0, 0, 0, // RTA_GATEWAY 10.0.2.2
                8, 0, 5, 0, 10, 0, 2, 2, // RTA_OIF 2
                8, 0, 4, 0, 2, 0, 0, 0,
            ],
            "the RTM_NEWROUTE encoding moved"
        );
        assert_eq!(
            msg[17], 0,
            "rtm_dst_len must be 0; that is what makes it the default route"
        );
    }

    /// **The route that reaches the front.** `10.0.0.0/8 via 10.13.4.1 dev
    /// eth1` — the exact route option 121 hands out on UpCloud's utility
    /// network, and the one whose absence left the appliance unable to ask the
    /// time. Unlike the default route this one carries `RTA_DST`, and
    /// `rtm_dst_len` is the prefix rather than zero.
    #[test]
    fn a_leased_route_carries_its_destination_and_its_prefix() {
        let msg = route_message(101, 3, Ipv4Addr::new(10, 0, 0, 0), 8, Ipv4Addr::new(10, 13, 4, 1));
        assert_eq!(msg[17], 8, "rtm_dst_len must be the prefix length");
        assert_eq!(msg[16], AF_INET);
        assert_eq!(msg[22], RT_SCOPE_UNIVERSE, "a route with a next hop is universe-scoped");
        // RTA_DST 10.0.0.0, RTA_GATEWAY 10.13.4.1, RTA_OIF 3 — in that order.
        assert_eq!(
            msg[28..],
            [8, 0, 1, 0, 10, 0, 0, 0, 8, 0, 5, 0, 10, 13, 4, 1, 8, 0, 4, 0, 3, 0, 0, 0],
            "the leased-route encoding moved"
        );
        // …and the default route is still the same bytes it always was, which
        // is what makes sharing one encoder safe.
        assert_eq!(
            default_route_message(3, 2, Ipv4Addr::new(10, 0, 2, 2)),
            route_message(3, 2, Ipv4Addr::UNSPECIFIED, 0, Ipv4Addr::new(10, 0, 2, 2))
        );
    }

    /// An on-link route (`via 0.0.0.0`, which RFC 3442 permits) carries NO
    /// `RTA_GATEWAY` and is link-scoped. `via 0.0.0.0` is not a next hop.
    #[test]
    fn an_on_link_leased_route_names_no_gateway() {
        let msg = route_message(102, 3, Ipv4Addr::new(192, 168, 9, 0), 24, Ipv4Addr::UNSPECIFIED);
        assert_eq!(msg[22], RT_SCOPE_LINK);
        assert_eq!(
            msg[28..],
            [8, 0, 1, 0, 192, 168, 9, 0, 8, 0, 4, 0, 3, 0, 0, 0],
            "an on-link route must carry RTA_DST and RTA_OIF and nothing else"
        );
    }

    /// **A guard that cannot fire, found by trying to make it.**
    ///
    /// The three golden vectors above were asserted against an `rtattr` encoder
    /// deliberately broken to put the *padded* length in `rta_len` — the classic
    /// netlink mistake — and all three stayed **green**. Every attribute gunnar
    /// sends carries a four-byte payload, so `rta_len` is 8, `RTA_ALIGN(8)` is
    /// 8, and the two rules are indistinguishable in every message this file
    /// produces. The vectors are still worth having; they simply cannot see
    /// this class of bug, and believing otherwise is how a rule table ends up
    /// trusted for something it never checked.
    ///
    /// So the rule is asserted where it is actually visible: on a payload whose
    /// length is not a multiple of four.
    #[test]
    fn rta_len_counts_the_payload_and_not_the_padding() {
        let attr = rtattr(IFA_LOCAL, &[1, 2, 3]);
        assert_eq!(
            attr,
            vec![7, 0, 2, 0, 1, 2, 3, 0],
            "rta_len must be 7 (4 header + 3 payload) in an 8-byte padded buffer"
        );
        assert_eq!(
            u16::from_ne_bytes([attr[0], attr[1]]),
            7,
            "rta_len is the padded length, so the kernel would read one byte of padding as data"
        );
        assert_eq!(
            attr.len(),
            8,
            "the buffer itself must be padded to RTA_ALIGN"
        );
    }

    #[test]
    fn getlink_is_a_plain_request_with_the_index_and_no_ack_flag() {
        let msg = get_link_message(1, 2);
        assert_eq!(
            msg,
            vec![
                // nlmsghdr: len=32, type=RTM_GETLINK(18), flags=REQUEST(1), seq=1, pid=0
                32, 0, 0, 0, 18, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
                // ifinfomsg: family=0, pad=0, type=0, index=2, flags=0, change=0
                0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            ],
            "the RTM_GETLINK encoding moved"
        );
        // NLM_F_ACK here would make the kernel send an error message AFTER the
        // data, and a reader that stops at the first message would still work —
        // until a slow day reorders the reads. The flag byte is the guard.
        assert_eq!(msg[6], 1, "flags must be NLM_F_REQUEST alone");
    }

    /// A synthetic `RTM_NEWLINK` answer with the attribute walk's hard case:
    /// the MAC payload is 6 bytes, so `rta_len` is 10 and the step to the next
    /// attribute is 12 — the first message in this file where `rta_len` and
    /// `RTA_ALIGN(rta_len)` actually differ (see
    /// `rta_len_counts_the_payload_and_not_the_padding`).
    fn getlink_reply(attrs: &[u8]) -> Vec<u8> {
        let mut b = Vec::new();
        let len = 32 + attrs.len();
        b.extend_from_slice(&(len as u32).to_ne_bytes());
        b.extend_from_slice(&RTM_NEWLINK.to_ne_bytes());
        b.extend_from_slice(&0u16.to_ne_bytes()); // flags
        b.extend_from_slice(&1u32.to_ne_bytes()); // seq
        b.extend_from_slice(&0u32.to_ne_bytes()); // pid
        b.extend_from_slice(&[0u8; 16]); // ifinfomsg
        b.extend_from_slice(attrs);
        b
    }

    // ── the net spec as a LIST ───────────────────────────────────────────────

    /// The historical single-entry spelling must parse to exactly one plan.
    /// Every medium in the constellation carries it; a list parser that
    /// changed what one entry means would change every one of them at once.
    // Spelled with `:dhcp` entries: a build without the client refuses them by name.
    #[cfg(feature = "dhcp")]
    #[test]
    fn one_entry_still_means_exactly_what_it_always_did() {
        let list = NetConfig::parse_list("eth0:dhcp").expect("the declared form");
        assert_eq!(list.len(), 1);
        assert_eq!(list[0], NetConfig::parse("eth0:dhcp").unwrap());

        let stat = NetConfig::parse_list("eth0:10.0.2.15/24:10.0.2.2").expect("the static form");
        assert_eq!(stat.len(), 1);
        assert_eq!(stat[0], NetConfig::parse("eth0:10.0.2.15/24:10.0.2.2").unwrap());
    }

    /// The cloud shape: a public NIC and a utility NIC, in that order.
    // Spelled with `:dhcp` entries: a build without the client refuses them by name.
    #[cfg(feature = "dhcp")]
    #[test]
    fn two_entries_parse_in_order_and_name_two_interfaces() {
        let list = NetConfig::parse_list("eth0:dhcp,eth1:dhcp").expect("the cloud form");
        assert_eq!(list.len(), 2);
        assert_eq!(list[0].interface(), "eth0");
        assert_eq!(
            list[1].interface(),
            "eth1",
            "order is load-bearing: the FIRST entry owns the default route"
        );
    }

    /// Whitespace around an entry is an operator's spacing, not a new spec.
    // Spelled with `:dhcp` entries: a build without the client refuses them by name.
    #[cfg(feature = "dhcp")]
    #[test]
    fn entries_may_be_spaced_and_may_mix_static_with_dhcp() {
        let list = NetConfig::parse_list("eth0:dhcp, eth1:10.20.6.152/22")
            .expect("a mixed list");
        assert_eq!(list.len(), 2);
        assert_eq!(list[1], NetConfig::parse("eth1:10.20.6.152/22").unwrap());
    }

    /// The refusals. A list parser that shrugged at these would boot an
    /// appliance with half a network and nothing in the log naming which half.
    #[test]
    fn a_malformed_list_is_refused_rather_than_partially_applied() {
        for bad in [
            "eth0:dhcp,",           // trailing comma — an empty entry
            ",eth0:dhcp",           // leading comma
            "eth0:dhcp,,eth1:dhcp", // a hole in the middle
            "eth0:dhcp,eth0:dhcp",  // TWO plans for one interface
            "eth0:dhcp,eth1:nonsense",
            "eth0:dhcp,eth1",
        ] {
            assert!(
                NetConfig::parse_list(bad).is_err(),
                "net spec {bad:?} was ACCEPTED"
            );
        }
    }

    /// One interface named twice is two answers to one question. The refusal
    /// must say which name, because a cmdline is read off a serial console.
    #[test]
    fn a_repeated_interface_is_named_in_the_refusal() {
        let err = NetConfig::parse_list("eth1:dhcp,eth1:10.0.0.1/24")
            .expect_err("a duplicate interface must be refused");
        assert!(
            err.to_string().contains("eth1"),
            "the refusal must name the interface: {err:#}"
        );
    }

    // ── the address dump behind `--grpc-bind <iface>:<port>` ───────────────

    /// One `RTM_NEWADDR` row as the kernel lays it out: 16-byte nlmsghdr, then
    /// `struct ifaddrmsg`, then attributes.
    fn addr_row(ifindex: u32, family: u8, attrs: &[u8]) -> Vec<u8> {
        let mut body = Vec::new();
        body.push(family);
        body.push(24); // ifa_prefixlen
        body.push(0); // ifa_flags
        body.push(0); // ifa_scope
        body.extend_from_slice(&ifindex.to_ne_bytes());
        body.extend_from_slice(attrs);
        netlink_message(RTM_NEWADDR, 0, 0, &body)
    }

    fn done_row() -> Vec<u8> {
        netlink_message(NLMSG_DONE, 0, 0, &0u32.to_ne_bytes())
    }

    #[test]
    fn the_dump_reader_finds_the_row_for_the_index_it_was_asked_about() {
        let mut dump = Vec::new();
        dump.extend_from_slice(&addr_row(1, AF_INET, &rtattr(IFA_LOCAL, &[127, 0, 0, 1])));
        dump.extend_from_slice(&addr_row(2, AF_INET, &rtattr(IFA_LOCAL, &[212, 147, 225, 55])));
        dump.extend_from_slice(&addr_row(3, AF_INET, &rtattr(IFA_LOCAL, &[10, 13, 6, 152])));
        dump.extend_from_slice(&done_row());
        // Every index, not just the interesting one: a reader that returned the
        // FIRST address in the table would pass a test that only asked for
        // index 1, and would bind the public interface on the appliance.
        assert_eq!(ipv4_from_addr_dump(&dump, 1), Some(Ipv4Addr::new(127, 0, 0, 1)));
        assert_eq!(
            ipv4_from_addr_dump(&dump, 2),
            Some(Ipv4Addr::new(212, 147, 225, 55))
        );
        assert_eq!(
            ipv4_from_addr_dump(&dump, 3),
            Some(Ipv4Addr::new(10, 13, 6, 152)),
            "index 3 is the LAST row: a reader that stopped early would miss the utility NIC"
        );
    }

    /// An interface that is in the dump with no address of its own must read
    /// as `None` — the answer that makes `BindSpec::resolve_with` refuse — and
    /// never as the address of some other interface.
    #[test]
    fn an_index_with_no_row_reads_as_none_and_not_as_a_neighbour() {
        let mut dump = Vec::new();
        dump.extend_from_slice(&addr_row(1, AF_INET, &rtattr(IFA_LOCAL, &[127, 0, 0, 1])));
        dump.extend_from_slice(&done_row());
        assert_eq!(ipv4_from_addr_dump(&dump, 2), None);
    }

    /// `IFA_ADDRESS` is the PEER on a point-to-point link. A reader that took
    /// whichever attribute came first would bind the far end of a tunnel, so
    /// `IFA_LOCAL` wins even when it is second in the attribute list.
    #[test]
    fn ifa_local_wins_over_ifa_address_whichever_order_they_arrive_in() {
        let mut attrs = Vec::new();
        attrs.extend_from_slice(&rtattr(IFA_ADDRESS, &[10, 9, 9, 9])); // the peer
        attrs.extend_from_slice(&rtattr(IFA_LOCAL, &[10, 13, 6, 152])); // this end
        let mut dump = addr_row(7, AF_INET, &attrs);
        dump.extend_from_slice(&done_row());
        assert_eq!(
            ipv4_from_addr_dump(&dump, 7),
            Some(Ipv4Addr::new(10, 13, 6, 152))
        );
        // And with only the peer attribute present, it is still better than
        // nothing — a broadcast link sends both with the same value.
        let mut only_peer = addr_row(7, AF_INET, &rtattr(IFA_ADDRESS, &[10, 9, 9, 9]));
        only_peer.extend_from_slice(&done_row());
        assert_eq!(
            ipv4_from_addr_dump(&only_peer, 7),
            Some(Ipv4Addr::new(10, 9, 9, 9))
        );
    }

    /// An IPv6 row for the same index must not be read as four bytes of IPv4.
    #[test]
    fn a_non_inet_family_row_is_skipped_rather_than_reinterpreted() {
        const AF_INET6: u8 = 10;
        let mut dump = addr_row(4, AF_INET6, &rtattr(IFA_LOCAL, &[0u8; 16]));
        dump.extend_from_slice(&done_row());
        assert_eq!(ipv4_from_addr_dump(&dump, 4), None);
    }

    /// **The live half.** Every Linux host has a loopback interface with `127.0.0.1` on it,
    /// so the whole path — `interface_index`, the netlink socket, the dump, the
    /// parse — has one answer that is known in advance on every Linux box this
    /// ever runs on. Without this the parser above is proven only against bytes
    /// this file wrote itself.
    #[test]
    fn interface_ipv4_reads_the_real_loopback_address_off_a_real_kernel() {
        let got = interface_ipv4("lo").expect("querying lo over netlink");
        assert_eq!(
            got,
            Some(Ipv4Addr::LOCALHOST),
            "lo must read as 127.0.0.1 over netlink; got {got:?}"
        );
    }

    /// The live test above is satisfied by a reader that returns the FIRST
    /// address in the table, because `lo` is index 1 and is first. This one is
    /// not: it asks a NIC that is not `lo` and refuses `127.0.0.1` as its
    /// answer. Both were RED together under a mutation that dropped the
    /// `index == ifindex` filter; with only the `lo` test, that mutation was
    /// green on a real kernel.
    ///
    /// Skipped rather than failed where `/sys/class/net` offers no second
    /// interface — a container with only loopback is not a defect in this code.
    #[test]
    fn interface_ipv4_does_not_answer_a_second_nic_with_the_loopback_address() {
        let Ok(entries) = std::fs::read_dir("/sys/class/net") else {
            eprintln!("no /sys/class/net on this host; nothing to ask");
            return;
        };
        let mut asked = 0usize;
        for entry in entries.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();
            if name == "lo" {
                continue;
            }
            let Ok(answer) = interface_ipv4(&name) else {
                continue;
            };
            asked += 1;
            assert_ne!(
                answer,
                Some(Ipv4Addr::LOCALHOST),
                "{name} was answered with the LOOPBACK address — the dump reader is not \
                 filtering by interface index"
            );
        }
        if asked == 0 {
            eprintln!("only loopback on this host; nothing to ask");
        }
    }

    /// And an interface that does not exist is an ERROR, not `Ok(None)` — the
    /// two are different sentences in the refusal and a caller that conflated
    /// them would tell an operator to check DHCP for a typo'd NIC name.
    #[test]
    fn a_nonexistent_interface_is_an_error_and_not_an_empty_answer() {
        let err = interface_ipv4("zz-no-such-nic").expect_err("no such interface");
        assert!(
            err.to_string().contains("zz-no-such-nic"),
            "the error must name the interface: {err:#}"
        );
    }

    #[test]
    fn the_dump_request_asks_for_inet_and_sets_the_dump_flags() {
        let msg = get_addr_dump_message(1);
        assert_eq!(u16::from_ne_bytes(msg[4..6].try_into().unwrap()), RTM_GETADDR);
        let flags = u16::from_ne_bytes(msg[6..8].try_into().unwrap());
        assert_eq!(
            flags,
            NLM_F_REQUEST | NLM_F_DUMP,
            "without NLM_F_DUMP the kernel answers with one row, or nothing"
        );
        assert_eq!(msg[NLMSGHDR_LEN], AF_INET, "the dump must be filtered to IPv4");
    }

    #[test]
    fn the_mac_is_read_out_of_a_getlink_answer_across_padded_attributes() {
        // IFLA_IFNAME("eth0\0") first — 5-byte payload, rta_len 9, step 12 —
        // so reaching IFLA_ADDRESS at all proves the walk aligns correctly.
        let mut attrs = Vec::new();
        attrs.extend_from_slice(&rtattr(3, b"eth0\0")); // IFLA_IFNAME = 3
        attrs.extend_from_slice(&rtattr(IFLA_ADDRESS, &[0x52, 0x54, 0, 0x12, 0x34, 0x56]));
        let mac = mac_from_getlink_reply(&getlink_reply(&attrs)).expect("a well-formed answer");
        assert_eq!(mac, [0x52, 0x54, 0, 0x12, 0x34, 0x56]);
    }

    /// Measure, never assert: every way the answer can be wrong must be refused, or
    /// `interface_mac` would hand the DHCP client a `chaddr` of garbage and
    /// every reply-matching gate downstream would filter on that garbage.
    #[test]
    fn getlink_answers_that_carry_no_mac_are_refused() {
        // No IFLA_ADDRESS at all.
        let no_addr = getlink_reply(&rtattr(3, b"eth0\0"));
        assert!(mac_from_getlink_reply(&no_addr).is_err());
        // An IFLA_ADDRESS that is not 6 bytes (e.g. a tunnel device).
        let short = getlink_reply(&rtattr(IFLA_ADDRESS, &[1, 2, 3, 4]));
        assert!(mac_from_getlink_reply(&short).is_err());
        // The kernel said error: errno must surface, not parse as attributes.
        let mut err = Vec::new();
        err.extend_from_slice(&20u32.to_ne_bytes());
        err.extend_from_slice(&NLMSG_ERROR.to_ne_bytes());
        err.extend_from_slice(&[0u8; 10]); // flags, seq, pid
        err.extend_from_slice(&(-19i32).to_ne_bytes()); // -ENODEV
        let e = mac_from_getlink_reply(&err).expect_err("an NLMSG_ERROR is not a link");
        assert!(
            format!("{e:#}").contains("errno 19"),
            "the kernel's errno must be named, got: {e:#}"
        );
        // An NLMSG_ERROR cut off before its errno: must be an error, not a
        // panic. This case found the panic — the fixture above was first
        // written two bytes short and the parser indexed past the buffer.
        assert!(
            mac_from_getlink_reply(&err[..18]).is_err(),
            "a truncated NLMSG_ERROR must be refused"
        );
        // Truncated garbage.
        assert!(mac_from_getlink_reply(&[0, 1, 2]).is_err());
    }

    /// Every message's `nlmsg_len` must agree with the buffer it is in. A
    /// mismatch is accepted by `send` and then either truncated or rejected by
    /// the kernel with `EINVAL`, which is a runtime failure for a compile-time
    /// mistake.
    #[test]
    fn every_message_declares_its_own_length() {
        for (name, msg) in [
            ("link", link_up_message(1, 2)),
            (
                "addr",
                address_message(2, 2, Ipv4Addr::new(10, 0, 2, 15), 24),
            ),
            (
                "route",
                default_route_message(3, 2, Ipv4Addr::new(10, 0, 2, 2)),
            ),
        ] {
            let declared = u32::from_ne_bytes([msg[0], msg[1], msg[2], msg[3]]) as usize;
            assert_eq!(
                declared,
                msg.len(),
                "{name}: nlmsg_len says {declared} and the buffer is {} bytes",
                msg.len()
            );
        }
    }

}

#[cfg(test)]
mod dhcp_retry_tests {
    use super::*;
    use std::time::Duration;

    /// ★ **A DHCP that failed at boot is asked again, forever, with backoff** (lane D12,
    /// test3-holger-rs r6: the front's eth1 got no lease on its first boot — UpCloud's DHCP
    /// is slow then — and nothing ever asked again, so the boxes' WireGuard to the front's
    /// utility address went unanswered for the whole life of the boot). Gunnar's law: 2 s
    /// doubling to 30 s, retries said at powers of two, the lease said once.
    #[test]
    fn a_failed_boot_dhcp_is_asked_again_with_backoff_until_a_lease_comes() {
        let mut left = 6;
        let mut slept = Vec::new();
        let mut said = Vec::new();
        let (lease, tries) = keep_asking(
            || {
                if left == 0 {
                    Ok("10.13.11.61/22")
                } else {
                    left -= 1;
                    Err("no DHCPOFFER in 3 s")
                }
            },
            |d| slept.push(d.as_secs()),
            |warn, line| said.push((warn, line)),
        );
        assert_eq!((lease, tries), ("10.13.11.61/22", 7));
        assert_eq!(slept, vec![2, 4, 8, 16, 30, 30, 30], "a wait before every ask: 2 s doubling to 30 s");
        assert!(said.iter().all(|(warn, _)| !warn), "no WARN from the helper (the boot's failure was the one): {said:?}");
        let retries = said.iter().filter(|(_, l)| l.contains("still no lease")).count();
        assert_eq!(retries, 3, "failures 1, 2 and 4 said, not 3, 5, 6: {said:?}");
        assert!(said.last().is_some_and(|(_, l)| l.contains("lease taken after 7")), "{said:?}");
    }

    /// ★ **A NIC whose first DHCP asks time out is pending until its lease comes, then not**
    /// (lane D12): the background retry marks the interface [`dhcp_pending`] (a step that
    /// needs its address waits), hands the lease over once it is taken, and clears the mark.
    #[test]
    fn a_nic_whose_first_asks_time_out_is_pending_until_its_lease_is_taken() {
        let (go, gate) = std::sync::mpsc::channel::<()>();
        let (tx, rx) = std::sync::mpsc::channel();
        let mut left = 3;
        let h = retry_in_background(
            "d12test-eth1",
            move || {
                gate.recv().ok();
                if left == 0 {
                    Ok("10.13.11.61/22")
                } else {
                    left -= 1;
                    Err("no DHCPOFFER in 3 s")
                }
            },
            |_| {},
            move |lease, tries| tx.send((lease, tries)).unwrap(),
        )
        .expect("the retry thread starts");
        assert!(dhcp_pending("d12test-eth1"), "pending while the asks time out");
        for _ in 0..3 {
            go.send(()).unwrap();
        }
        std::thread::sleep(Duration::from_millis(50));
        assert!(dhcp_pending("d12test-eth1"), "three timeouts: still pending, still asking");
        go.send(()).unwrap();
        assert_eq!(rx.recv_timeout(Duration::from_secs(5)).unwrap(), ("10.13.11.61/22", 4));
        h.join().unwrap();
        assert!(!dhcp_pending("d12test-eth1"), "the lease is taken: no longer pending");
        assert!(!dhcp_pending("eth9"), "an interface never asked for is not pending");
    }
}

#[cfg(all(test, feature = "dhcp"))]
mod resolver_probe_tests {
    use super::*;
    use std::time::Duration;

    /// ★ **Row 29: a resolver that misses its FIRST query is proven by a later one**
    /// (Gunnar cf05cce1; release-3 test2 on UpCloud: the twin logged `ERROR resolver NOT
    /// proven … Try again` 2 s after DHCP published the provider's resolvers, the front
    /// proving the same ones in 1 ms). The probe asks again for about 30 s; a failed try
    /// is not an ERROR; only the whole window failing is.
    #[test]
    fn a_resolver_that_fails_once_then_answers_is_proven_on_the_second_try() {
        let mut calls = 0u32;
        let mut slept = Duration::ZERO;
        let r = prove_with(
            || {
                calls += 1;
                if calls == 1 { Err("failed to lookup address information: Try again".into()) } else { Ok("140.82.121.3".into()) }
            },
            |d| slept += d,
            RESOLVER_RETRY_WAITS,
        );
        assert_eq!(r, Ok((2, "140.82.121.3".to_string())));
        assert_eq!(slept, RESOLVER_RETRY_WAITS[0], "one wait between the two tries");
    }

    #[test]
    fn a_resolver_that_never_answers_is_not_proven_after_the_whole_window() {
        let mut slept = Duration::ZERO;
        let r = prove_with(|| Err("Temporary failure in name resolution".into()), |d| slept += d, RESOLVER_RETRY_WAITS);
        assert_eq!(r, Err((RESOLVER_RETRY_WAITS.len() as u32 + 1, "Temporary failure in name resolution".to_string())));
        let window: Duration = RESOLVER_RETRY_WAITS.iter().sum();
        assert!(window >= Duration::from_secs(25) && window <= Duration::from_secs(35), "about 30 s: {window:?}");
        assert_eq!(slept, window);
    }
}

#[cfg(test)]
mod ack_tests {
    use super::*;

    /// ★ **A live update's re-exec finds its addresses in place** (Gunnar; the dwarf mock
    /// 2026-10-13: every DHCP apply after the re-exec was refused `errno 17 (File exists)`).
    /// EEXIST is "already there" where it is tolerated; every other errno stays a refusal.
    #[test]
    fn an_address_already_there_is_not_a_refusal() {
        assert_eq!(ack_verdict(0, false), Ok(Ack::Applied));
        assert_eq!(ack_verdict(-17, true), Ok(Ack::AlreadyThere));
        assert_eq!(ack_verdict(-17, false), Err(17), "where it is not tolerated, EEXIST stays a refusal");
        assert_eq!(ack_verdict(-1, true), Err(1));
        let _: fn(&rustix::fd::OwnedFd, &[u8], &str) -> Result<Ack> = request_or_there;
        // edda's apply tolerates EEXIST for the address (kept) and REPLACES the default
        // route (Gunnar's kept it as it was: edda's stands, the plan's gateway wins).
        let src = include_str!("net.rs");
        let apply = &src[src.find("pub fn apply(plan: &NetPlan)").unwrap()..];
        let apply = &apply[..apply.find("\n}\n").unwrap()];
        assert_eq!(apply.matches("request_or_there(").count(), 2, "the address and the route tolerate EEXIST: {apply}");
    }
}

#[cfg(all(test, feature = "dhcp"))]
mod late_lease_tests {
    use super::*;

    fn session(interface: &str, owns_default_route: bool) -> DhcpSession {
        DhcpSession {
            interface: interface.into(),
            mac: [0x02, 0, 0, 0xaa, 0xbb, 0xcc],
            lease: crate::dhcp::Lease {
                address: Ipv4Addr::new(10, 0, 2, 15),
                prefix: 24,
                gateway: Some(Ipv4Addr::new(10, 0, 2, 2)),
                server: Ipv4Addr::new(10, 0, 2, 2),
                lease_secs: 3600,
                t1_secs: None,
                t2_secs: None,
                host_name: None,
                dns: vec![Ipv4Addr::new(10, 0, 2, 3)],
                routes: Vec::new(),
            },
            owns_default_route,
        }
    }

    static SAID: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
    fn keep(line: &str) {
        SAID.lock().unwrap().push(line.to_string());
    }

    /// ★ **A late lease is said the way a boot lease is said** (lane G6, rickard-81 on
    /// Gunnar 7e33c0f7): D12's background retry applied the lease and said it at INFO
    /// only, so the boot narration — the installer's one window into a box with no
    /// console — never heard that the network came up after all. The late line is the
    /// boot's line, word for word, with the retry's count after it.
    #[test]
    fn a_late_lease_is_narrated_with_the_boot_leases_own_line() {
        let s = session("eth0", true);
        assert_eq!(lease_note(&s), "network up: eth0 10.0.2.15/24 gw Some(10.0.2.2) via DHCP from 10.0.2.2 (lease 3600s)");
        let late = LateLease { narrate: keep, t1_override_var: None };
        let landing = late_landing(&s, 7, "holger-front", Some("crates.io"), &late, |_| None);
        assert!(landing.note.starts_with(&lease_note(&s)), "{}", landing.note);
        assert!(landing.note.contains("after the boot's failure") && landing.note.contains("7 asks"), "{}", landing.note);
        (late.narrate)(&landing.note);
        assert_eq!(SAID.lock().unwrap().last(), Some(&landing.note));
        // The boot's own arm says the same line (one wording, not two).
        let src = include_str!("net.rs");
        let arm = &src[src.find("NetConfig::Dhcp { interface } => match configure_via_dhcp").unwrap()..];
        let arm = &arm[..arm.find("Err(err) =>").unwrap()];
        assert!(arm.contains("(how.narrate)(&lease_note(&session))"), "the boot lease is said by lease_note: {arm}");
        let retry = &src[src.find("fn retry_dhcp_in_background").unwrap()..];
        let retry = &retry[..retry.find("\n}\n").unwrap()];
        assert!(retry.contains("(late.narrate)(&landing.note)"), "the late lease is said: {retry}");
    }

    /// ★ **The late lease's renewal honours the T1 test seam** (lane G6): the boot's
    /// sessions are renewed with `dhcp_t1_override(<product>_DHCP_T1_OVERRIDE_SECS)`, the
    /// late one was renewed with `None` — a test ISO forcing T1 to 30 s saw the late NIC
    /// renew on the server's clock instead. The seam's variable is the product's.
    #[test]
    fn a_late_lease_is_renewed_with_the_products_t1_seam() {
        let s = session("eth1", false);
        let late = LateLease { narrate: keep, t1_override_var: Some("G6_TEST_T1") };
        let mut asked = Vec::new();
        let landing = late_landing(&s, 1, "gunnar", None, &late, |var| {
            asked.push(var.to_string());
            (var == "G6_TEST_T1").then_some(30)
        });
        assert_eq!(asked, ["G6_TEST_T1"], "the product's variable is read");
        assert_eq!(landing.t1_override, Some(30), "and its value renews the late lease");
        let quiet = late_landing(&s, 1, "gunnar", None, &LateLease::QUIET, |_| panic!("no seam declared: nothing read"));
        assert_eq!(quiet.t1_override, None);
        let src = include_str!("net.rs");
        let retry = &src[src.find("fn retry_dhcp_in_background").unwrap()..];
        let retry = &retry[..retry.find("\n}\n").unwrap()];
        assert!(retry.contains("dhcp_renewal(session, landing.t1_override)"), "the renewal takes the seam: {retry}");
    }

    /// ★ **The late lease's resolver is proven once it lands** (lane G6): the boot's lease
    /// proves the published resolver (`prove_with`, about 30 s); the late one published the
    /// file and proved nothing. Only the NIC that owns the default route publishes the
    /// resolver, so only that one proves it; it is named by the product, as at boot.
    #[test]
    fn a_late_lease_proves_its_resolver_when_it_owns_the_default_route() {
        let late = LateLease { narrate: keep, t1_override_var: None };
        let main = late_landing(&session("eth0", true), 3, "gunnar", Some("github.com"), &late, |_| None);
        assert_eq!(main.resolver_probe.as_deref(), Some("github.com"));
        assert_eq!(main.hostname.as_deref(), Some("gunnar-aabbcc"), "the box is named as at boot");
        let utility = late_landing(&session("eth1", false), 3, "gunnar", Some("github.com"), &late, |_| None);
        assert_eq!((utility.resolver_probe, utility.hostname), (None, None), "a utility NIC publishes no resolver and names nothing");
        let none = late_landing(&session("eth0", true), 3, "gunnar", None, &late, |_| None);
        assert_eq!(none.resolver_probe, None, "no probe asked: none run");
        let src = include_str!("net.rs");
        let retry = &src[src.find("fn retry_dhcp_in_background").unwrap()..];
        let retry = &retry[..retry.find("\n}\n").unwrap()];
        assert!(retry.contains("prove_resolver(name)"), "the proof runs: {retry}");
    }
}