From 0407ca9387b0906e8091762c0eff52210f0c99f5 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 9 Mar 2023 13:04:54 +0100 Subject: Add UPnP IGD support (fix #1) --- src/main.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 5074d08..3c5df47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use std::net::{IpAddr, SocketAddr, UdpSocket, ToSocketAddrs}; +use std::net::{IpAddr, SocketAddr, SocketAddrV4, ToSocketAddrs, UdpSocket}; use std::process::Command; use std::sync::Mutex; use std::thread; use std::time::Duration; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use log::*; use serde::{Deserialize, Serialize}; @@ -21,12 +21,17 @@ const TIMEOUT: Duration = Duration::from_secs(300); /// Interval at which to gossip last_seen info const GOSSIP_INTERVAL: Duration = Duration::from_secs(300); +const IGD_INTERVAL: Duration = Duration::from_secs(60); +const IGD_LEASE_DURATION: Duration = Duration::from_secs(300); + type Pubkey = String; #[derive(Deserialize)] struct Config { /// The Wireguard interface name interface: Pubkey, + /// Forward an external port to Wiregard using UPnP IGD + upnp_forward_external_port: Option, /// The port to use for gossip inside the Wireguard mesh (must be the same on all nodes) gossip_port: u16, /// The list of peers we try to connect to @@ -175,6 +180,7 @@ impl Daemon { thread::scope(|s| { s.spawn(|| self.wg_loop()); s.spawn(|| self.recv_loop()); + s.spawn(|| self.igd_loop()); }); unreachable!() } @@ -285,6 +291,54 @@ impl Daemon { debug!("RECV {}\t{:?}", src, gossip); Ok((src, gossip)) } + + fn igd_loop(&self) { + if let Some(external_port) = self.config.upnp_forward_external_port { + loop { + if let Err(e) = self.igd_loop_iter(external_port) { + error!("IGD loop error: {}", e); + } + std::thread::sleep(IGD_INTERVAL); + } + } + } + + fn igd_loop_iter(&self, external_port: u16) -> Result<()> { + let gateway = igd::search_gateway(Default::default())?; + + let gwa = gateway.addr.ip().octets(); + let cmplen = match gwa { + [192, 168, _, _] => 3, + [10, _, _, _] => 2, + _ => bail!( + "Gateway IP does not appear to be in a local network ({})", + gateway.addr.ip() + ), + }; + let private_ip = get_if_addrs::get_if_addrs()? + .into_iter() + .map(|i| i.addr.ip()) + .filter_map(|a| match a { + std::net::IpAddr::V4(a4) if a4.octets()[..cmplen] == gwa[..cmplen] => Some(a4), + _ => None, + }) + .next() + .ok_or(anyhow!("No interface has an IP on same subnet as gateway"))?; + info!( + "IGD: gateway is {}, private IP is {}, making announce", + gateway.addr, private_ip + ); + + gateway.add_port( + igd::PortMappingProtocol::UDP, + external_port, + SocketAddrV4::new(private_ip, self.listen_port), + IGD_LEASE_DURATION.as_secs() as u32, + "Wireguard via wgautomesh", + )?; + + Ok(()) + } } struct State { @@ -392,8 +446,10 @@ impl State { if let Some(endpoint) = &peer.endpoint { match endpoint.to_socket_addrs() { Err(e) => error!("Could not resolve DNS for {}: {}", endpoint, e), - Ok(iter) => for addr in iter { - endpoints.push((addr, 0)); + Ok(iter) => { + for addr in iter { + endpoints.push((addr, 0)); + } } } } -- cgit v1.2.3