Skip to content

Commit

Permalink
Add more logs, check required commands availability (#76)
Browse files Browse the repository at this point in the history
* 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 <aciarcinski@teonite.com>

* fmt

* don't invoke commands to test if they exist

* Update dependencies.rs

* bump

---------

Co-authored-by: Adam <aciarcinski@teonite.com>
  • Loading branch information
t-aleksander and moubctez authored Oct 7, 2024
1 parent 1bd58e2 commit cc37249
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[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(())
Expand Down
55 changes: 55 additions & 0 deletions src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
5 changes: 2 additions & 3 deletions src/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ where
let mut offset = 0;
loop {
let response = NetlinkMessage::<I>::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),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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()
};

Expand Down
67 changes: 40 additions & 27 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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}");
Expand All @@ -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}")))
Expand All @@ -70,6 +74,7 @@ pub(crate) fn configure_dns(
#[cfg(target_os = "macos")]
/// Obtain list of network services
fn network_services() -> Result<Vec<String>, IoError> {
debug!("Getting list of network services");
let output = Command::new("networksetup")
.arg("-listallnetworkservices")
.output()?;
Expand All @@ -82,7 +87,7 @@ fn network_services() -> Result<Vec<String>, IoError> {
.lines()
.filter_map(|line| line.ok().filter(|line| !line.contains('*')))
.collect();

debug!("Network services: {lines:?}");
Ok(lines)
} else {
Err(IoError::other(format!(
Expand Down Expand Up @@ -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}");
Expand All @@ -130,19 +136,22 @@ 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(())
}

#[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(())
}

Expand All @@ -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 => {
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion src/wgapi.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,6 +18,7 @@ pub struct WGApi<API = Kernel> {
impl WGApi {
/// Create new instance of `WGApi`.
pub fn new(ifname: String) -> Result<Self, WireguardInterfaceError> {
check_external_dependencies()?;
Ok(WGApi {
ifname,
_api: PhantomData,
Expand Down
Loading

0 comments on commit cc37249

Please sign in to comment.