use crate::join::Peer;
use crate::seed::TunnelKind;
use crate::tunnel::{Identity, Tunnel, TunnelError, TunnelStatus};
use crate::Role;
use std::net::{Ipv4Addr, SocketAddrV4};
use wg_appliance_core::{addr, dataplane, decode_key, encode_key, generate_keypair, PeerSpec, TunDevice, TunnelHandle, TunnelSpec, KEY_LEN};
pub const IFACE: &str = "wg0";
pub const FRONT_PORT: u16 = 51820;
pub const MTU: u32 = 1420;
pub const KEEPALIVE_S: u16 = 25;
pub struct WireGuard {
private: [u8; KEY_LEN],
public_b64: String,
listen_port: Option<u16>,
overlay: Option<Ipv4Addr>,
peers: Vec<Peer>,
given: Option<TunnelSpec>,
via_front: bool,
psk: Option<[u8; KEY_LEN]>,
handle: Option<TunnelHandle>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct BoxPlan {
pub address: (Ipv4Addr, u8),
pub front: PeerSpec,
}
impl std::fmt::Debug for BoxPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BoxPlan {{ address: {}/{}, front: {:?} owning {:?} }}", self.address.0, self.address.1, self.front.endpoint, self.front.allowed_ips)
}
}
pub fn box_plan(overlay: Ipv4Addr, peers: &[Peer]) -> Result<BoxPlan, TunnelError> {
if !crate::in_overlay(overlay) {
return Err(TunnelError::Failed(format!("{IFACE}: {overlay} is not in the estate's overlay {}/{}", crate::OVERLAY_NET.0, crate::OVERLAY_NET.1)));
}
let front = peers.iter().find(|p| p.role == Role::Front).ok_or_else(|| TunnelError::Failed(format!("{IFACE}: the installer fed no front tunnel; nothing to dial")))?;
let endpoint = front.endpoint.as_deref().filter(|e| !e.trim().is_empty()).ok_or_else(|| TunnelError::Failed(format!("peer `{}` is the front and names no endpoint; the front is the one box everybody dials", front.name)))?;
let endpoint = endpoint.parse::<SocketAddrV4>().map_err(|_| TunnelError::Failed(format!("peer `{}`: endpoint `{endpoint}` is not ipv4:port", front.name)))?;
let public_key = decode_key(&front.tunnel_pubkey).map_err(|e| TunnelError::Failed(format!("peer `{}`: {}", front.name, e.0)))?;
Ok(BoxPlan {
address: (overlay, crate::OVERLAY_NET.1),
front: PeerSpec { public_key, preshared_key: None, endpoint: Some(endpoint), allowed_ips: vec![crate::OVERLAY_NET], persistent_keepalive: Some(KEEPALIVE_S) },
})
}
pub fn box_plan_with_psk(overlay: Ipv4Addr, peers: &[Peer], psk: Option<&str>) -> Result<BoxPlan, TunnelError> {
let mut plan = box_plan(overlay, peers)?;
plan.front.preshared_key = psk_of(psk)?;
Ok(plan)
}
fn psk_of(psk: Option<&str>) -> Result<Option<[u8; KEY_LEN]>, TunnelError> {
match psk.map(str::trim) {
None => Ok(None),
Some("") => Err(TunnelError::Failed(format!("REFUSED [wg-psk-blank] {IFACE}: the installer fed a BLANK WireGuard pre-shared key; the link is never raised without its key"))),
Some(p) => decode_key(p).map(Some).map_err(|_| TunnelError::Failed(format!("{IFACE}: the installer fed a WireGuard pre-shared key that is not one (32 bytes, base64)"))),
}
}
impl WireGuard {
pub fn box_via_front_with_psk(private: [u8; KEY_LEN], psk: Option<&str>) -> Result<WireGuard, TunnelError> {
Ok(WireGuard { psk: psk_of(psk)?, ..WireGuard::box_via_front(private) })
}
pub fn mint(listen_port: Option<u16>) -> WireGuard {
let (private, public) = generate_keypair();
WireGuard { private, public_b64: encode_key(&public), listen_port, overlay: None, peers: Vec::new(), given: None, via_front: false, psk: None, handle: None }
}
pub fn with_key(private: [u8; KEY_LEN], listen_port: Option<u16>) -> WireGuard {
let public = wg_appliance_core::keys::public_from_private_bytes(&private);
WireGuard { private, public_b64: encode_key(&public), listen_port, overlay: None, peers: Vec::new(), given: None, via_front: false, psk: None, handle: None }
}
pub fn from_spec(spec: TunnelSpec) -> WireGuard {
let public = wg_appliance_core::keys::public_from_private_bytes(&spec.private_key);
WireGuard { private: spec.private_key, public_b64: encode_key(&public), listen_port: spec.listen_port, overlay: None, peers: Vec::new(), given: Some(spec), via_front: false, psk: None, handle: None }
}
pub fn box_via_front(private: [u8; KEY_LEN]) -> WireGuard {
WireGuard { via_front: true, ..WireGuard::with_key(private, None) }
}
pub fn private_key(&self) -> &[u8; KEY_LEN] {
&self.private
}
pub fn public_key(&self) -> &str {
&self.public_b64
}
pub fn identity(&self, overlay: Ipv4Addr) -> Identity {
Identity { overlay, pubkey: self.public_b64.clone() }
}
pub fn spec(&self) -> Result<TunnelSpec, TunnelError> {
if let Some(g) = &self.given {
return Ok(TunnelSpec { private_key: g.private_key, listen_port: g.listen_port, peers: g.peers.clone() });
}
if self.via_front {
let overlay = self.overlay.ok_or_else(|| TunnelError::Failed(format!("{IFACE}: no overlay address yet")))?;
let mut plan = box_plan(overlay, &self.peers)?;
plan.front.preshared_key = self.psk;
return Ok(TunnelSpec { private_key: self.private, listen_port: self.listen_port, peers: vec![plan.front] });
}
let mut peers = Vec::new();
for p in &self.peers {
if p.tunnel_pubkey == self.public_b64 {
continue;
}
let public_key = decode_key(&p.tunnel_pubkey).map_err(|e| TunnelError::Failed(format!("peer `{}`: {}", p.name, e.0)))?;
let endpoint = match (&p.endpoint, p.role) {
(Some(e), _) => Some(e.parse::<SocketAddrV4>().map_err(|_| TunnelError::Failed(format!("peer `{}`: endpoint `{e}` is not ipv4:port", p.name)))?),
(None, Role::Front) => return Err(TunnelError::Failed(format!("peer `{}` is the front and names no endpoint; the front is the one box everybody dials", p.name))),
(None, _) => None,
};
peers.push(PeerSpec {
public_key,
preshared_key: None,
endpoint,
allowed_ips: vec![(p.overlay, 32)],
persistent_keepalive: Some(KEEPALIVE_S),
});
}
if peers.is_empty() {
return Err(TunnelError::Failed("no peers: a tunnel with nobody in it is not up".into()));
}
Ok(TunnelSpec { private_key: self.private, listen_port: self.listen_port, peers })
}
fn respawn(&mut self) -> Result<(), TunnelError> {
let spec = self.spec()?;
if let Some(mut h) = self.handle.take() {
h.shutdown();
}
let tun = TunDevice::create(IFACE, MTU).map_err(|e| TunnelError::Failed(format!("{IFACE}: {e} (CAP_NET_ADMIN?)")))?;
if let (true, Some(overlay)) = (self.via_front, self.overlay) {
let (a, p) = box_plan(overlay, &self.peers)?.address;
addr::assign_ipv4(IFACE, a, p).map_err(|e| TunnelError::Failed(format!("{IFACE} {a}/{p}: {e}")))?;
addr::link_up(IFACE).map_err(|e| TunnelError::Failed(format!("{IFACE} up: {e}")))?;
}
let names: Vec<&str> = if self.given.is_some() { Vec::new() } else { self.peers.iter().filter(|p| p.tunnel_pubkey != self.public_b64).map(|p| p.name.as_str()).collect() };
let handle = dataplane::spawn_named(tun, &spec, &names).map_err(|e| TunnelError::Failed(format!("{IFACE}: {e}")))?;
self.handle = Some(handle);
Ok(())
}
}
impl Tunnel for WireGuard {
fn kind(&self) -> TunnelKind {
TunnelKind::Wireguard
}
fn public_key(&self) -> String {
self.public_b64.clone()
}
fn up(&mut self, me: &Identity, peers: &[Peer]) -> Result<(), TunnelError> {
if me.pubkey != self.public_b64 {
return Err(TunnelError::Failed("the identity handed in is not this driver's key".into()));
}
self.overlay = Some(me.overlay);
if self.handle.is_some() && self.peers == peers {
return Ok(());
}
self.peers = peers.to_vec();
self.respawn()
}
fn repoint(&mut self, me: &Identity, peers: &[Peer]) -> Result<bool, TunnelError> {
if me.pubkey != self.public_b64 {
return Err(TunnelError::Failed("the identity handed in is not this driver's key".into()));
}
let dial = |overlay: Option<Ipv4Addr>, peers: &[Peer]| -> Option<(Ipv4Addr, String, Option<String>)> {
let f = peers.iter().find(|p| p.role == Role::Front)?;
Some((overlay?, f.tunnel_pubkey.trim().to_string(), f.endpoint.clone()))
};
let same = if self.via_front { dial(self.overlay, &self.peers).is_some() && dial(self.overlay, &self.peers) == dial(Some(me.overlay), peers) } else { self.overlay == Some(me.overlay) && self.peers == peers };
self.overlay = Some(me.overlay);
if same && self.handle.is_some() {
return Ok(false);
}
self.peers = peers.to_vec();
if self.handle.is_none() {
return Ok(false);
}
self.respawn().map(|()| true)
}
fn set_preshared_key(&mut self, psk: Option<&str>) -> Result<bool, TunnelError> {
let psk = psk_of(psk)?;
if psk == self.psk {
return Ok(false);
}
self.psk = psk;
if self.handle.is_some() {
self.respawn()?;
}
Ok(true)
}
fn add_peer(&mut self, peer: &Peer) -> Result<(), TunnelError> {
if self.peers.iter().any(|p| p.name == peer.name && p.tunnel_pubkey == peer.tunnel_pubkey) {
return Ok(());
}
self.peers.retain(|p| p.name != peer.name);
self.peers.push(peer.clone());
if self.handle.is_some() {
self.respawn()?;
}
Ok(())
}
fn status(&self) -> TunnelStatus {
let up = self.handle.is_some();
let reachable = match &self.handle {
Some(h) => {
let stats = h.peers();
self.peers
.iter()
.filter(|p| p.tunnel_pubkey != self.public_b64 && (!self.via_front || p.role == Role::Front))
.enumerate()
.filter(|(i, _)| stats.get(*i).is_some_and(|s| s.handshaken()))
.map(|(_, p)| p.name.clone())
.collect()
}
None => Vec::new(),
};
TunnelStatus {
kind: TunnelKind::Wireguard,
up,
reachable,
detail: format!("{IFACE} · {} peer(s) · {}", self.peers.len(), self.overlay.map(|o| o.to_string()).unwrap_or_else(|| "no address yet".into())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn peer(name: &str, role: Role, overlay: Ipv4Addr, endpoint: Option<&str>) -> Peer {
let (_, public) = generate_keypair();
Peer { name: name.into(), role, tunnel_pubkey: encode_key(&public), overlay, endpoint: endpoint.map(str::to_string), utility: None }
}
#[test]
fn the_estate_is_shaped_into_a_spec_with_the_front_dialled_and_the_others_learned() {
let mut wg = WireGuard::mint(None);
let front = peer("front", Role::Front, crate::OVERLAY_FRONT, Some("95.111.211.189:51820"));
let other = peer("twin", Role::Twin, crate::OVERLAY_BOX_B, None);
let me = Peer { name: "main".into(), role: Role::Primary, tunnel_pubkey: wg.public_key().to_string(), overlay: crate::OVERLAY_BOX_A, endpoint: None, utility: None };
wg.peers = vec![front.clone(), me, other.clone()];
let spec = wg.spec().unwrap();
assert_eq!(spec.peers.len(), 2, "this box is not its own peer");
assert_eq!(spec.peers[0].endpoint, Some("95.111.211.189:51820".parse().unwrap()));
assert_eq!(spec.peers[0].allowed_ips, vec![(crate::OVERLAY_FRONT, 32)]);
assert_eq!(spec.peers[1].endpoint, None, "a box behind NAT is learned from its handshake");
assert_eq!(spec.peers[1].allowed_ips, vec![(crate::OVERLAY_BOX_B, 32)]);
assert_eq!(spec.peers[0].persistent_keepalive, Some(KEEPALIVE_S));
assert_eq!(&spec.private_key, wg.private_key());
}
#[test]
fn a_front_without_an_endpoint_a_bad_key_and_no_peers_are_refused_by_name() {
let mut wg = WireGuard::mint(Some(FRONT_PORT));
wg.peers = vec![peer("front", Role::Front, crate::OVERLAY_FRONT, None)];
assert!(wg.spec().unwrap_err().to_string().contains("names no endpoint"));
let mut bad = peer("twin", Role::Twin, crate::OVERLAY_BOX_B, None);
bad.tunnel_pubkey = "not-a-key".into();
wg.peers = vec![bad];
assert!(wg.spec().unwrap_err().to_string().contains("peer `twin`"));
wg.peers.clear();
assert!(wg.spec().unwrap_err().to_string().contains("no peers"));
let s = wg.status();
assert!(!s.up && s.reachable.is_empty());
}
#[test]
fn a_box_on_the_relay_puts_its_address_on_wg0_and_the_front_owns_the_overlay() {
let front = peer("front", Role::Front, crate::OVERLAY_FRONT, Some("10.13.8.1:51820"));
let twin = peer("twin", Role::Twin, crate::OVERLAY_BOX_B, None);
let plan = box_plan(crate::OVERLAY_BOX_A, &[twin.clone(), front.clone()]).unwrap();
assert_eq!(plan.address, (crate::OVERLAY_BOX_A, 24), "the address ON wg0, with the overlay's /24");
assert_eq!(plan.front.allowed_ips, vec![crate::OVERLAY_NET], "the /24 is routed via the front");
assert_eq!(plan.front.endpoint, Some("10.13.8.1:51820".parse().unwrap()));
assert_eq!(plan.front.persistent_keepalive, Some(KEEPALIVE_S));
let mut wg = WireGuard::box_via_front(*WireGuard::mint(None).private_key());
wg.overlay = Some(crate::OVERLAY_BOX_A);
wg.peers = vec![front.clone(), twin];
let spec = wg.spec().unwrap();
assert_eq!(spec.peers.len(), 1, "the other box is reached THROUGH the relay, never dialled");
assert!(spec.peers[0] == plan.front);
let router = dataplane::Router::new(&spec.peers);
assert_eq!(router.select(crate::OVERLAY_BOX_B), Some(0), "the twin's address goes to the front");
assert_eq!(router.select(Ipv4Addr::new(8, 8, 8, 8)), None, "never a default route");
assert!(box_plan(Ipv4Addr::new(10, 13, 9, 2), std::slice::from_ref(&front)).unwrap_err().to_string().contains("not in the estate's overlay"));
assert!(box_plan(crate::OVERLAY_BOX_A, &[]).unwrap_err().to_string().contains("no front"));
let mut blind = front;
blind.endpoint = None;
assert!(box_plan(crate::OVERLAY_BOX_A, &[blind]).unwrap_err().to_string().contains("names no endpoint"));
assert!(!WireGuard::mint(None).via_front, "the per-peer shape is unchanged");
}
#[test]
fn with_key_reproduces_the_same_public_key_and_from_spec_keeps_a_given_spec() {
let a = WireGuard::mint(None);
let b = WireGuard::with_key(*a.private_key(), None);
assert_eq!(a.public_key(), b.public_key());
let (_, public) = generate_keypair();
let given = TunnelSpec {
private_key: *a.private_key(),
listen_port: None,
peers: vec![PeerSpec { public_key: public, preshared_key: None, endpoint: Some("10.0.0.1:51820".parse().unwrap()), allowed_ips: vec![(Ipv4Addr::new(0, 0, 0, 0), 0)], persistent_keepalive: None }],
};
let m = WireGuard::from_spec(given);
assert_eq!(m.spec().unwrap().peers[0].allowed_ips, vec![(Ipv4Addr::new(0, 0, 0, 0), 0)], "a Mullvad exit takes everything");
assert_eq!(m.kind(), TunnelKind::Wireguard);
}
const PSK: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
#[test]
fn a_blank_fed_psk_is_refused_never_read_as_no_key() {
let front = peer("vm1", Role::Front, crate::OVERLAY_FRONT, Some("10.13.8.1:51820"));
for blank in ["", " "] {
assert!(box_plan_with_psk(crate::OVERLAY_BOX_A, std::slice::from_ref(&front), Some(blank)).is_err(), "{blank:?}");
assert!(WireGuard::box_via_front_with_psk([3u8; 32], Some(blank)).is_err(), "{blank:?}");
assert!(WireGuard::box_via_front([3u8; 32]).set_preshared_key(Some(blank)).is_err(), "{blank:?}");
}
assert!(box_plan_with_psk(crate::OVERLAY_BOX_A, &[front], None).unwrap().front.preshared_key.is_none());
}
#[test]
fn a_box_on_the_relay_dials_the_front_with_the_fed_psk() {
let front = peer("front", Role::Front, crate::OVERLAY_FRONT, Some("10.13.8.1:51820"));
let plan = box_plan_with_psk(crate::OVERLAY_BOX_A, std::slice::from_ref(&front), Some(PSK)).unwrap();
assert_eq!(plan.front.preshared_key, Some(decode_key(PSK).unwrap()));
let plain = box_plan_with_psk(crate::OVERLAY_BOX_A, std::slice::from_ref(&front), None).unwrap();
assert!(plain == box_plan(crate::OVERLAY_BOX_A, std::slice::from_ref(&front)).unwrap() && plain.front.preshared_key.is_none(), "none fed: the old plan");
let e = box_plan_with_psk(crate::OVERLAY_BOX_A, std::slice::from_ref(&front), Some("not-a-key")).unwrap_err().to_string();
assert!(e.contains("pre-shared key") && !e.contains("not-a-key"), "{e}");
let mut wg = WireGuard::box_via_front_with_psk(*WireGuard::mint(None).private_key(), Some(PSK)).unwrap();
wg.overlay = Some(crate::OVERLAY_BOX_A);
wg.peers = vec![front.clone()];
assert_eq!(wg.spec().unwrap().peers[0].preshared_key, Some(decode_key(PSK).unwrap()));
assert!(WireGuard::box_via_front_with_psk([1; 32], Some("short")).is_err());
let mut later = WireGuard::box_via_front(*WireGuard::mint(None).private_key());
later.overlay = Some(crate::OVERLAY_BOX_A);
later.peers = vec![front];
assert!(later.set_preshared_key(Some(PSK)).unwrap(), "a new PSK is a change");
assert!(!later.set_preshared_key(Some(PSK)).unwrap(), "the same one is not");
assert_eq!(later.spec().unwrap().peers[0].preshared_key, Some(decode_key(PSK).unwrap()));
}
#[test]
fn a_repoint_before_the_pump_runs_keeps_the_new_front_for_the_raise() {
let first = peer("front", Role::Front, crate::OVERLAY_FRONT, Some("10.13.8.1:51820"));
let moved = Peer { endpoint: Some("10.13.8.9:51820".into()), ..peer("front", Role::Front, crate::OVERLAY_FRONT, None) };
let mut wg = WireGuard::box_via_front(*WireGuard::mint(None).private_key());
let me = wg.identity(crate::OVERLAY_BOX_A);
assert!(!wg.repoint(&me, std::slice::from_ref(&first)).unwrap(), "no pump: nothing re-pointed");
assert!(!wg.repoint(&me, std::slice::from_ref(&moved)).unwrap());
assert_eq!(wg.spec().unwrap().peers[0].endpoint, Some("10.13.8.9:51820".parse().unwrap()), "the raise dials the new front");
assert!(wg.repoint(&Identity { overlay: crate::OVERLAY_BOX_A, pubkey: "other".into() }, &[moved]).is_err(), "never another key");
}
}