From cc37249f826165f695b0062ebc913ead539056d2 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:56:20 +0200 Subject: [PATCH] Add more logs, check required commands availability (#76) * add more logs * finish windows logs * remove iptables, add comment * rephrase * remove whitespace * fixes * remove command * Apply suggestions from code review Co-authored-by: Adam * fmt * don't invoke commands to test if they exist * Update dependencies.rs * bump --------- Co-authored-by: Adam --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/client.rs | 2 +- src/dependencies.rs | 55 ++++++++++++++++++++++++++ src/error.rs | 4 +- src/lib.rs | 1 + src/netlink.rs | 5 +-- src/utils.rs | 67 ++++++++++++++++++------------- src/wgapi.rs | 3 +- src/wgapi_freebsd.rs | 55 +++++++++++++++++++++----- src/wgapi_linux.rs | 90 ++++++++++++++++++++++++++++++------------ src/wgapi_userspace.rs | 83 +++++++++++++++++++++++++++----------- src/wgapi_windows.rs | 25 ++++++++++-- 13 files changed, 297 insertions(+), 97 deletions(-) create mode 100644 src/dependencies.rs diff --git a/Cargo.lock b/Cargo.lock index 9a4669f..c39485f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.5.0" +version = "0.5.1" dependencies = [ "base64", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index f42cb81..f34e3a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.5.0" +version = "0.5.1" edition = "2021" description = "A unified multi-platform high-level API for managing WireGuard interfaces" license = "Apache-2.0" diff --git a/examples/client.rs b/examples/client.rs index 5f9ece1..91dcca2 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { #[cfg(not(windows))] wgapi.configure_interface(&interface_config)?; #[cfg(windows)] - wgapi.configure_interface(&interface_config, &[])?; + wgapi.configure_interface(&interface_config, &[], &[])?; wgapi.configure_peer_routing(&interface_config.peers)?; Ok(()) diff --git a/src/dependencies.rs b/src/dependencies.rs new file mode 100644 index 0000000..d4b63b6 --- /dev/null +++ b/src/dependencies.rs @@ -0,0 +1,55 @@ +use std::env; + +use crate::error::WireguardInterfaceError; + +#[cfg(target_os = "linux")] +const COMMANDS: [&str; 2] = ["resolvconf", "ip"]; + +#[cfg(target_os = "windows")] +const COMMANDS: [&str; 1] = [("wireguard.exe")]; + +#[cfg(target_os = "macos")] +const COMMANDS: [&str; 2] = ["wireguard-go", "networksetup"]; + +#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] +const COMMANDS: [&str; 1] = ["resolvconf"]; + +pub(crate) fn check_external_dependencies() -> Result<(), WireguardInterfaceError> { + debug!("Checking if all commands required by wireguard-rs are available"); + let paths = env::var_os("PATH").ok_or(WireguardInterfaceError::MissingDependency( + "Environment variable `PATH` not found".into(), + ))?; + + // Find the missing command to provide a more informative error message later + let missing = COMMANDS.iter().find(|cmd| { + env::split_paths(&paths) + .find(|dir| { + debug!("Trying to find {cmd} in {dir:?}"); + match dir.join(cmd).try_exists() { + Ok(true) => { + debug!("{cmd} found in {dir:?}"); + true + } + Ok(false) => { + debug!("{cmd} not found in {dir:?}"); + false + } + Err(err) => { + warn!("Error while checking for {cmd} in {dir:?}: {err}"); + false + } + } + }) + .is_none() + }); + + if let Some(cmd) = missing { + return Err(WireguardInterfaceError::MissingDependency(format!( + "Command `{}` required by wireguard-rs couldn't be found. The following directories were checked: {paths:?}", + cmd + ))); + } else { + debug!("All commands required by wireguard-rs are available"); + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs index 51413b2..f6e01d8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,8 +15,8 @@ pub enum WireguardInterfaceError { CommandExecutionError { stdout: String, stderr: String }, #[error("IP address/mask error")] IpAddrMask(#[from] crate::net::IpAddrParseError), - #[error("{0} executable not found in system PATH")] - ExecutableNotFound(String), + #[error("Required dependency not found, details: {0}")] + MissingDependency(String), #[error("Unix socket error: {0}")] UnixSockerError(String), #[error("Peer configuration error: {0}")] diff --git a/src/lib.rs b/src/lib.rs index 396f15f..fea086d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ pub(crate) mod netlink; mod utils; mod wgapi; +mod dependencies; #[cfg(target_os = "freebsd")] mod wgapi_freebsd; #[cfg(target_os = "linux")] diff --git a/src/netlink.rs b/src/netlink.rs index e81acc9..9533673 100644 --- a/src/netlink.rs +++ b/src/netlink.rs @@ -198,7 +198,7 @@ where let mut offset = 0; loop { let response = NetlinkMessage::::deserialize(&recv_buf[offset..])?; - debug!("Read netlink response from socket: {response:?}"); + trace!("Read netlink response from socket: {response:?}"); match response.payload { // We've parsed all parts of the response and can leave the loop. NetlinkPayload::Error(msg) if msg.code.is_none() => return Ok(responses), @@ -563,7 +563,7 @@ pub(crate) fn add_route( } /// Add rule for fwmark. -pub(crate) fn add_rule(address: &IpAddrMask, fwmark: u32) -> NetlinkResult<()> { +pub(crate) fn add_fwmark_rule(address: &IpAddrMask, fwmark: u32) -> NetlinkResult<()> { let mut message = RuleMessage::default(); let rule_msg_hdr = RuleHeader { family: address.address_family(), @@ -627,7 +627,6 @@ pub(crate) fn add_main_table_rule( family: address.address_family(), table: RouteHeader::RT_TABLE_MAIN, action: RuleAction::ToTable, - flags: RuleFlags::Invert, ..Default::default() }; diff --git a/src/utils.rs b/src/utils.rs index b952fc1..4834248 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,12 +4,13 @@ use std::collections::HashSet; use std::io::{BufRead, BufReader, Cursor, Error as IoError}; #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] use std::net::{Ipv4Addr, Ipv6Addr}; -#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] -use std::{io::Write, process::Stdio}; use std::{ + fs::OpenOptions, net::{IpAddr, SocketAddr, ToSocketAddrs}, process::Command, }; +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] +use std::{io::Write, process::Stdio}; #[cfg(target_os = "freebsd")] use crate::check_command_output_status; @@ -30,7 +31,7 @@ pub(crate) fn configure_dns( search_domains: &[&str], ) -> Result<(), WireguardInterfaceError> { // Build the resolvconf command - debug!("Setting up DNS"); + debug!("Starting DNS servers configuration for interface {ifname}, DNS: {dns:?}, search domains: {search_domains:?}"); let mut cmd = Command::new("resolvconf"); let mut args = vec!["-a", ifname, "-m", "0"]; // Set the exclusive flag if no search domains are provided, @@ -43,6 +44,7 @@ pub(crate) fn configure_dns( match cmd.stdin(Stdio::piped()).spawn() { Ok(mut child) => { + debug!("Command resolvconf spawned successfully, proceeding with writing nameservers and search domains to its stdin"); if let Some(mut stdin) = child.stdin.take() { for entry in dns { debug!("Adding nameserver entry: {entry}"); @@ -53,9 +55,11 @@ pub(crate) fn configure_dns( writeln!(stdin, "search {domain}")?; } } + debug!("Waiting for resolvconf command to finish"); let status = child.wait().expect("Failed to wait for command"); if status.success() { + info!("DNS servers and search domains set successfully for interface {ifname}"); Ok(()) } else { Err(WireguardInterfaceError::DnsError(format!("Failed to execute resolvconf command while setting DNS servers and search domains: {status}"))) @@ -70,6 +74,7 @@ pub(crate) fn configure_dns( #[cfg(target_os = "macos")] /// Obtain list of network services fn network_services() -> Result, IoError> { + debug!("Getting list of network services"); let output = Command::new("networksetup") .arg("-listallnetworkservices") .output()?; @@ -82,7 +87,7 @@ fn network_services() -> Result, IoError> { .lines() .filter_map(|line| line.ok().filter(|line| !line.contains('*'))) .collect(); - + debug!("Network services: {lines:?}"); Ok(lines) } else { Err(IoError::other(format!( @@ -114,6 +119,7 @@ pub(crate) fn configure_dns( "Command `networksetup` failed while setting DNS servers for {service}: {status}" ))); } + info!("DNS servers set successfully for {service}"); // Set search domains, if empty, clear all search domains. debug!("Setting search domains for {service}"); @@ -130,6 +136,8 @@ pub(crate) fn configure_dns( if !status.success() { return Err(WireguardInterfaceError::DnsError(format!("Command `networksetup` failed while setting search domains for {service}: {status}"))); } + + info!("Search domains set successfully for {service}"); } Ok(()) @@ -137,12 +145,13 @@ pub(crate) fn configure_dns( #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] pub(crate) fn clear_dns(ifname: &str) -> Result<(), WireguardInterfaceError> { - info!("Removing DNS configuration for interface {ifname}"); + debug!("Removing DNS configuration for interface {ifname}"); let args = ["-d", ifname, "-f"]; debug!("Executing resolvconf with args: {args:?}"); let mut cmd = Command::new("resolvconf"); let output = cmd.args(args).output()?; check_command_output_status(output)?; + info!("DNS configuration removed successfully for interface {ifname}"); Ok(()) } @@ -169,16 +178,21 @@ pub(crate) fn add_peer_routing( unique_allowed_ips.insert(addr); } } + debug!("Allowed IPs that will be used during the peer routing setup: {unique_allowed_ips:?}"); // If there is default route skip adding other routes. if let Some(default_route) = default_route { - debug!("Found default route: {default_route:?}"); + debug!("Found default route in AllowedIPs: {default_route:?}"); let is_ipv6 = default_route.ip.is_ipv6(); let proto = if is_ipv6 { "-6" } else { "-4" }; + debug!("Using the following IP version: {proto}"); + debug!("Getting current host configuration for interface {ifname}"); let mut host = netlink::get_host(ifname)?; - debug!("Current host: {host:?}"); + debug!("Host configuration read for interface {ifname}"); + trace!("Current host: {host:?}"); + debug!("Choosing fwmark for marking WireGuard traffic"); let fwmark = match host.fwmark { Some(fwmark) if fwmark != 0 => fwmark, Some(_) | None => { @@ -198,34 +212,33 @@ pub(crate) fn add_peer_routing( table } }; - debug!("Using fwmark: {fwmark}"); - // Add table rules - debug!("Adding route for allowed IP: {default_route}"); + debug!("Using the following fwmark for marking WireGuard traffic: {fwmark}"); + + // Add routes and table rules + debug!("Adding default route: {default_route}"); netlink::add_route(ifname, default_route, Some(fwmark))?; - netlink::add_rule(default_route, fwmark)?; + debug!("Default route added successfully"); + debug!("Adding fwmark rule for the WireGuard interface to prevent routing loops"); + netlink::add_fwmark_rule(default_route, fwmark)?; + debug!("Fwmark rule added successfully"); - debug!("Adding rule for main table"); + debug!("Adding rule for main table to suppress current default gateway"); netlink::add_main_table_rule(default_route, 0)?; + debug!("Main table rule added successfully"); - if is_ipv6 { - debug!("Reloading ip6tables"); - let output = Command::new("ip6tables-restore").arg("-n").output()?; - check_command_output_status(output)?; - } else { - debug!("Setting systemctl net.ipv4.conf.all.src_valid_mark=1"); - let output = Command::new("sysctl") - .args(["-q", "net.ipv4.conf.all.src_valid_mark=1"]) - .output()?; - check_command_output_status(output)?; - - debug!("Reloading iptables"); - let output = Command::new("iptables-restore").arg("-n").output()?; - check_command_output_status(output)?; + if !is_ipv6 { + debug!("Setting net.ipv4.conf.all.src_valid_mark=1"); + OpenOptions::new() + .write(true) + .open("/proc/sys/net/ipv4/conf/all/src_valid_mark")? + .write_all(b"1")?; + debug!("net.ipv4.conf.all.src_valid_mark=1 set successfully"); } } else { for allowed_ip in unique_allowed_ips { - debug!("Processing allowed IP: {allowed_ip}"); + debug!("Adding a route for allowed IP: {allowed_ip}"); netlink::add_route(ifname, allowed_ip, None)?; + debug!("Route added for allowed IP: {allowed_ip}"); } } info!("Peers routing added successfully"); diff --git a/src/wgapi.rs b/src/wgapi.rs index 87e82e6..d02e196 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -1,7 +1,7 @@ //! Shared multi-platform management API abstraction use std::marker::PhantomData; -use crate::error::WireguardInterfaceError; +use crate::{dependencies::check_external_dependencies, error::WireguardInterfaceError}; pub struct Kernel; pub struct Userspace; @@ -18,6 +18,7 @@ pub struct WGApi { impl WGApi { /// Create new instance of `WGApi`. pub fn new(ifname: String) -> Result { + check_external_dependencies()?; Ok(WGApi { ifname, _api: PhantomData, diff --git a/src/wgapi_freebsd.rs b/src/wgapi_freebsd.rs index ca0f1ff..f4a6667 100644 --- a/src/wgapi_freebsd.rs +++ b/src/wgapi_freebsd.rs @@ -14,14 +14,19 @@ use crate::{ impl WireguardInterfaceApi for WGApi { /// Creates a WireGuard network interface. fn create_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Creating interface {}", &self.ifname); + debug!("Creating interface {}", &self.ifname); bsd::create_interface(&self.ifname)?; + info!("Interface {} created successfully", &self.ifname); Ok(()) } fn assign_address(&self, address: &IpAddrMask) -> Result<(), WireguardInterfaceError> { debug!("Assigning address {address} to interface {}", self.ifname); bsd::assign_address(&self.ifname, address)?; + info!( + "Address {address} assigned to interface {} successfully", + self.ifname + ); Ok(()) } @@ -40,7 +45,12 @@ impl WireguardInterfaceApi for WGApi { /// ## Note: /// Based on ip type `` will be equal to `-inet` or `-inet6` fn configure_peer_routing(&self, peers: &[Peer]) -> Result<(), WireguardInterfaceError> { + debug!("Configuring peer routing for interface {}", self.ifname); add_peer_routing(peers, &self.ifname)?; + info!( + "Peer routing configured successfully for interface {}", + self.ifname + ); Ok(()) } @@ -48,54 +58,83 @@ impl WireguardInterfaceApi for WGApi { &self, config: &InterfaceConfiguration, ) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Configuring interface {} with config: {config:?}", self.ifname ); // assign IP address to interface + debug!( + "Parsing address {} for interface {}", + config.address, self.ifname + ); let address = IpAddrMask::from_str(&config.address)?; + debug!( + "Address {} parsed successfully for interface {}", + config.address, self.ifname + ); self.assign_address(&address)?; - // configure interface + debug!("Setting host configuration for interface {}", self.ifname); let host = config.try_into()?; bsd::set_host(&self.ifname, &host)?; + debug!("Host configuration set for interface {}.", self.ifname); + trace!("Host configuration: {host:?}"); // Set maximum transfer unit (MTU). if let Some(mtu) = config.mtu { + debug!("Setting MTU of {mtu} for interface {}", self.ifname); bsd::set_mtu(&self.ifname, mtu)?; + debug!("MTU of {mtu} set for interface {}, value: {mtu}"); } + info!( + "Interface {} configured successfully with config: {config:?}", + self.ifname + ); Ok(()) } /// Remove WireGuard network interface. fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Removing interface {}", &self.ifname); + debug!("Removing interface {}", &self.ifname); bsd::delete_interface(&self.ifname)?; + debug!("Interface {} removed successfully", &self.ifname); clear_dns(&self.ifname)?; + + info!("Interface {} removed successfully", &self.ifname); Ok(()) } fn configure_peer(&self, peer: &Peer) -> Result<(), WireguardInterfaceError> { - info!("Configuring peer {peer:?} on interface {}", self.ifname); + debug!("Configuring peer {peer:?} on interface {}", self.ifname); bsd::set_peer(&self.ifname, peer)?; + info!( + "Peer {peer:?} configured successfully on interface {}", + self.ifname + ); Ok(()) } fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Removing peer with public key {peer_pubkey} from interface {}", self.ifname ); bsd::delete_peer(&self.ifname, peer_pubkey)?; + info!( + "Peer with public key {peer_pubkey} removed successfully from interface {}", + self.ifname + ); Ok(()) } fn read_interface_data(&self) -> Result { debug!("Reading host info for interface {}", self.ifname); let host = bsd::get_host(&self.ifname)?; + debug!("Host info read for interface {}", self.ifname); + trace!("Host configuration: {host:?}"); Ok(host) } @@ -113,10 +152,6 @@ impl WireguardInterfaceApi for WGApi { warn!("Received empty DNS server list. Skipping DNS configuration..."); return Ok(()); } - info!( - "Configuring DNS for interface {}, using address: {dns:?}", - self.ifname - ); configure_dns(&self.ifname, dns, search_domains)?; Ok(()) } diff --git a/src/wgapi_linux.rs b/src/wgapi_linux.rs index 6e7b44d..605a8d8 100644 --- a/src/wgapi_linux.rs +++ b/src/wgapi_linux.rs @@ -1,4 +1,4 @@ -use std::{net::IpAddr, str::FromStr}; +use std::{marker::PhantomData, net::IpAddr, process::Command, str::FromStr}; use crate::{ netlink, @@ -14,14 +14,19 @@ use crate::{ /// Requires Linux kernel version 5.6+. impl WireguardInterfaceApi for WGApi { fn create_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Creating interface {}", self.ifname); + debug!("Creating interface {}", self.ifname); netlink::create_interface(&self.ifname)?; + info!("Interface {} created successfully", self.ifname); Ok(()) } fn assign_address(&self, address: &IpAddrMask) -> Result<(), WireguardInterfaceError> { debug!("Assigning address {address} to interface {}", self.ifname); netlink::address_interface(&self.ifname, address)?; + info!( + "Address {address} assigned to interface {} successfully", + self.ifname + ); Ok(()) } @@ -29,83 +34,120 @@ impl WireguardInterfaceApi for WGApi { &self, config: &InterfaceConfiguration, ) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Configuring interface {} with config: {config:?}", self.ifname ); // flush all IP addresses + debug!("Flushing all IP addresses from interface {}", self.ifname); netlink::flush_interface(&self.ifname)?; + debug!("All IP addresses flushed from interface {}", self.ifname); // assign IP address to interface + debug!( + "Assigning address {} to interface {}", + config.address, self.ifname + ); let address = IpAddrMask::from_str(&config.address)?; self.assign_address(&address)?; + debug!( + "Address {} assigned to interface {} successfully", + config.address, self.ifname + ); // configure interface + debug!("Setting host configuration for interface {}", self.ifname); let host = config.try_into()?; netlink::set_host(&self.ifname, &host)?; + debug!("Host configuration set for interface {}.", self.ifname); + trace!("Host configuration: {host:?}"); // set maximum transfer unit if let Some(mtu) = config.mtu { + debug!("Setting MTU of {mtu} for interface {}", self.ifname); netlink::set_mtu(&self.ifname, mtu)?; + debug!("MTU of {mtu} set for interface {}, value: {{", self.ifname); + } else { + debug!( + "Skipping setting the MTU for interface {}, as it has not been provided", + self.ifname + ); } + info!( + "Interface {} configured successfully with config: {config:?}", + self.ifname + ); + Ok(()) } - /// On a Linux system, the `sysctl` command is required to work if using `0.0.0.0/0` or `::/0`. - /// For every allowed IP, it runs: - /// `ip route add dev ` - /// `` - interface name while creating api - /// `` - `-4` or `-6` based on allowed ip type - /// ``- one of [Peer](crate::Peer) allowed ip + /// Configures peer routing. Internally uses netlink to set up routing rules for each peer. + /// If allowed IPs contain a default route, instead of adding a route for every peer, the following changes are made: + /// - A new default route is added + /// - The current default route is suppressed by modifying the main routing table rule with `suppress_prefixlen 0`, this makes + /// it so that the whole main routing table rules are still applied except for the default route rules (so the new default route is used instead) + /// - A rule pushing all traffic through the WireGuard interface is added with the exception of traffic marked with 51820 (default) fwmark which + /// is used for the WireGuard traffic itself (so it doesn't get stuck in a loop) /// - /// For `0.0.0.0/0` or `::/0` allowed IP, it adds default routing and skips other routings.: - /// - `ip route add 0.0.0.0/0 dev table ` - /// `` - fwmark attribute of [Host](crate::Host) or 51820 default if value is `None`. - /// `` - Interface name. - /// - `ip rule add not fwmark table `. - /// - `ip rule add table main suppress_prefixlength 0`. - /// - `sysctl -q net.ipv4.conf.all.src_valid_mark=1` - runs only for `0.0.0.0/0`. - /// - `iptables-restore -n`. For `0.0.0.0/0` only. - /// - `iptables6-restore -n`. For `::/0` only. - /// Based on ip type `` will be equal to `-4` or `-6`. fn configure_peer_routing(&self, peers: &[Peer]) -> Result<(), WireguardInterfaceError> { add_peer_routing(peers, &self.ifname)?; Ok(()) } fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Removing interface {}", self.ifname); + debug!( + "Removing interface {}. Getting its host configuration first...", + self.ifname + ); let host = netlink::get_host(&self.ifname)?; + debug!("Host configuration read for interface {}", self.ifname); + trace!("Host configuration: {host:?}"); if let Some(fwmark) = host.fwmark { if fwmark != 0 { + debug!("Cleaning fwmark rules for interface {}", self.ifname); clean_fwmark_rules(fwmark)?; + debug!("Fwmark rules cleaned for interface {}", self.ifname); } } + debug!("Performing removal of interface {}", self.ifname); netlink::delete_interface(&self.ifname)?; + debug!( + "Interface {} removed successfully. Clearing the dns...", + self.ifname + ); clear_dns(&self.ifname)?; + debug!("DNS cleared for interface {}", self.ifname); + + info!("Interface {} removed successfully", self.ifname); Ok(()) } fn configure_peer(&self, peer: &Peer) -> Result<(), WireguardInterfaceError> { - info!("Configuring peer {peer:?} on interface {}", self.ifname); + debug!("Configuring peer {peer:?} on interface {}", self.ifname); netlink::set_peer(&self.ifname, peer)?; + info!("Peer {peer:?} configured on interface {}", self.ifname); Ok(()) } fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Removing peer with public key {peer_pubkey} from interface {}", self.ifname ); netlink::delete_peer(&self.ifname, peer_pubkey)?; + info!( + "Peer with public key {peer_pubkey} removed from interface {}", + self.ifname + ); Ok(()) } fn read_interface_data(&self) -> Result { debug!("Reading host info for interface {}", self.ifname); let host = netlink::get_host(&self.ifname)?; + debug!("Host info read for interface {}", self.ifname); Ok(host) } @@ -123,10 +165,6 @@ impl WireguardInterfaceApi for WGApi { warn!("Received empty DNS server list. Skipping DNS configuration..."); return Ok(()); } - info!( - "Configuring DNS for interface {}, using address: {dns:?}", - self.ifname - ); configure_dns(&self.ifname, dns, search_domains)?; Ok(()) } diff --git a/src/wgapi_userspace.rs b/src/wgapi_userspace.rs index 2a5cd55..9028784 100644 --- a/src/wgapi_userspace.rs +++ b/src/wgapi_userspace.rs @@ -17,6 +17,7 @@ use crate::utils::clear_dns; use crate::{bsd, utils::resolve}; use crate::{ check_command_output_status, + dependencies::check_external_dependencies, error::WireguardInterfaceError, utils::{add_peer_routing, configure_dns}, wgapi::{Userspace, WGApi}, @@ -36,12 +37,7 @@ impl WGApi { /// # Errors /// Will return `WireguardInterfaceError` if `wireguard-go` can't be found. pub fn new(ifname: String) -> Result { - // check that `wireguard-go` is available - Command::new(USERSPACE_EXECUTABLE).arg("--version").output().map_err(|err| { - error!("Failed to create userspace API. {USERSPACE_EXECUTABLE} executable not found in PATH. Error: {err}"); - WireguardInterfaceError::ExecutableNotFound(USERSPACE_EXECUTABLE.into()) - })?; - + check_external_dependencies()?; Ok(WGApi { ifname, _api: PhantomData, @@ -54,26 +50,35 @@ impl WGApi { /// Create UNIX socket to communicate with `wireguard-go`. fn socket(&self) -> io::Result { + debug!("Creating socket for interface {}", self.ifname); let path = self.socket_path(); let socket = UnixStream::connect(path)?; socket.set_read_timeout(Some(Duration::new(3, 0)))?; + debug!("Socket created for interface {}", self.ifname); Ok(socket) } // FIXME: currently other errors are ignored and result in 0 being returned. fn parse_errno(buf: impl Read) -> u32 { + debug!("Parsing errno from buffer"); let reader = BufReader::new(buf); for line_result in reader.lines() { let line = match line_result { Ok(line) => line, Err(err) => { - error!("Error parsing buffer line: {err}"); + error!("Error parsing errno buffer line: {err}, continuing with next line..."); continue; } }; if let Some((keyword, value)) = line.split_once('=') { if keyword == "errno" { - return value.parse().unwrap_or_default(); + match value.parse() { + Ok(errno) => return errno, + Err(err) => { + error!("Failed to parse errno: {err}, using default value 0"); + return 0; + } + } } } } @@ -108,11 +113,12 @@ impl WGApi { impl WireguardInterfaceApi for WGApi { fn create_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Creating userspace interface {}", self.ifname); + debug!("Creating userspace interface {}", self.ifname); let output = Command::new(USERSPACE_EXECUTABLE) .arg(&self.ifname) .output()?; check_command_output_status(output)?; + info!("Userspace interface {} created successfully", self.ifname); Ok(()) } @@ -140,19 +146,24 @@ impl WireguardInterfaceApi for WGApi { warn!("Received empty DNS server list. Skipping DNS configuration..."); return Ok(()); } - info!( + debug!( "Configuring DNS for interface {}, using address: {dns:?}", self.ifname ); // Setting DNS is not supported for macOS. #[cfg(target_os = "macos")] { - configure_dns(dns, search_domains) + configure_dns(dns, search_domains)?; } #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] { - configure_dns(&self.ifname, dns, search_domains) + configure_dns(&self.ifname, dns, search_domains)?; } + info!( + "DNS configured for interface {}, using address: {dns:?}", + self.ifname + ); + Ok(()) } /// Assign IP address to network interface. @@ -162,6 +173,7 @@ impl WireguardInterfaceApi for WGApi { bsd::assign_address(&self.ifname, address)?; #[cfg(target_os = "linux")] netlink::address_interface(&self.ifname, address)?; + info!("Address {address} assigned to interface {}", self.ifname); Ok(()) } @@ -171,7 +183,7 @@ impl WireguardInterfaceApi for WGApi { &self, config: &InterfaceConfiguration, ) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Configuring interface {} with config: {config:?}", self.ifname ); @@ -181,17 +193,30 @@ impl WireguardInterfaceApi for WGApi { self.assign_address(&address)?; // configure interface + debug!("Setting host configuration for interface {}", self.ifname); let host = config.try_into()?; self.write_host(&host)?; + debug!("Host configuration set for interface {}.", self.ifname); + trace!("Host configuration: {host:?}"); // Set maximum transfer unit (MTU). if let Some(mtu) = config.mtu { + debug!("Setting MTU of {mtu} for interface {}", self.ifname); #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] bsd::set_mtu(&self.ifname, mtu)?; #[cfg(target_os = "linux")] netlink::set_mtu(&self.ifname, mtu)?; + debug!( + "MTU of {mtu} set for interface {}, value: {mtu}", + self.ifname + ); } + info!( + "Interface {} configured successfully with config: {config:?}", + self.ifname + ); + Ok(()) } @@ -249,14 +274,18 @@ impl WireguardInterfaceApi for WGApi { /// Remove WireGuard network interface. fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Removing interface {}", self.ifname); + debug!("Removing interface {}", self.ifname); // `wireguard-go` should by design shut down if the socket is removed + debug!("Shutting down socket for interface {}", self.ifname); let socket = self.socket()?; socket.shutdown(Shutdown::Both).map_err(|err| { - error!("Failed to shutdown socket: {err}"); - WireguardInterfaceError::UnixSockerError(err.to_string()) + WireguardInterfaceError::UnixSockerError(format!( + "Failed to shutdown socket for interface {}: {err}", + self.ifname + )) })?; fs::remove_file(self.socket_path())?; + debug!("Socket shutdown for interface {}", self.ifname); #[cfg(target_os = "macos")] { configure_dns(&[], &[])?; @@ -266,11 +295,12 @@ impl WireguardInterfaceApi for WGApi { clear_dns(&self.ifname)?; } + info!("Interface {} removed successfully", self.ifname); Ok(()) } fn configure_peer(&self, peer: &Peer) -> Result<(), WireguardInterfaceError> { - info!("Configuring peer {peer:?} on interface {}", self.ifname); + debug!("Configuring peer {peer:?} on interface {}", self.ifname); let mut socket = self.socket()?; socket.write_all(b"set=1\n")?; socket.write_all(peer.as_uapi_update().as_bytes())?; @@ -278,6 +308,7 @@ impl WireguardInterfaceApi for WGApi { let errno = Self::parse_errno(socket); if errno == 0 { + info!("Peer {peer:?} configured on interface {}", self.ifname); Ok(()) } else { Err(WireguardInterfaceError::PeerConfigurationError(format!( @@ -288,7 +319,7 @@ impl WireguardInterfaceApi for WGApi { } fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Removing peer with public key {peer_pubkey} from interface {}", self.ifname ); @@ -302,6 +333,10 @@ impl WireguardInterfaceApi for WGApi { let errno = Self::parse_errno(socket); if errno == 0 { + info!( + "Peer with public key {peer_pubkey} removed from interface {}", + self.ifname + ); Ok(()) } else { Err(WireguardInterfaceError::PeerConfigurationError(format!( @@ -314,11 +349,15 @@ impl WireguardInterfaceApi for WGApi { fn read_interface_data(&self) -> Result { debug!("Reading host info for interface {}", self.ifname); match self.read_host() { - Ok(host) => Ok(host), - Err(err) => { - error!("Failed to read interface {} data: {err}", self.ifname); - Err(WireguardInterfaceError::ReadInterfaceError(err.to_string())) + Ok(host) => { + debug!("Host info read for interface {}", self.ifname); + trace!("Host info: {host:?}"); + Ok(host) } + Err(err) => Err(WireguardInterfaceError::ReadInterfaceError(format!( + "Failed to read interface {} data, error: {err}", + self.ifname + ))), } } } diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs index 9c09f9e..b98f52a 100644 --- a/src/wgapi_windows.rs +++ b/src/wgapi_windows.rs @@ -36,7 +36,7 @@ impl WireguardInterfaceApi for WGApi { dns: &[IpAddr], search_domains: &[&str], ) -> Result<(), WireguardInterfaceError> { - info!( + debug!( "Configuring interface {} with config: {config:?}", self.ifname ); @@ -51,6 +51,8 @@ impl WireguardInterfaceApi for WGApi { let mut file = File::create(&file_name)?; + debug!("WireGuard configuration file {file_name} created in {file_path}. Preparing configuration..."); + let mut wireguard_configuration = format!( "[Interface]\nPrivateKey = {}\nAddress = {}\n", config.prvkey, config.address @@ -117,10 +119,17 @@ impl WireguardInterfaceApi for WGApi { } } + debug!( + "WireGuard configuration prepared: {wireguard_configuration}, writing to the file at {file_path}..." + ); file.write_all(wireguard_configuration.as_bytes())?; info!("WireGuard configuration written to file: {file_path}",); // Check for existing service and remove it + debug!( + "Checking for existing wireguard service for interface {}", + self.ifname + ); let output = Command::new("wg") .arg("show") .arg(&self.ifname) @@ -129,14 +138,17 @@ impl WireguardInterfaceApi for WGApi { error!("Failed to read interface data. Error: {err}"); WireguardInterfaceError::ReadInterfaceError(err.to_string()) })?; + debug!("WireGuard service check output: {output:?}",); // Service already exists if output.status.success() { + debug!("Service already exists, removing it first"); Command::new("wireguard") .arg("/uninstalltunnelservice") .arg(&self.ifname) .output()?; + debug!("Waiting for service to be removed"); let mut counter = 1; loop { // Occasionally the tunnel is still available even though wg show cannot find it, causing /installtunnelservice to fail @@ -159,8 +171,10 @@ impl WireguardInterfaceApi for WGApi { counter = counter + 1; } + debug!("Finished waiting for service to be removed, the service is considered to be removed, proceeding further"); } + debug!("Installing the new service for interface {}", self.ifname); let service_installation_output = Command::new("wireguard") .arg("/installtunnelservice") .arg(file_path) @@ -171,7 +185,7 @@ impl WireguardInterfaceApi for WGApi { WireguardInterfaceError::ServiceInstallationFailed { err, message } })?; - info!("Service installation output: {service_installation_output:?}",); + debug!("Done installing the new service. Service installation output: {service_installation_output:?}",); if !service_installation_output.status.success() { let message = format!( @@ -186,6 +200,10 @@ impl WireguardInterfaceApi for WGApi { // TODO: set maximum transfer unit (MTU) + info!( + "Interface {} configured successfully with config: {config:?}", + self.ifname + ); Ok(()) } @@ -194,7 +212,7 @@ impl WireguardInterfaceApi for WGApi { } fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { - info!("Removing interface {}", self.ifname); + debug!("Removing interface {}", self.ifname); Command::new("wireguard") .arg("/uninstalltunnelservice") @@ -205,6 +223,7 @@ impl WireguardInterfaceApi for WGApi { WireguardInterfaceError::CommandExecutionFailed(err) })?; + info!("Interface {} removed successfully", self.ifname); Ok(()) }