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

nordisk/holger

server/lib/src/tunnel_addr.rs

//! **Estate links over the WireGuard tunnel only** (lane E1, 2026-10-19; Gunnar's
//! `tunnel_addr` PATTERN, Holger's own code).
//!
//! An estate box — one whose pairing seed names a WireGuard tunnel — is
//! WireGuard-only: every box-to-box link (the front to main, main to the twin,
//! the twin's certification and every CA or credential byte) rides `wg0`'s
//! overlay. The utility network carries only the installer's own link and the
//! tunnel itself. The ONE box's door (the fleet pull channel, the shop) is the
//! one link that leaves the tunnel, and it is a DIAL out to a public name, never
//! a box-to-box address — this module has no say over it.
//!
//! The posture is set once at start by whoever raised the tunnel (holger-server's
//! `paired` lane: the seed's tunnel kind, and the overlay address it was fed) and
//! read by:
//!
//! * the boot, which binds the control plane, the HTTP door and the replication
//!   door on the overlay address ([`estate_bind`]) and REFUSES TO START on a
//!   bind it cannot keep on the tunnel;
//! * the twin's follow and the recharge, which dial only a tunnel address
//!   ([`estate_dial`]);
//! * the doors that move CA material (`TwinCsr`, `IssueTwinCertificate`,
//!   `InstallTwinCertificate`, `BecomeTwin`, `Recharge`, the sealed slot), which
//!   refuse a caller that did not come over the tunnel ([`estate_peer`]).
//!
//! Without an estate posture (a standalone appliance, a test rig) every rule here
//! answers "as before".

use crate::lock::RwLockOrRecover as _;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::RwLock;

/// The refusal's frozen code (`holger-twin-17`).
pub const NOT_WG: &str = holger_errcode::TWIN_ESTATE_NOT_WG.code;

/// **This box's estate posture**: WireGuard-only, the overlay network, and this
/// box's own address on it once the tunnel is fed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Estate {
    /// The overlay network (`nordisk-pairing`'s `OVERLAY_NET`).
    pub net: (Ipv4Addr, u8),
    /// This box's overlay address (wg0's), `None` until fed.
    pub overlay: Option<Ipv4Addr>,
    /// `(utility, overlay)` of every fed box: an address the installer or the ONE box
    /// names by its UTILITY address is dialled at the same box's overlay address.
    pub pairs: Vec<(Ipv4Addr, Ipv4Addr)>,
}

impl Estate {
    /// Is `ip` on the tunnel?
    pub fn in_tunnel(&self, ip: IpAddr) -> bool {
        let IpAddr::V4(ip) = ip else { return false };
        let (net, bits) = self.net;
        let mask = if bits == 0 {
            0
        } else {
            u32::MAX << (32 - u32::from(bits.min(32)))
        };
        u32::from(ip) & mask == u32::from(net) & mask
    }

    /// Pure: **where a door binds under the posture.** An unspecified host
    /// (`0.0.0.0:<port>`) moves to the overlay address; the overlay address
    /// and loopback stay; anything else — the utility address, a public one —
    /// is refused by name, and so is an unspecified host before the overlay is
    /// known.
    pub fn bind(&self, what: &str, at: &str) -> Result<String, String> {
        let sa: SocketAddr = at
            .trim()
            .parse()
            .map_err(|e| refusal(format!("{what} `{at}` is not host:port ({e})")))?;
        if sa.ip().is_loopback() || self.in_tunnel(sa.ip()) {
            return Ok(sa.to_string());
        }
        if sa.ip().is_unspecified() {
            return match self.overlay {
                Some(o) => Ok(SocketAddr::new(IpAddr::V4(o), sa.port()).to_string()),
                None => Err(refusal(format!("{what} `{at}` would answer on every address, and this estate box has no tunnel address yet"))),
            };
        }
        Err(refusal(format!("{what} `{at}` is not on the estate's tunnel ({}/{}): an estate box binds its estate doors on wg0 only", self.net.0, self.net.1)))
    }

    /// Pure: **may this box dial `endpoint` as an estate link?** A tunnel or
    /// loopback host only (`http://h:p`, `h:p`, a bare host).
    pub fn dial(&self, what: &str, endpoint: &str) -> Result<(), String> {
        match host_ip(endpoint) {
            Some(ip) if ip.is_loopback() || self.in_tunnel(ip) => Ok(()),
            Some(_) => Err(refusal(format!("{what} `{endpoint}` is not on the estate's tunnel ({}/{}): an estate link never leaves wg0", self.net.0, self.net.1))),
            None => Err(refusal(format!("{what} `{endpoint}` names no tunnel address: an estate link dials wg0 addresses only"))),
        }
    }

