From 8295a8328531bd105f0ae881226781d9a5d38593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 5 Sep 2025 10:24:16 +0200 Subject: [PATCH 1/4] Skip routing to endpoint --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/net.rs | 37 +++++++++++++++++++++++++++++++++++++ src/utils.rs | 12 ++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f2bc08..c8c8329 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -146,7 +146,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.7.6" +version = "0.7.7" dependencies = [ "base64", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index b4c3844..44c8d24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.7.6" +version = "0.7.7" edition = "2024" rust-version = "1.85" description = "A unified multi-platform high-level API for managing WireGuard interfaces" diff --git a/src/net.rs b/src/net.rs index e4ac646..0541d90 100644 --- a/src/net.rs +++ b/src/net.rs @@ -98,6 +98,30 @@ impl IpAddrMask { } } + /// Check if given address fits in `IpAddrMask`. + #[must_use] + pub fn contains(&self, other: IpAddr) -> bool { + match (self.ip, other) { + (IpAddr::V4(ip), IpAddr::V4(other)) => { + let mask = if self.cidr == 0 { + 0 + } else { + u32::MAX << (32 - self.cidr) + }; + (ip.to_bits() & mask) == (other.to_bits() & mask) + } + (IpAddr::V6(ip), IpAddr::V6(other)) => { + let mask = if self.cidr == 0 { + 0 + } else { + u128::MAX << (128 - self.cidr) + }; + (ip.to_bits() & mask) == (other.to_bits() & mask) + } + _ => false, + } + } + #[cfg(target_os = "linux")] #[must_use] pub fn to_nlas_allowed_ip(&self) -> WgAllowedIp { @@ -261,4 +285,17 @@ mod tests { )) ); } + + #[test] + fn addr_mask_contains() { + let ip = IpAddrMask::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 24); + assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)))); + assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)))); + assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 254)))); + assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 255)))); + assert!(!ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))); + + let ip = IpAddrMask::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); + assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))); + } } diff --git a/src/utils.rs b/src/utils.rs index 758e29b..992a301 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -402,6 +402,18 @@ pub(crate) fn add_peer_routing( }, } } else { + // Check if allowed IPs contain endpoint's address. + if let Some(endpoint) = peer.endpoint { + let endpoint_ip = endpoint.ip(); + if addr.contains(endpoint_ip) { + warn!( + "Not adding route to {addr} because it contains endpoint {}", + endpoint_ip + ); + continue; + } + } + // Equivalent to `route -n add -inet[6] -interface `. match add_linked_route(addr, ifname) { Ok(()) => debug!("Route to {addr} has been added for interface {ifname}"), From 1f08388f1bf9724778552dbdf8784722a88153f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 15 Sep 2025 13:21:14 +0200 Subject: [PATCH 2/4] Cleanup --- Cargo.lock | 26 ++++++++++++++++++-------- src/utils.rs | 3 +-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8c8329..05f1000 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,24 +433,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.223" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.223" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.223" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56" dependencies = [ "proc-macro2", "quote", @@ -505,9 +515,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "utf8parse" diff --git a/src/utils.rs b/src/utils.rs index 992a301..9a85607 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -407,8 +407,7 @@ pub(crate) fn add_peer_routing( let endpoint_ip = endpoint.ip(); if addr.contains(endpoint_ip) { warn!( - "Not adding route to {addr} because it contains endpoint {}", - endpoint_ip + "Not adding route to {addr} because it contains endpoint {endpoint_ip}" ); continue; } From 03edffd09e6e1de01c43746bdf3e34c0560e1499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 22 Sep 2025 11:04:31 +0200 Subject: [PATCH 3/4] Always route endpoint through default gateway --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/bsd/mod.rs | 8 +- src/utils.rs | 226 +++++++++++++++++++++++-------------------------- 4 files changed, 109 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a52a99..ba3c452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,7 +146,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.7.7" +version = "0.7.8" dependencies = [ "base64", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 44c8d24..a763629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.7.7" +version = "0.7.8" edition = "2024" rust-version = "1.85" description = "A unified multi-platform high-level API for managing WireGuard interfaces" diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index 2b479c3..31c9512 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -458,9 +458,7 @@ pub fn get_gateway(ip_version: IpVersion) -> Result, IoError> { /// Add routing gateway. pub fn add_gateway(dest: &IpAddrMask, gateway: IpAddr, is_blackhole: bool) -> Result<(), IoError> { - debug!( - "Adding gateway, destination: {dest}, gateway: {gateway}, is blackhole: {is_blackhole}..." - ); + debug!("Adding gateway: destination {dest}, gateway {gateway}, is blackhole {is_blackhole}."); match (dest.ip, dest.mask(), gateway) { (IpAddr::V4(ip), IpAddr::V4(mask), IpAddr::V4(gw)) => { let payload = DestAddrMask::::new(ip.into(), mask.into(), gw.into()); @@ -481,7 +479,7 @@ pub fn add_gateway(dest: &IpAddrMask, gateway: IpAddr, is_blackhole: bool) -> Re /// Remove routing gateway. pub fn delete_gateway(dest: &IpAddrMask) -> Result<(), IoError> { - debug!("Deleting gateway with destination {dest}..."); + debug!("Deleting gateway with destination {dest}."); match (dest.ip, dest.mask()) { (IpAddr::V4(ip), IpAddr::V4(mask)) => { let payload = @@ -504,7 +502,7 @@ pub fn delete_gateway(dest: &IpAddrMask) -> Result<(), IoError> { /// Add link layer address gateway. pub fn add_linked_route(dest: &IpAddrMask, if_name: &str) -> Result<(), IoError> { - debug!("Adding link layer gateway, destination: {dest}, interface: {if_name}"); + debug!("Adding link layer gateway: destination {dest}, interface {if_name}"); let name = CString::new(if_name).unwrap(); let if_index = unsafe { libc::if_nametoindex(name.as_ptr()) as u16 }; if if_index == 0 { diff --git a/src/utils.rs b/src/utils.rs index 9a85607..52c96da 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -323,13 +323,18 @@ pub(crate) fn add_peer_routing( use crate::bsd::{IoError, delete_gateway}; + let gateway_v4 = get_gateway(IpVersion::IPv4); + if let Ok(Some(gateway)) = gateway_v4 { + debug!("Default gateway for IPv4: {gateway}"); + } + let gateway_v6 = get_gateway(IpVersion::IPv6); + if let Ok(Some(gateway)) = gateway_v6 { + debug!("Default gateway for IPv4: {gateway}"); + } + debug!("Adding peer routing for interface: {ifname}"); for peer in peers { debug!("Processing peer: {}", peer.public_key); - let mut default_route_v4 = false; - let mut default_route_v6 = false; - let mut gateway_v4 = Ok(None); - let mut gateway_v6 = Ok(None); for addr in &peer.allowed_ips { debug!("Processing route for allowed IP: {addr}, interface: {ifname}"); // FIXME: currently it is impossible to add another default route, so use the hack from @@ -337,7 +342,7 @@ pub(crate) fn add_peer_routing( if addr.ip.is_unspecified() && addr.cidr == 0 { debug!( "Found following default route in the allowed IPs: {addr}, interface: \ - {ifname}, proceeding with default route initial setup..." + {ifname}, proceeding with default route initial setup." ); let default1; let default2; @@ -346,36 +351,29 @@ pub(crate) fn add_peer_routing( default1 = IpAddrMask::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 1); // 128.0.0.0/1 default2 = IpAddrMask::new(IpAddr::V4(Ipv4Addr::new(128, 0, 0, 0)), 1); - gateway_v4 = get_gateway(IpVersion::IPv4); - debug!("Default gateway for IPv4 value: {gateway_v4:?}"); - default_route_v4 = true; } else { // ::/1 default1 = IpAddrMask::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 1); // 8000::/1 default2 = IpAddrMask::new(IpAddr::V6(Ipv6Addr::new(0x8000, 0, 0, 0, 0, 0, 0, 0)), 1); - gateway_v6 = get_gateway(IpVersion::IPv6); - debug!("Default gateway for IPv6 value: {gateway_v6:?}"); - default_route_v6 = true; } match add_linked_route(&default1, ifname) { Ok(()) => debug!("Route to {default1} has been added for interface {ifname}"), Err(err) => match err { IoError::WriteIo(Errno::ENETUNREACH) => { warn!( - "Failed to add default route {default1} for interface \ - {ifname}: Network is unreachable. This may happen if your \ - interface's IP address is not the same IP version as the \ - default gateway ({default1}) that was tried to be set, in this \ - case this warning can be ignored. Otherwise, there may be some \ - other issues with your network configuration." + "Failed to add default route {default1} for interface {ifname}: \ + Network is unreachable. This may happen if interface's IP address \ + is not the same IP version as the default gateway ({default1}) \ + that was tried to be set, in this case this warning can be \ + ignored. Otherwise, there may be some other issues with network \ + configuration." ); } _ => { error!( - "Failed to add route to {default1} for interface {ifname}: \ - {err}" + "Failed to add route to {default1} for interface {ifname}: {err}" ); } }, @@ -385,34 +383,22 @@ pub(crate) fn add_peer_routing( Err(err) => match err { IoError::WriteIo(Errno::ENETUNREACH) => { warn!( - "Failed to add default route {default2} for interface \ - {ifname}: Network is unreachable. This may happen if your \ - interface's IP address is not the same IP version as the \ - default gateway ({default2}) that was tried to be set, in this \ - case this warning can be ignored. Otherwise, there may be some \ - other issues with your network configuration." + "Failed to add default route {default2} for interface {ifname}: \ + Network is unreachable. This may happen if interface's IP address \ + is not the same IP version as the default gateway ({default2}) \ + that was tried to be set, in this case this warning can be \ + ignored. Otherwise, there may be some other issues with network \ + configuration." ); } _ => { error!( - "Failed to add route to {default2} for interface {ifname}: \ - {err}" + "Failed to add route to {default2} for interface {ifname}: {err}" ); } }, } } else { - // Check if allowed IPs contain endpoint's address. - if let Some(endpoint) = peer.endpoint { - let endpoint_ip = endpoint.ip(); - if addr.contains(endpoint_ip) { - warn!( - "Not adding route to {addr} because it contains endpoint {endpoint_ip}" - ); - continue; - } - } - // Equivalent to `route -n add -inet[6] -interface `. match add_linked_route(addr, ifname) { Ok(()) => debug!("Route to {addr} has been added for interface {ifname}"), @@ -423,107 +409,103 @@ pub(crate) fn add_peer_routing( } } - if default_route_v4 || default_route_v6 { - if let Some(endpoint) = peer.endpoint { - debug!("Default routes have been set, proceeding with further configuration..."); - let host = IpAddrMask::host(endpoint.ip()); - let localhost = if endpoint.is_ipv4() { - IpAddr::V4(Ipv4Addr::LOCALHOST) - } else { - IpAddr::V6(Ipv6Addr::LOCALHOST) - }; - debug!("Cleaning up old route to {host}, if it exists..."); - match delete_gateway(&host) { - Ok(()) => { - debug!( - "Previously existing route to {host} has been removed, if it existed" - ); - } - Err(err) => { - debug!("Previously existing route to {host} has not been removed: {err}"); - } - } - if endpoint.is_ipv6() && default_route_v6 { + // Logic below is valid only in case an endpoint has been configured for the peer. + let Some(endpoint) = peer.endpoint else { + continue; + }; + + let endpoint_ip = IpAddrMask::host(endpoint.ip()); + let localhost = if endpoint.is_ipv4() { + IpAddr::V4(Ipv4Addr::LOCALHOST) + } else { + IpAddr::V6(Ipv6Addr::LOCALHOST) + }; + + match delete_gateway(&endpoint_ip) { + Ok(()) => { + debug!("Former route to {endpoint_ip} has been removed, if it existed."); + } + Err(err) => { + debug!("Former route to {endpoint_ip} has not been removed: {err}"); + } + } + + debug!("Default routes have been set, proceeding with further configuration."); + if endpoint.is_ipv6() { + debug!( + "Endpoint is an IPv6 address and a default IPv6 route is present in the allowed \ + IPs; proceeding with further configuration." + ); + match gateway_v6 { + Ok(Some(gateway)) => { debug!( - "Endpoint is an IPv6 address and a default route (IPv6) is present in \ - the alloweds IPs, proceeding with further configuration..." + "Default gateway for IPv6 has been found before: {gateway}, routing the \ + traffic destined to {endpoint_ip} through it." ); - match gateway_v6 { - Ok(Some(gateway)) => { - debug!( - "Default gateway for IPv4 has been found before: {gateway}, \ - routing the traffic destined to {host} through it..." - ); - match add_gateway(&host, gateway, false) { - Ok(()) => { - debug!("Route to {host} has been added for gateway {gateway}"); - } - Err(err) => { - error!( - "Failed to add route to {host} for gateway {gateway}: \ - {err}" - ); - } - } + match add_gateway(&endpoint_ip, gateway, false) { + Ok(()) => { + debug!("Route to {endpoint_ip} has been added for gateway {gateway}"); } - Ok(None) => { - debug!( - "Default gateway for IPv6 has not been found, routing the \ - traffic destined to {host} through localhost as a blackhole \ - route..." + Err(err) => { + error!( + "Failed to add route to {endpoint_ip} for gateway {gateway}: {err}" ); - match add_gateway(&host, localhost, true) { - Ok(()) => debug!("Blackhole route to {host} has been added"), - Err(err) => { - error!("Failed to add blackhole route to {host}: {err}"); - } - } } + } + } + Ok(None) => { + debug!( + "Default gateway for IPv6 has not been found, routing the traffic destined \ + to {endpoint_ip} through localhost as a blackhole route." + ); + match add_gateway(&endpoint_ip, localhost, true) { + Ok(()) => debug!("Blackhole route to {endpoint_ip} has been added"), Err(err) => { - error!("Failed to get gateway for {host}: {err}"); + error!("Failed to add blackhole route to {endpoint_ip}: {err}"); } } - } else if default_route_v4 { + } + Err(ref err) => { + error!("Failed to get gateway for {endpoint_ip}: {err}"); + } + } + } else { + debug!( + "Endpoint is an IPv4 address and a default IPv4 route is present in the allowed \ + IPs; proceeding with further configuration." + ); + match gateway_v4 { + Ok(Some(gateway)) => { debug!( - "Endpoint is an IPv4 address and a default route (IPv4) is present in \ - the alloweds IPs, proceeding with further configuration..." + "Default gateway for IPv4 has been found before: {gateway}, routing the \ + traffic destined to {endpoint_ip} through it." ); - match gateway_v4 { - Ok(Some(gateway)) => { - debug!( - "Default gateway for IPv4 has been found before: {gateway}, \ - routing the traffic destined to {host} through it..." - ); - match add_gateway(&host, gateway, false) { - Ok(()) => { - debug!("Added route to {host} for gateway {gateway}"); - } - Err(err) => { - error!( - "Failed to add route to {host} for gateway {gateway}: \ - {err}" - ); - } - } + match add_gateway(&endpoint_ip, gateway, false) { + Ok(()) => { + debug!("Added route to {endpoint_ip} for gateway {gateway}"); } - Ok(None) => { - debug!( - "Default gateway for IPv4 has not been found, routing the \ - traffic destined to {host} through localhost as a blackhole \ - route..." + Err(err) => { + error!( + "Failed to add route to {endpoint_ip} for gateway {gateway}: {err}" ); - match add_gateway(&host, localhost, true) { - Ok(()) => debug!("Blackhole route to {host} has been added"), - Err(err) => { - error!("Failed to add blackhole route to {host}: {err}"); - } - } } + } + } + Ok(None) => { + debug!( + "Default gateway for IPv4 has not been found, routing the traffic destined \ + to {endpoint_ip} through localhost as a blackhole route." + ); + match add_gateway(&endpoint_ip, localhost, true) { + Ok(()) => debug!("Blackhole route to {endpoint_ip} has been added"), Err(err) => { - error!("Failed to get gateway for {host}: {err}"); + error!("Failed to add blackhole route to {endpoint_ip}: {err}"); } } } + Err(ref err) => { + error!("Failed to get gateway for {endpoint_ip}: {err}"); + } } } } From f397617c8508306e4f3e6615a7fe73b7aea8a830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Wed, 24 Sep 2025 11:12:47 +0200 Subject: [PATCH 4/4] Abandon contains() --- Cargo.lock | 4 ++-- src/net.rs | 37 ------------------------------------- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba3c452..ff93d8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "log" diff --git a/src/net.rs b/src/net.rs index 0541d90..e4ac646 100644 --- a/src/net.rs +++ b/src/net.rs @@ -98,30 +98,6 @@ impl IpAddrMask { } } - /// Check if given address fits in `IpAddrMask`. - #[must_use] - pub fn contains(&self, other: IpAddr) -> bool { - match (self.ip, other) { - (IpAddr::V4(ip), IpAddr::V4(other)) => { - let mask = if self.cidr == 0 { - 0 - } else { - u32::MAX << (32 - self.cidr) - }; - (ip.to_bits() & mask) == (other.to_bits() & mask) - } - (IpAddr::V6(ip), IpAddr::V6(other)) => { - let mask = if self.cidr == 0 { - 0 - } else { - u128::MAX << (128 - self.cidr) - }; - (ip.to_bits() & mask) == (other.to_bits() & mask) - } - _ => false, - } - } - #[cfg(target_os = "linux")] #[must_use] pub fn to_nlas_allowed_ip(&self) -> WgAllowedIp { @@ -285,17 +261,4 @@ mod tests { )) ); } - - #[test] - fn addr_mask_contains() { - let ip = IpAddrMask::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 24); - assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)))); - assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)))); - assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 254)))); - assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 255)))); - assert!(!ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))); - - let ip = IpAddrMask::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - assert!(ip.contains(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))); - } }