1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
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<String, (WGApi, task::JoinHandle<()>)>,
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<RwLock<State>>, 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<E: std::fmt::Display, O, F: std::future::Future<Output = Result<O, E>>>(
f: F,
) -> () {
match f.await {
Err(e) => error!("oh no: {e}"),
_ => (),
};
}
|