    /// Pure: **a fed `host:port` (or `http://host:port`) of a box, over the tunnel**
    /// (Gunnar's `over_the_tunnel`): a utility address a fed box holds becomes its
    /// overlay address, then the estate dial rule holds it to the tunnel.
    pub fn route(&self, what: &str, at: &str) -> Result<String, String> {
        let (scheme, rest) = match at.trim().split_once("://") {
            Some((s, r)) => (format!("{s}://"), r.to_string()),
            None => (String::new(), at.trim().to_string()),
        };
        let mapped = match rest.parse::<std::net::SocketAddrV4>() {
            Ok(sa) => match self.pairs.iter().find(|(u, _)| u == sa.ip()) {
                Some((_, o)) => format!("{scheme}{}", std::net::SocketAddrV4::new(*o, sa.port())),
                None => at.trim().to_string(),
            },
            Err(_) => at.trim().to_string(),
        };
        self.dial(what, &mapped)?;
        Ok(mapped)
    }

    /// Pure: **did this caller come over the tunnel?** Loopback (the box
    /// itself) or a tunnel address; an unknown peer is refused too.
    pub fn peer(&self, what: &str, remote: Option<SocketAddr>) -> Result<(), String> {
        match remote {
            Some(r) if r.ip().is_loopback() || self.in_tunnel(r.ip()) => Ok(()),
            Some(r) => Err(refusal(format!("{what} from {r} came over the utility network, not the estate's tunnel: CA and credential material moves over wg0 only"))),
            None => Err(refusal(format!("{what} from an unknown peer: CA and credential material moves over wg0 only"))),
        }
    }
}

fn refusal(why: String) -> String {
    format!("REFUSED {}", holger_errcode::TWIN_ESTATE_NOT_WG.tag(why))
}

/// The host of `endpoint` as an IP, when it is one.
fn host_ip(endpoint: &str) -> Option<IpAddr> {
    let rest = endpoint
        .trim()
        .split_once("://")
        .map(|(_, r)| r)
        .unwrap_or(endpoint.trim());
    let hostport = rest.split('/').next().unwrap_or(rest);
    if let Ok(sa) = hostport.parse::<SocketAddr>() {
        return Some(sa.ip());
    }
    let host = hostport
        .rsplit_once(':')
        .map(|(h, _)| h)
        .unwrap_or(hostport)
        .trim_matches(['[', ']']);
    host.parse().ok()
}

static POSTURE: RwLock<Option<Estate>> = RwLock::new(None);

/// [`Estate::route`] under the process posture; as given without one.
pub fn estate_route(what: &str, at: &str) -> Result<String, String> {
    match estate() {
        Some(e) => e.route(what, at),
        None => Ok(at.to_string()),
    }
}

/// **Set the posture** (at start): `Some` makes this box WireGuard-only.
pub fn set(estate: Option<Estate>) {
    *POSTURE.write_or_recover() = estate;
}

/// The posture now: `None` = not an estate box.
pub fn estate() -> Option<Estate> {
    POSTURE.read_or_recover().clone()
}

/// Is this box WireGuard-only?
pub fn wg_only() -> bool {
    estate().is_some()
}

/// Pure: **is a box with this seed WireGuard-only?** A WireGuard seed is an
/// estate box; another tunnel kind or no seed is not.
pub fn estate_is_wg_only(seed_is_wireguard: Option<bool>) -> bool {
    seed_is_wireguard == Some(true)
}

/// [`Estate::bind`] under the process posture; as given without one.
pub fn estate_bind(what: &str, at: &str) -> Result<String, String> {
    match estate() {
        Some(e) => e.bind(what, at),
        None => Ok(at.to_string()),
    }
}

/// [`Estate::dial`] under the process posture; allowed without one.
pub fn estate_dial(what: &str, endpoint: &str) -> Result<(), String> {
    match estate() {
        Some(e) => e.dial(what, endpoint),
        None => Ok(()),
    }
}

/// [`Estate::peer`] under the process posture; allowed without one.
pub fn estate_peer(what: &str, remote: Option<SocketAddr>) -> Result<(), String> {
    match estate() {
        Some(e) => e.peer(what, remote),
        None => Ok(()),
    }
}

