Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more logs, check required commands availability #76

Merged
merged 12 commits into from
Oct 7, 2024
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
41 changes: 41 additions & 0 deletions src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::process::Command;

use crate::error::WireguardInterfaceError;

#[cfg(target_os = "linux")]
const COMMANDS: [(&str, &str); 2] = [
("resolvconf", "--version"),
("ip", "help"),
];

// There is no Windows command to check the version of WireGuard.
// The "/" argument (or any other non-existent argument) normally returns a help message popup. However when invoked
// by means of rust's std::process::Command, it only results in an `Ok` output (or an `Err` if the command is not found),
// allowing us to check if the command is available.
#[cfg(target_os = "windows")]
const COMMANDS: [(&str, &str); 1] = [("wireguard", "/")];

#[cfg(target_os = "macos")]
const COMMANDS: [(&str, &str); 2] = [("wireguard-go", "--version"), ("networksetup", "-version")];

#[cfg(target_os = "freebsd")]
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
const COMMANDS: [(&str, &str); 0] = [];
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved

/// Check if the commands/executables required for interface management are available.
pub(crate) fn check_external_dependencies() -> Result<(), WireguardInterfaceError> {
for (cmd, arg) in COMMANDS.iter() {
debug!(
"Checking if command `{}` is available by running: {} {}",
cmd, cmd, arg
);
Command::new(cmd).arg(arg).output().map_err(|err| {
WireguardInterfaceError::MissingDependency(format!(
"Command `{}` required by wireguard-rs couldn't be found, details: {}",
cmd, err
))
})?;
debug!("Command `{}` is available", cmd);
}

Ok(())
}
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,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
62 changes: 37 additions & 25 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::net::{Ipv4Addr, Ipv6Addr};
use std::{io::Write, process::Stdio};
use std::{
net::{IpAddr, SocketAddr, ToSocketAddrs},
fs::OpenOptions,
process::Command,
};

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");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
let fwmark = match host.fwmark {
Some(fwmark) if fwmark != 0 => fwmark,
Some(_) | None => {
Expand All @@ -198,34 +212,32 @@ 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}");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved

// 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");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
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 by writing to /proc/sys/net/ipv4/conf/all/src_valid_mark");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
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}");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
netlink::add_route(ifname, allowed_ip, None)?;
debug!("Route added for allowed ip: {allowed_ip}");
t-aleksander marked this conversation as resolved.
Show resolved Hide resolved
}
}
info!("Peers routing added successfully");
Expand Down
4 changes: 2 additions & 2 deletions 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 @@ -17,8 +17,8 @@ pub struct WGApi<API = Kernel> {

impl WGApi {
/// Create new instance of `WGApi`.
#[must_use]
pub fn new(ifname: String) -> Result<Self, WireguardInterfaceError> {
check_external_dependencies()?;
Ok(WGApi {
ifname,
_api: PhantomData,
Expand Down
Loading
Loading