use crate::join::Peer;
use crate::seed::{Seed, TunnelKind};
use std::collections::BTreeMap;
use std::fmt;
use std::net::Ipv4Addr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Identity {
pub overlay: Ipv4Addr,
pub pubkey: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TunnelStatus {
pub kind: TunnelKind,
pub up: bool,
pub reachable: Vec<String>,
pub detail: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TunnelError {
NotBuilt(TunnelKind),
Unimplemented { kind: TunnelKind, missing: &'static str },
NotConfigured { kind: TunnelKind, what: String },
Failed(String),
InstallerLinkNone,
}
impl fmt::Display for TunnelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TunnelError::NotBuilt(k) => write!(
f,
"REFUSED [tunnel-not-built] the seed asks for `{}` and this image carries no such driver — compose the medium with it (a build-thing setting), do not guess at runtime",
k.as_str()
),
TunnelError::Unimplemented { kind, missing } => write!(f, "REFUSED [tunnel-{}-unimplemented] the `{}` driver is built but cannot bring a tunnel up: missing {missing}", kind.as_str(), kind.as_str()),
TunnelError::NotConfigured { kind, what } => write!(f, "REFUSED [tunnel-{}-unconfigured] {what}", kind.as_str()),
TunnelError::Failed(e) => write!(f, "REFUSED [tunnel-failed] {e}"),
TunnelError::InstallerLinkNone => write!(f, "REFUSED [installer-link-none] the installer reaches a VM through its WireGuard link, under mutual TLS both ways, ALWAYS (owner 2026-09-25); `tunnel = none` gives it no link and is refused — lay the estate with `tunnel = wireguard`"),
}
}
}
impl std::error::Error for TunnelError {}
pub trait Tunnel {
fn kind(&self) -> TunnelKind;
fn public_key(&self) -> String {
String::new()
}
fn up(&mut self, me: &Identity, peers: &[Peer]) -> Result<(), TunnelError>;
fn add_peer(&mut self, peer: &Peer) -> Result<(), TunnelError>;
fn status(&self) -> TunnelStatus;
fn installer_reach(&self) -> Result<crate::installer_link::Reach, TunnelError> {
crate::installer_link::reach(self.kind())
}
}
#[derive(Debug, Default)]
pub struct NoTunnel {
peers: Vec<Peer>,
up: bool,
}
impl Tunnel for NoTunnel {
fn kind(&self) -> TunnelKind {
TunnelKind::None
}
fn up(&mut self, _me: &Identity, peers: &[Peer]) -> Result<(), TunnelError> {
self.peers = peers.to_vec();
self.up = true;
Ok(())
}
fn add_peer(&mut self, peer: &Peer) -> Result<(), TunnelError> {
if !self.peers.iter().any(|p| p.name == peer.name) {
self.peers.push(peer.clone());
}
Ok(())
}
fn status(&self) -> TunnelStatus {
TunnelStatus {
kind: TunnelKind::None,
up: self.up,
reachable: self.peers.iter().map(|p| p.name.clone()).collect(),
detail: "no tunnel: the cloud's own addresses".into(),
}
}
}
pub fn driver(kind: TunnelKind) -> Result<Box<dyn Tunnel>, TunnelError> {
build(kind, None, &BTreeMap::new())
}
pub fn driver_for(seed: &Seed, feed: &BTreeMap<String, String>) -> Result<Box<dyn Tunnel>, TunnelError> {
build(seed.tunnel, Some(seed), feed)
}
#[allow(unused_variables)]
fn build(kind: TunnelKind, seed: Option<&Seed>, feed: &BTreeMap<String, String>) -> Result<Box<dyn Tunnel>, TunnelError> {
match kind {
TunnelKind::None => Ok(Box::new(NoTunnel::default())),
#[cfg(feature = "wireguard")]
TunnelKind::Wireguard => Ok(Box::new(crate::wireguard::WireGuard::mint(None))),
#[cfg(feature = "mullvad")]
TunnelKind::Mullvad => Ok(Box::new(crate::mullvad::Mullvad::new(
crate::wireguard::WireGuard::mint(None),
None,
seed.and_then(|s| s.mullvad.clone()).unwrap_or_default(),
feed.get(crate::seed::FEED_MULLVAD_ACCOUNT).cloned(),
))),
#[cfg(feature = "tailscale")]
TunnelKind::Tailscale => Ok(Box::new(crate::tailscale::Tailscale::new(
seed.and_then(|s| s.tailscale.clone()).unwrap_or_default(),
feed.get(crate::seed::FEED_TAILSCALE_AUTHKEY).cloned(),
))),
#[allow(unreachable_patterns)]
other => Err(TunnelError::NotBuilt(other)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Role;
#[test]
fn no_tunnel_is_up_at_once_and_names_every_peer_and_a_missing_driver_is_refused_by_name() {
let mut t = driver(TunnelKind::None).unwrap();
assert_eq!(t.public_key(), "", "`none` has no key to present");
let me = Identity { overlay: Ipv4Addr::new(10, 13, 9, 90), pubkey: String::new() };
let front = Peer { name: "front".into(), role: Role::Front, tunnel_pubkey: String::new(), overlay: Ipv4Addr::new(10, 13, 8, 179), endpoint: None, utility: None };
t.up(&me, &[front.clone()]).unwrap();
t.add_peer(&front).unwrap();
let s = t.status();
assert!(s.up && s.reachable == vec!["front".to_string()], "{s:?}");
for (kind, built) in [
(TunnelKind::Wireguard, cfg!(feature = "wireguard")),
(TunnelKind::Mullvad, cfg!(feature = "mullvad")),
(TunnelKind::Tailscale, cfg!(feature = "tailscale")),
] {
match driver(kind) {
Ok(d) => assert!(built && d.kind() == kind, "{kind:?} built without its feature"),
Err(e) => {
let e = e.to_string();
assert!(!built && e.starts_with("REFUSED [tunnel-not-built]") && e.contains(kind.as_str()), "{e}");
}
}
}
}
#[test]
fn the_refusals_carry_their_names() {
let e = TunnelError::Unimplemented { kind: TunnelKind::Tailscale, missing: "x" }.to_string();
assert!(e.starts_with("REFUSED [tunnel-tailscale-unimplemented]"), "{e}");
let e = TunnelError::NotConfigured { kind: TunnelKind::Mullvad, what: "no account".into() }.to_string();
assert!(e.starts_with("REFUSED [tunnel-mullvad-unconfigured] no account"), "{e}");
}
}