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::{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>,
handle: Option<TunnelHandle>,
}
impl WireGuard {
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, 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, 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), handle: 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() });
}
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?)")))?;
let handle = dataplane::spawn(tun, &spec).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 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)
.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 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);
}
}