use dbus::channel::MatchingReceiver; use defguard_wireguard_rs::{net::IpAddrMask, WGApi, WireguardInterfaceApi}; use log::{debug, error, info}; use std::{ collections::{BTreeSet, HashMap}, ops::DerefMut, path::PathBuf, str::FromStr, sync::Arc, }; use thiserror::Error; use tokio::{ net::TcpListener, runtime::Builder, signal::unix::{signal, SignalKind}, sync::RwLock, task, }; use crate::{daemon_config::*, daemon_dbus::*, daemon_network::*}; #[derive(Debug, Error)] pub enum DaemonError { #[error("{0}")] Io(#[from] std::io::Error), #[error("{0}")] XdgBase(#[from] xdg::BaseDirectoriesError), // TODO hier wärs nett zu unterscheiden was decoded wurde #[error("{0}")] Decoding(#[from] serde_json::Error), #[error("{0}")] IpMaskParse(#[from] defguard_wireguard_rs::net::IpAddrParseError), #[error("{0}")] WgInterfaceError(#[from] defguard_wireguard_rs::error::WireguardInterfaceError), #[error("{0}")] DbusError(#[from] dbus::Error), } pub struct State { pub conf: Config, pub nw_handles: HashMap)>, pub hostnames: BTreeSet<(String, String)>, } impl Drop for State { fn drop(&mut self) { for (wg_api, _) in self.nw_handles.values() { let _ = wg_api.remove_interface(); } } } pub fn daemon() -> Result<(), DaemonError> { let (config_path, config) = load_config()?; info!("read config"); // NOTE this should not be upgraded to a multi-writer structure carelessly, as we also use the // exclusivity of write locks to ensure only one thread at a time syncs the config to disk let state = Arc::new(RwLock::new(State { conf: config, nw_handles: HashMap::new(), hostnames: BTreeSet::new(), })); let rt = Builder::new_current_thread().enable_all().build()?; rt.block_on(run_networks(state, config_path))?; Ok(()) } async fn run_networks(state: Arc>, config_path: PathBuf) -> Result<(), DaemonError> { let mut state_rw_guard = state.write().await; let state_rw = state_rw_guard.deref_mut(); // load existing configurations let mut hostname_pairs = BTreeSet::new(); for (name, nw) in &state_rw.conf.networks { let (wg_api, mut new_hostnames) = add_network( name.clone(), nw.privkey.clone(), nw.address.clone(), nw.listen_port, &nw.peers, ) .await?; hostname_pairs.append(&mut new_hostnames); let addr = IpAddrMask::from_str(&nw.address)?.ip; let h = task::spawn(print_error(run_network( state.clone(), TcpListener::bind((addr, nw.mäsch_port)).await?, name.clone(), ))); state_rw.nw_handles.insert(name.clone(), (wg_api, h)); debug!("loaded configuration for {0}", name); } match sync_hostnames(&hostname_pairs) { Ok(_) => (), Err(e) => error!("Failed to sync hostnames to disk: {e}"), }; info!("loaded all existing configurations"); drop(state_rw_guard); // set up dbus interface let (c, recv_token) = start_dbus(state.clone(), config_path).await?; // wait for SIGTERM/SIGINT let mut sigterm_fut = signal(SignalKind::terminate())?; let mut sigint_fut = signal(SignalKind::interrupt())?; let mut sighup_fut = signal(SignalKind::hangup())?; tokio::select! { _ = sigterm_fut.recv() => info!("Received SIGTERM"), _ = sigint_fut.recv() => info!("Received SIGINT"), _ = sighup_fut.recv() => info!("Received SIGHUP"), }; // clean exit c.stop_receive(recv_token); let mut state_rw_guard = state.write().await; for (_, (wg_api, h)) in state_rw_guard.nw_handles.drain() { let _ = wg_api.remove_interface(); h.abort(); // could also join the handles... don't think that would do too much, though } Ok(()) } pub async fn print_error>>( f: F, ) -> () { match f.await { Err(e) => error!("oh no: {e}"), _ => (), }; }