use once_cell::sync::OnceCell; use sled::{Db, Tree}; static TUNNELS_TREE: OnceCell = OnceCell::new(); fn tunnels_tree(db: &Db) -> &'static Tree { TUNNELS_TREE.get_or_init(|| db.open_tree("tunnels").unwrap()) } mod wireguard; #[derive(serde::Deserialize)] #[serde(tag = "type")] pub(crate) enum NewTunnel { Wireguard(wireguard::NewInterface), } #[derive(serde::Deserialize)] pub(crate) struct NewPeer { tunnel_id: String, peer: NewPeerKind, } #[derive(serde::Deserialize)] #[serde(tag = "type")] pub(crate) enum NewPeerKind { Wireguard(wireguard::Peer), } #[derive(serde::Deserialize, serde::Serialize)] #[serde(tag = "type")] pub(crate) enum Tunnel { Wireguard(wireguard::Interface), } #[derive(serde::Deserialize, serde::Serialize)] pub(crate) struct Peer { tunnel_id: String, peer: PeerKind, } #[derive(serde::Deserialize, serde::Serialize)] #[serde(tag = "type")] pub(crate) enum PeerKind { Wireguard(wireguard::Peer), } pub(crate) async fn add_peer(db: &Db, new_peer: NewPeer) -> Result { let tree = tunnels_tree(db); let tunnel_bytes = tree .get(new_peer.tunnel_id.as_bytes())? .ok_or_else(|| anyhow::anyhow!("Missing tunnel"))?; let tunnel: Tunnel = serde_json::from_slice(&tunnel_bytes)?; let peer = match (new_peer.peer, tunnel) { (NewPeerKind::Wireguard(peer), Tunnel::Wireguard(interface)) => { wireguard::add_peer(&interface, &peer).await?; Peer { tunnel_id: new_peer.tunnel_id, peer: PeerKind::Wireguard(peer), } } // _ => return Err(anyhow::anyhow!("Peer kind mismatch")), }; let v = serde_json::to_vec(&peer)?; loop { let id = db.generate_id()?; let peer_id = peer_id(&peer.tunnel_id, id); if tree .compare_and_swap( peer_id.as_bytes(), None as Option>, Some(v.as_slice()), )? .is_ok() { break; } } tree.flush_async().await?; Ok(peer) } pub(crate) async fn create(new_tunnel: NewTunnel) -> Result { match new_tunnel { NewTunnel::Wireguard(new_interface) => { let interface = wireguard::generate_config(new_interface).await?; wireguard::apply(&interface).await?; Ok(Tunnel::Wireguard(interface)) } } } pub(crate) async fn save(db: &Db, tunnel: &Tunnel) -> Result<(), anyhow::Error> { let tree = tunnels_tree(db); let v = serde_json::to_vec(tunnel)?; loop { let id = db.generate_id()?; let tunnel_id = tunnel_id(id); if tree .compare_and_swap( tunnel_id.as_bytes(), None as Option>, Some(v.as_slice()), )? .is_ok() { break; } } tree.flush_async().await?; Ok(()) } fn tunnel_id(id: u64) -> String { format!("tunnel-{}", id) } fn peer_id(tunnel_id: &str, id: u64) -> String { format!("{}/peers/{}", tunnel_id, id) }