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));
}
}