/// Pure: **the door hosts an estate box may name** — the tunnel ones (and
/// loopback) only; under no posture, all of them.
pub fn tunnel_hosts(estate: Option<&Estate>, hosts: Vec<String>) -> Vec<String> {
    match estate {
        None => hosts,
        Some(e) => hosts
            .into_iter()
            .filter(|h| {
                h.trim()
                    .parse::<IpAddr>()
                    .is_ok_and(|ip| ip.is_loopback() || e.in_tunnel(ip))
            })
            .collect(),
    }
}

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

    fn estate(overlay: Option<[u8; 4]>) -> Estate {
        Estate {
            net: (Ipv4Addr::new(10, 44, 0, 0), 24),
            overlay: overlay.map(Ipv4Addr::from),
            pairs: vec![
                (Ipv4Addr::new(10, 13, 8, 2), Ipv4Addr::new(10, 44, 0, 2)),
                (Ipv4Addr::new(10, 13, 8, 3), Ipv4Addr::new(10, 44, 0, 3)),
            ],
        }
    }

    /// ★ **A box the ONE box or the installer names by its utility address is dialled
    /// over the tunnel** (a recharge's twin, a fence's old primary): mapped to the same
    /// box's overlay address; an address no fed box holds is refused, never dialled.
    #[test]
    fn a_fed_utility_address_is_dialled_at_the_same_boxs_overlay_address() {
        let e = estate(Some([10, 44, 0, 3]));
        assert_eq!(
            e.route("the old primary", "10.13.8.2:50051").unwrap(),
            "10.44.0.2:50051"
        );
        assert_eq!(
            e.route("the twin", "http://10.13.8.3:50051").unwrap(),
            "http://10.44.0.3:50051"
        );
        assert_eq!(
            e.route("the twin", "10.44.0.3:50051").unwrap(),
            "10.44.0.3:50051"
        );
        assert!(
            e.route("the old primary", "10.13.8.9:50051")
                .unwrap_err()
                .contains(NOT_WG),
            "nobody's utility address: refused"
        );
    }

    /// ★ **An estate door binds on wg0 only**: `0.0.0.0` moves to the overlay
    /// address, the overlay and loopback stay, the utility address is REFUSED,
    /// and so is `0.0.0.0` before the tunnel address is known.
    #[test]
    fn an_estate_door_binds_on_the_tunnel_and_a_utility_bind_is_refused() {
        let e = estate(Some([10, 44, 0, 2]));
        assert_eq!(
            e.bind("the gRPC door", "0.0.0.0:50051").unwrap(),
            "10.44.0.2:50051"
        );
        assert_eq!(
            e.bind("the replication door", "10.44.0.2:50052").unwrap(),
            "10.44.0.2:50052"
        );
        assert_eq!(
            e.bind("a twin's public door", "127.0.0.1:8443").unwrap(),
            "127.0.0.1:8443"
        );
        let utility = e.bind("the gRPC door", "10.13.8.2:50051").unwrap_err();
        assert!(
            utility.contains(NOT_WG) && utility.contains("10.13.8.2"),
            "{utility}"
        );
        assert!(
            estate(None)
                .bind("the gRPC door", "0.0.0.0:50051")
                .unwrap_err()
                .contains(NOT_WG),
            "no tunnel address yet"
        );
    }

    /// ★ **An estate link to a non-wg address is refused**: the twin dials the
    /// primary's overlay, never its utility address.
    #[test]
    fn an_estate_link_to_a_non_wg_address_is_refused() {
        let e = estate(Some([10, 44, 0, 3]));
        assert!(e.dial("the primary", "http://10.44.0.2:50051").is_ok());
        assert!(e.dial("the primary", "10.44.0.2:50052").is_ok());
        assert!(
            e.dial("the primary", "127.0.0.1:50051").is_ok(),
            "the box itself"
        );
        for bad in [
            "http://10.13.8.2:50051",
            "10.13.8.2:50052",
            "94.237.1.2:443",
            "main.holger.test:50051",
        ] {
            assert!(
                e.dial("the primary", bad).unwrap_err().contains(NOT_WG),
                "{bad}"
            );
        }
    }

    /// ★ **CA material never comes over a non-wg socket**: a caller on the
    /// utility network (or one nobody can name) is refused; a tunnel or
    /// loopback caller is admitted.
    #[test]
    fn ca_material_is_refused_to_a_caller_off_the_tunnel() {
        let e = estate(Some([10, 44, 0, 2]));
        assert!(e
            .peer(
                "IssueTwinCertificate",
                Some("10.44.0.1:40000".parse().unwrap())
            )
            .is_ok());
        assert!(e
            .peer(
                "IssueTwinCertificate",
                Some("127.0.0.1:40000".parse().unwrap())
            )
            .is_ok());
        assert!(e
            .peer(
                "IssueTwinCertificate",
                Some("10.13.8.1:40000".parse().unwrap())
            )
            .unwrap_err()
            .contains(NOT_WG));
        assert!(e
            .peer("IssueTwinCertificate", None)
            .unwrap_err()
            .contains(NOT_WG));
    }

    /// The door's hosts under the posture are the tunnel ones only.
    #[test]
    fn an_estate_door_names_only_tunnel_hosts() {
        let e = estate(Some([10, 44, 0, 2]));
        assert_eq!(
            tunnel_hosts(
                Some(&e),
                vec![
                    "10.13.8.2".into(),
                    "10.44.0.2".into(),
                    "door.example".into()
                ]
            ),
            vec!["10.44.0.2".to_string()]
        );
        assert_eq!(
            tunnel_hosts(None, vec!["10.13.8.2".into()]),
            vec!["10.13.8.2".to_string()]
        );
    }

    /// An estate box (a WireGuard seed) is WireGuard-only; a standalone one is not.
    #[test]
    fn an_estate_box_is_wg_only_and_a_standalone_appliance_is_not() {
        assert!(estate_is_wg_only(Some(true)));
        assert!(!estate_is_wg_only(Some(false)));
        assert!(!estate_is_wg_only(None));
    }
}