diff --git a/src/main.rs b/src/main.rs index 8ccc924..caf3b78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ #![feature(ip)] mod igd; use igd::*; -use std::collections::HashMap; -use std::net::{IpAddr, SocketAddr, ToSocketAddrs, UdpSocket}; +use std::collections::{HashMap, HashSet}; +use std::net::{IpAddr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket}; use std::process::Command; use std::sync::Mutex; use std::thread; @@ -33,8 +33,6 @@ type Pubkey = String; #[derive(Deserialize)] struct Config { - /// The Wireguard interface name - interface: Pubkey, /// The port to use for gossip inside the Wireguard mesh (must be the same on all nodes) gossip_port: u16, /// The secret to use to authenticate nodes between them @@ -43,10 +41,6 @@ struct Config { /// The file where to persist known peer addresses persist_file: Option, - /// Use IPv6 instead of IPv4 - #[serde(default)] - ipv6: bool, - /// Enable LAN discovery #[serde(default)] lan_discovery: bool, @@ -63,10 +57,14 @@ struct Config { struct Peer { /// The peer's Wireguard public key pubkey: Pubkey, - /// The peer's Wireguard address + /// The destination used for gossip packets address: IpAddr, + /// The Wireguard interface name + interface: String, + /// The endpoint port + port: u16, /// An optionnal Wireguard endpoint used to initialize a connection to this peer - endpoint: Option, + endpoint: Option, } fn main() -> Result<()> { @@ -120,9 +118,15 @@ fn kdf(secret: &str) -> xsalsa20poly1305::Key { hash.as_bytes().clone().into() } -fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option, u64)>)> { +struct IfInfo { + our_pubkey: Pubkey, + listen_port: u16, + peers: Vec<(Pubkey, Option, u64)>, +} + +fn wg_dump(interface: &str) -> Result { let output = Command::new("wg") - .args(["show", &config.interface, "dump"]) + .args(["show", interface, "dump"]) .output()?; let mut lines = std::str::from_utf8(&output.stdout)?.split('\n'); @@ -130,7 +134,7 @@ fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option Result<(Pubkey, u16, Vec<(Pubkey, Option>(); - Ok((our_pubkey, listen_port, peers)) + Ok(IfInfo { our_pubkey, listen_port, peers }) } // ============ DAEMON CODE ================= @@ -159,10 +163,9 @@ fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option, + our_pubkey: Pubkey, } struct PeerInfo { @@ -170,10 +173,10 @@ struct PeerInfo { gossip_ip: IpAddr, gossip_prio: u64, // Info retrieved from wireguard - endpoint: Option, + endpoint: Option, last_seen: u64, // Info received by LAN broadcast - lan_endpoint: Option<(SocketAddr, u64)>, + lan_endpoint: Option<(IpAddr, u64)>, } #[derive(Serialize, Deserialize, Debug)] @@ -182,35 +185,47 @@ enum Gossip { Pong, Announce { pubkey: Pubkey, - endpoints: Vec<(SocketAddr, u64)>, + endpoints: Vec<(IpAddr, u64)>, }, Request, LanBroadcast { pubkey: Pubkey, - listen_port: u16, }, } impl Daemon { fn new(config: Config) -> Result { let gossip_key = kdf(config.gossip_secret.as_deref().unwrap_or_default()); - let (our_pubkey, listen_port, _peers) = wg_dump(&config)?; - let bind_addr = if config.ipv6 { - SocketAddr::new("::".parse()?, config.gossip_port) // IPv6 - } else { - SocketAddr::new("0.0.0.0".parse()?, config.gossip_port) // IPv4 - }; - let socket = UdpSocket::bind(bind_addr)?; - socket.set_broadcast(true)?; + + let interface_names = config.peers.iter().map(|peer| peer.interface.clone()).collect::>(); + let interfaces = interface_names.into_iter().map(|interface_name| wg_dump(&interface_name).map(|ifinfo| (interface_name, ifinfo))).collect::>>()?; + let socket = UdpSocket::bind(SocketAddr::new("::".parse()?, config.gossip_port))?; + //socket.set_broadcast(true)?; + socket.set_ttl(1)?; + + let our_pubkey = interfaces.iter().next().unwrap().1.our_pubkey.clone(); + + let peers = config.peers.iter().map(|peer_cfg| { + ( + peer_cfg.pubkey.clone(), + PeerInfo { + gossip_ip: peer_cfg.address, + gossip_prio: fasthash(format!("{}-{}", our_pubkey, peer_cfg.pubkey).as_bytes()), + endpoint: None, // Is resolved as DNS name later + last_seen: u64::MAX, + lan_endpoint: None, + } + ) + }).collect(); Ok(Daemon { config, gossip_key, - our_pubkey, - listen_port, socket, + our_pubkey, state: Mutex::new(State { - peers: HashMap::new(), + interfaces, + peers, gossip: HashMap::new(), }), }) @@ -245,7 +260,7 @@ impl Daemon { fn initialize(&self) -> Result<()> { let mut state = self.state.lock().unwrap(); - state.read_wg_peers(self)?; + state.read_wg_peers()?; state.setup_wg_peers(self, 0)?; Ok(()) } @@ -276,7 +291,7 @@ impl Daemon { let mut state = self.state.lock().unwrap(); // 1. Update local peers info of peers - state.read_wg_peers(self)?; + state.read_wg_peers()?; // 2. Send gossip for peers where there is a big update let announces = state @@ -331,12 +346,10 @@ impl Daemon { } Gossip::LanBroadcast { pubkey, - listen_port, } => { if self.config.lan_discovery { if let Some(peer) = state.peers.get_mut(&pubkey) { - let addr = SocketAddr::new(from.ip(), listen_port); - peer.lan_endpoint = Some((addr, time())); + peer.lan_endpoint = Some((from.ip(), time())); } } } @@ -403,7 +416,6 @@ impl Daemon { fn lan_broadcast_iter(&self) -> Result<()> { let packet = self.make_packet(&Gossip::LanBroadcast { pubkey: self.our_pubkey.clone(), - listen_port: self.listen_port, })?; let addr = if self.config.ipv6 { SocketAddr::new("ff05::1".parse().unwrap(), self.config.gossip_port) @@ -446,10 +458,10 @@ impl Daemon { } } - struct State { peers: HashMap, - gossip: HashMap>, + gossip: HashMap>, + interfaces: HashMap, } impl State { @@ -479,7 +491,7 @@ impl State { &mut self, daemon: &Daemon, pubkey: Pubkey, - mut endpoints: Vec<(SocketAddr, u64)>, + mut endpoints: Vec<(IpAddr, u64)>, ) -> Result<()> { let propagate = { match self.gossip.get_mut(&pubkey) { @@ -521,36 +533,21 @@ impl State { Ok(()) } - fn read_wg_peers(&mut self, daemon: &Daemon) -> Result<()> { - let (_, _, wg_peers) = wg_dump(&daemon.config)?; + fn read_wg_peers(&mut self) -> Result<()> { // Clear old known endpoints if any for (_, peer) in self.peers.iter_mut() { peer.endpoint = None; } - for (pk, endpoint, last_seen) in wg_peers { - match self.peers.get_mut(&pk) { - Some(i) => { - i.endpoint = endpoint; - i.last_seen = last_seen; - } - None => { - let gossip_ip = match daemon.config.peers.iter().find(|x| x.pubkey == pk) { - Some(x) => x.address, - None => continue, - }; - let gossip_prio = fasthash(format!("{}-{}", daemon.our_pubkey, pk).as_bytes()); - self.peers.insert( - pk, - PeerInfo { - endpoint, - lan_endpoint: None, - gossip_prio, - gossip_ip, - last_seen, - }, - ); + for (ifname, ifinfo) in &mut self.interfaces { + *ifinfo = wg_dump(&ifname)?; + for (pk, endpoint, last_seen) in &mut ifinfo.peers { + if let Some(i) = self.peers.get_mut(&*pk) { + i.endpoint = endpoint.as_ref().map(SocketAddr::ip); + i.last_seen = *last_seen; + } else { + warn!("unknown peer: {}", pk); } } } @@ -585,7 +582,7 @@ impl State { Command::new("wg") .args([ "set", - &daemon.config.interface, + &peer_cfg.interface, "peer", &peer_cfg.pubkey, "persistent-keepalive", @@ -611,11 +608,11 @@ impl State { .cloned() .unwrap_or_default(); if let Some(endpoint) = &peer_cfg.endpoint { - match endpoint.to_socket_addrs() { + match format!("{}:0", endpoint).to_socket_addrs() { Err(e) => error!("Could not resolve DNS for {}: {}", endpoint, e), Ok(iter) => { for addr in iter { - endpoints.push((addr, 0)); + endpoints.push((addr.ip(), 0)); } } } @@ -624,11 +621,7 @@ impl State { endpoints } }; - let single_ip_cidr:u8 = if peer_cfg.address.is_ipv6(){ - 128 - } else { - 32 - }; + if !endpoints.is_empty() { let endpoint = endpoints[i % endpoints.len()].0; @@ -641,19 +634,20 @@ impl State { // Skip if we are already using that endpoint continue; } + info!("Configure {} with endpoint {}", peer_cfg.pubkey, endpoint); Command::new("wg") .args([ "set", - &daemon.config.interface, + &peer_cfg.interface, "peer", &peer_cfg.pubkey, "endpoint", - &endpoint.to_string(), + &SocketAddr::new(endpoint, peer_cfg.port).to_string(), "persistent-keepalive", "10", "allowed-ips", - &format!("{}/{}", peer_cfg.address, single_ip_cidr), + "::/0,0.0.0.0/0" ]) .output()?; let packet = daemon.make_packet(&Gossip::Ping)?; @@ -666,11 +660,11 @@ impl State { Command::new("wg") .args([ "set", - &daemon.config.interface, + &peer_cfg.interface, "peer", &peer_cfg.pubkey, "allowed-ips", - &format!("{}/{}", peer_cfg.address, single_ip_cidr), + "::/0,0.0.0.0/0" ]) .output()?; }