From 136855e0259a8cf0219e620cf2b937eb7875c5ef Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:09:16 +0200 Subject: [PATCH 01/12] Fix compilation on windows (#103) --- Cargo.lock | 42 ++++++++++++++++-------------------------- Cargo.toml | 4 +++- src/error.rs | 1 + src/wgapi.rs | 3 +++ src/wgapi_windows.rs | 2 +- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1c396c..eb6a69f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.39" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", @@ -451,7 +451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.1", + "windows-sys 0.60.2", ] [[package]] @@ -468,9 +468,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "fs-err" @@ -655,11 +655,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -806,9 +805,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -816,15 +815,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -918,9 +917,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -993,7 +992,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.1", + "windows-sys 0.60.2", ] [[package]] @@ -1172,7 +1171,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.1", + "windows-sys 0.60.2", ] [[package]] @@ -1524,15 +1523,6 @@ dependencies = [ "windows-targets 0.53.4", ] -[[package]] -name = "windows-sys" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index d324180..180befb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ keywords = ["wireguard", "network", "vpn"] categories = ["network-programming"] [dependencies] -boringtun = { path = "boringtun/boringtun", default-features = false, features = ["device"] } base64 = "0.22" log = "0.4" serde = { version = "1.0", features = ["derive"], optional = true } @@ -26,6 +25,9 @@ tracing = "0.1" tracing-subscriber = "0.3" [target.'cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))'.dependencies] +boringtun = { path = "boringtun/boringtun", default-features = false, features = [ + "device", +] } libc = { version = "0.2", default-features = false } nix = { version = "0.30", features = ["ioctl", "socket"] } diff --git a/src/error.rs b/src/error.rs index ac8f4dc..64775d0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -41,6 +41,7 @@ pub enum WireguardInterfaceError { ServiceRemovalFailed(String), #[error("Socket is closed: {0}")] SocketClosed(String), + #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] #[error("BoringTun {0}")] BoringTun(#[from] boringtun::device::Error), } diff --git a/src/wgapi.rs b/src/wgapi.rs index b433ff4..55b5e15 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -1,6 +1,7 @@ //! Shared multi-platform management API abstraction use std::marker::PhantomData; +#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] use boringtun::device::DeviceHandle; #[cfg(feature = "check_dependencies")] @@ -16,6 +17,7 @@ pub struct Userspace; /// to detect the correct API implementation for most common platforms. pub struct WGApi { pub(super) ifname: String, + #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] pub(super) device_handle: Option, pub(super) _api: PhantomData, } @@ -27,6 +29,7 @@ impl WGApi { check_external_dependencies()?; Ok(WGApi { ifname: ifname.into(), + #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] device_handle: None, _api: PhantomData, }) diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs index ccd67af..4d36e72 100644 --- a/src/wgapi_windows.rs +++ b/src/wgapi_windows.rs @@ -20,7 +20,7 @@ use crate::{ /// Manages interfaces created with Windows kernel using https://git.zx2c4.com/wireguard-nt. impl WireguardInterfaceApi for WGApi { - fn create_interface(&self) -> Result<(), WireguardInterfaceError> { + fn create_interface(&mut self) -> Result<(), WireguardInterfaceError> { info!("Opening/creating interface {}", self.ifname); Ok(()) } From cac8211e9caac729b5c848d9a9fdd5476c1360de Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 21 Oct 2025 01:50:40 -0700 Subject: [PATCH 02/12] Direct WireGuard management on Windows using wireguard-nt (#102) --- .github/workflows/ci.yml | 2 + Cargo.lock | 335 +++++++++++++++------ Cargo.toml | 13 +- README.md | 32 +- src/dependencies.rs | 2 +- src/error.rs | 8 +- src/wgapi.rs | 12 +- src/wgapi_windows.rs | 578 +++++++++++++++++++------------------ src/wireguard_interface.rs | 13 +- 9 files changed, 614 insertions(+), 381 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72c62f8..16cf149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,13 @@ on: push: branches: - main + - dev paths-ignore: - "LICENSE" pull_request: branches: - main + - dev paths-ignore: - "LICENSE" diff --git a/Cargo.lock b/Cargo.lock index eb6a69f..6143813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -181,7 +181,7 @@ dependencies = [ "parking_lot", "ring", "socket2", - "thiserror", + "thiserror 2.0.17", "tracing", "uniffi", "untrusted", @@ -229,14 +229,14 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] name = "cc" -version = "1.2.40" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "shlex", @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", "clap_derive", @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstyle", "clap_lex", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" @@ -387,6 +387,7 @@ dependencies = [ "base64", "boringtun", "env_logger", + "ipnet", "libc", "log", "netlink-packet-core", @@ -398,9 +399,11 @@ dependencies = [ "nix", "serde", "serde_test", - "thiserror", + "thiserror 2.0.17", "tracing", "tracing-subscriber", + "windows", + "wireguard-nt", "x25519-dalek", ] @@ -417,9 +420,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -451,7 +454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -468,9 +471,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "fs-err" @@ -483,9 +486,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -499,19 +502,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] @@ -599,6 +602,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -643,9 +652,19 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] [[package]] name = "linux-raw-sys" @@ -727,7 +746,7 @@ checksum = "3176f18d11a1ae46053e59ec89d46ba318ae1343615bd3f8c908bfc84edae35c" dependencies = [ "byteorder", "pastey", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -778,11 +797,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -926,9 +945,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -938,9 +957,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -949,9 +968,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ring" @@ -992,7 +1011,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1124,12 +1143,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1168,10 +1187,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1183,13 +1202,33 @@ dependencies = [ "smawk", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1463,15 +1502,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -1490,11 +1520,112 @@ dependencies = [ "nom", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] [[package]] name = "windows-sys" @@ -1520,7 +1651,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.4", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -1541,19 +1681,28 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.4" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1564,9 +1713,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -1576,9 +1725,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -1588,9 +1737,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -1600,9 +1749,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -1612,9 +1761,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -1624,9 +1773,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -1636,9 +1785,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -1648,9 +1797,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" @@ -1661,6 +1810,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "wireguard-nt" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b4dbcc6c93786cf22e420ef96e8976bfb92a455070282302b74de5848191f4" +dependencies = [ + "bitflags", + "getrandom 0.2.16", + "ipnet", + "libloading", + "log", + "thiserror 1.0.69", + "widestring", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 180befb..fe11c87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,24 @@ serde_test = "1.0" tracing = "0.1" tracing-subscriber = "0.3" -[target.'cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))'.dependencies] +[target.'cfg(any(target_family = "unix"))'.dependencies] boringtun = { path = "boringtun/boringtun", default-features = false, features = [ "device", ] } libc = { version = "0.2", default-features = false } nix = { version = "0.30", features = ["ioctl", "socket"] } + +[target.'cfg(target_os = "windows")'.dependencies] +ipnet = "2.11.0" +windows = { version = "0.62.1", features = [ + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_Networking_WinSock", + "Win32_System_Com", +]} +wireguard-nt = "0.5.0" + [target.'cfg(target_os = "linux")'.dependencies] netlink-packet-core = "0.8" netlink-packet-generic = "0.4" diff --git a/README.md b/README.md index 9e24472..cddbb61 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,37 @@ It was developed as part of [defguard](https://github.com/defguard/defguard) sec ### Windows support -Please note that [WireGuard](https://www.wireguard.com/install/) needs to be installed on Windows with commands `wg` and `wireguard` available to be called from the command line. +Please note that [WireGuard-NT](https://git.zx2c4.com/wireguard-nt/about/) [dll file](https://download.wireguard.com/wireguard-nt/) has to be placed under `resources-windows/binaries/wireguard.dll` path relative to your binary. + +#### Windows development + +For Windows development you'll need: + +1. The `stable-x86_64-pc-windows-gnu` Rust toolchain. Use `rustup` to change the toolchain: + +``` +rustup install stable-x86_64-pc-windows-gnu +rustup default stable-x86_64-pc-windows-gnu +``` + +2. Install [MSYS2](https://www.msys2.org/) + +3. Then run this in the MSYS2 terminal: + +``` +pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-nasm +``` + +4. Finally add msys to your PATH: + +``` +# cmd +set PATH=C:\msys64\ucrt64\bin;%PATH% +# power-shell +$env:PATH = "C:\msys64\ucrt64\bin;" + $env:PATH +``` + +More info can be found [here](https://stackoverflow.com/a/79640980). ## Examples diff --git a/src/dependencies.rs b/src/dependencies.rs index 9e63789..2e0d33c 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -6,7 +6,7 @@ use crate::error::WireguardInterfaceError; const COMMANDS: [&str; 2] = ["resolvconf", "ip"]; #[cfg(target_os = "windows")] -const COMMANDS: [&str; 1] = [("wireguard.exe")]; +const COMMANDS: [&str; 0] = []; #[cfg(target_os = "macos")] const COMMANDS: [&str; 1] = ["networksetup"]; diff --git a/src/error.rs b/src/error.rs index 64775d0..7dc7b9b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,9 @@ use thiserror::Error; +#[cfg(target_os = "windows")] +use crate::wgapi_windows::WindowsError; + #[derive(Debug, Error)] #[non_exhaustive] pub enum WireguardInterfaceError { @@ -41,7 +44,10 @@ pub enum WireguardInterfaceError { ServiceRemovalFailed(String), #[error("Socket is closed: {0}")] SocketClosed(String), - #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] + #[cfg(target_os = "windows")] + #[error(transparent)] + WindowsError(#[from] WindowsError), + #[cfg(target_family = "unix")] #[error("BoringTun {0}")] BoringTun(#[from] boringtun::device::Error), } diff --git a/src/wgapi.rs b/src/wgapi.rs index 55b5e15..14f040a 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -1,8 +1,10 @@ //! Shared multi-platform management API abstraction use std::marker::PhantomData; -#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] +#[cfg(target_family = "unix")] use boringtun::device::DeviceHandle; +#[cfg(target_os = "windows")] +use wireguard_nt::Adapter; #[cfg(feature = "check_dependencies")] use crate::dependencies::check_external_dependencies; @@ -17,8 +19,10 @@ pub struct Userspace; /// to detect the correct API implementation for most common platforms. pub struct WGApi { pub(super) ifname: String, - #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] + #[cfg(target_family = "unix")] pub(super) device_handle: Option, + #[cfg(target_os = "windows")] + pub(super) adapter: Option, pub(super) _api: PhantomData, } @@ -29,8 +33,10 @@ impl WGApi { check_external_dependencies()?; Ok(WGApi { ifname: ifname.into(), - #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] + #[cfg(target_family = "unix")] device_handle: None, + #[cfg(target_os = "windows")] + adapter: None, _api: PhantomData, }) } diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs index 4d36e72..782e662 100644 --- a/src/wgapi_windows.rs +++ b/src/wgapi_windows.rs @@ -1,14 +1,27 @@ use std::{ - env, - fs::File, - io::{BufRead, BufReader, Cursor, Write}, - net::{IpAddr, SocketAddr}, - process::Command, + collections::HashMap, + net::IpAddr, str::FromStr, - thread::sleep, - time::{Duration, SystemTime}, + sync::{LazyLock, Mutex}, }; +use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use thiserror::Error; +use windows::{ + Win32::{ + Foundation::{ERROR_BUFFER_OVERFLOW, NO_ERROR}, + NetworkManagement::IpHelper::{ + DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, DNS_SETTING_IPV6, + DNS_SETTING_NAMESERVER, GAA_FLAG_INCLUDE_PREFIX, GetAdaptersAddresses, + IP_ADAPTER_ADDRESSES_LH, SetInterfaceDnsSettings, + }, + Networking::WinSock::AF_UNSPEC, + System::Com::CLSIDFromString, + }, + core::{GUID, PCSTR, PCWSTR, PSTR}, +}; +use wireguard_nt::Wireguard; + use crate::{ InterfaceConfiguration, WireguardInterfaceApi, error::WireguardInterfaceError, @@ -18,10 +31,172 @@ use crate::{ wgapi::{Kernel, WGApi}, }; +static WIREGUARD_DLL_PATH: &str = "resources-windows/binaries/wireguard.dll"; +// Load wireguard.dll. Unsafe because we are loading an arbitrary dll file. +static WIREGUARD_DLL: LazyLock> = LazyLock::new(|| { + Mutex::new( + unsafe { wireguard_nt::load_from_path(WIREGUARD_DLL_PATH) } + .expect("Failed to load wireguard.dll"), + ) +}); + +#[derive(Debug, Error)] +pub enum WindowsError { + #[error("Empty interface array")] + EmptyInterfaceArrayError, + #[error("Invalid adapter id: {0}")] + InvalidAdapterId(String), + #[error("Non-zero return value: {0}")] + NonZeroReturnValue(u32), + #[error("Adapter not found: {0}")] + AdapterNotFound(String), + #[error(transparent)] + WireguardNtError(#[from] wireguard_nt::Error), + #[error(transparent)] + FromUtf16Error(#[from] std::string::FromUtf16Error), + #[error(transparent)] + FromUtf8Error(#[from] std::string::FromUtf8Error), + #[error(transparent)] + WindowsCoreError(#[from] windows::core::Error), + #[error("Missing peer endpoint for peer {0}")] + MissingPeerEndpoint(String), +} + +/// Converts a string representation of a GUID into a `windows::core::GUID`. +/// Example guid string: "{6B29FC40-CA47-1067-B31D-00DD010662DA}". +fn guid_from_str(s: &str) -> Result { + let wide = str_to_wide_null_terminated(s); + let guid = unsafe { CLSIDFromString(PCWSTR(wide.as_ptr())).map_err(WindowsError::from)? }; + Ok(guid) +} + +/// Returns the GUID of a network adapter given its name. +/// Example adapter name: "Ethernet", "WireGuard". +fn get_adapter_guid(adapter_name: &str) -> Result { + debug!("Finding adapter {adapter_name}"); + // We have to call `GetAdaptersAddresses` twice - first call to just get the `buffer_size` to hold the adapters. + // Before the second call we allocate the buffer with `buffer_size` capacity so that the call can actually + // store the adapters in the buffer. + let mut buffer_size: u32 = 0; + let mut result = unsafe { + // Sets the `buffer_size` + GetAdaptersAddresses( + AF_UNSPEC.0 as u32, + GAA_FLAG_INCLUDE_PREFIX, + None, + None, + &mut buffer_size, + ) + }; + + // We expect the overflow here, since `buffer_size = 0`. No overflow means no adapters. + if result != ERROR_BUFFER_OVERFLOW.0 { + return Err(WindowsError::EmptyInterfaceArrayError); + } + + // Allocate the buffer and actually get the adapters + let mut buffer = vec![0u8; buffer_size as usize]; + let addresses = buffer.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH; + result = unsafe { + GetAdaptersAddresses( + AF_UNSPEC.0 as u32, + GAA_FLAG_INCLUDE_PREFIX, + None, + Some(addresses), + &mut buffer_size, + ) + }; + if result != NO_ERROR.0 { + return Err(WindowsError::NonZeroReturnValue(result)); + } + + // Find our adapter + let mut current = buffer.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH; + let mut guid: Option = None; + while !current.is_null() { + // SAFETY: + // `current` comes from the linked list allocated and initialized by + // `GetAdaptersAddresses`. The pointer is valid, properly aligned, + // non-null (checked above), and the backing `buffer` lives for the + // duration of this loop. No concurrent mutation occurs, so aliasing + // rules are respected. + let adapter = unsafe { &*current }; + + let friendly_name = unsafe { PCWSTR(adapter.FriendlyName.0).to_string()? }; + if friendly_name == adapter_name { + let adapter_name_str = unsafe { PCSTR(PSTR(adapter.AdapterName.0).0).to_string()? }; + guid = Some(guid_from_str(&adapter_name_str)?); + info!("Found adapter {adapter_name}, GUID: {guid:?}"); + break; + } + + current = adapter.Next; + } + + guid.ok_or_else(|| WindowsError::AdapterNotFound(adapter_name.to_string())) +} + +impl From for Peer { + fn from(peer: wireguard_nt::WireguardPeer) -> Self { + Self { + public_key: Key::new(peer.public_key), + preshared_key: Some(Key::new(peer.preshared_key)), + protocol_version: None, + endpoint: Some(peer.endpoint), + tx_bytes: peer.tx_bytes, + rx_bytes: peer.rx_bytes, + last_handshake: peer.last_handshake, + persistent_keepalive_interval: Some(peer.persistent_keepalive), + allowed_ips: peer + .allowed_ips + .iter() + .map(|ip| IpAddrMask::new(ip.addr(), ip.prefix_len())) + .collect(), + } + } +} + +impl From for Host { + fn from(iface: wireguard_nt::WireguardInterface) -> Self { + let mut peers = HashMap::new(); + for peer in iface.peers { + peers.insert(Key::new(peer.public_key), peer.into()); + } + Self { + listen_port: iface.listen_port, + private_key: Some(Key::new(iface.private_key)), + fwmark: None, + peers, + } + } +} + +/// Converts an str to wide (u16), null-terminated +fn str_to_wide_null_terminated(s: &str) -> Vec { + s.encode_utf16().chain(std::iter::once(0)).collect() +} + /// Manages interfaces created with Windows kernel using https://git.zx2c4.com/wireguard-nt. impl WireguardInterfaceApi for WGApi { fn create_interface(&mut self) -> Result<(), WireguardInterfaceError> { - info!("Opening/creating interface {}", self.ifname); + debug!("Opening/creating interface {}", self.ifname); + + // Try to open the adapter. If it's not present create it. + let wireguard = WIREGUARD_DLL.lock().expect("Failed to lock WIREGUARD_DLL"); + let adapter = match wireguard_nt::Adapter::open(&wireguard, &self.ifname) { + Ok(adapter) => { + debug!("Found existing adapter {}", self.ifname); + adapter + } + Err(_) => { + debug!("Adapter {} does not exist, creating", self.ifname); + wireguard_nt::Adapter::create(&wireguard, &self.ifname, &self.ifname, None) + .map_err(WindowsError::from)? + } + }; + self.adapter = Some(adapter); + + info!("Opened/created interface {}", self.ifname); Ok(()) } @@ -33,213 +208,78 @@ impl WireguardInterfaceApi for WGApi { fn configure_interface( &self, config: &InterfaceConfiguration, - dns: &[IpAddr], - search_domains: &[&str], ) -> Result<(), WireguardInterfaceError> { debug!( "Configuring interface {} with config: {config:?}", self.ifname ); - // Interface is created here so that there is no need to pass private key only for Windows - let file_name = format!("{}.conf", &self.ifname); - let path = env::current_dir()?; - let file_path_buf = path.join(&file_name); - let file_path = file_path_buf.to_str().unwrap_or_default(); - - debug!("Creating WireGuard configuration file {file_name} in: {file_path}"); - - let mut file = File::create(&file_name)?; + // Retrieve the adapter - should be created by calling `Self::create_interface` first. + let Some(ref adapter) = self.adapter else { + Err(WindowsError::AdapterNotFound(self.ifname.clone()))? + }; - debug!( - "WireGuard configuration file {file_name} created in {file_path}. Preparing configuration..." - ); - - let address = config - .addresses + // Prepare peers + debug!("Preparing peers for adapter {}", self.ifname); + let peers: Result, WindowsError> = config + .peers .iter() - .map(|addr| addr.to_string()) - .collect::>() - .join(","); - let mut wireguard_configuration = format!( - "[Interface]\nPrivateKey = {}\nAddress = {address}\n", - config.prvkey - ); - - if !dns.is_empty() { - // Format: - // DNS = , - // If search domains are present: - // DNS = ,,, - let dns_addresses = format!( - "\nDNS = {}{}", - // DNS addresses part - dns.iter() - .map(|v| v.to_string()) - .collect::>() - .join(","), - // Search domains part, optional - if !search_domains.is_empty() { - format!( - ",{}", - search_domains - .iter() - .map(|v| v.to_string()) - .collect::>() - .join(",") - ) - } else { - "".to_string() - } - ); - wireguard_configuration.push_str(dns_addresses.as_str()); - } - - for peer in &config.peers { - wireguard_configuration - .push_str(format!("\n[Peer]\nPublicKey = {}", peer.public_key).as_str()); - - if let Some(preshared_key) = &peer.preshared_key { - wireguard_configuration - .push_str(format!("\nPresharedKey = {}", preshared_key).as_str()); - } - - if let Some(keep_alive) = peer.persistent_keepalive_interval { - wireguard_configuration - .push_str(format!("\nPersistentKeepalive = {}", keep_alive).as_str()); - } - - if let Some(endpoint) = peer.endpoint { - wireguard_configuration.push_str(format!("\nEndpoint = {}", endpoint).as_str()); - } - - if !peer.allowed_ips.is_empty() { - let allowed_ips = format!( - "\nAllowedIPs = {}", - peer.allowed_ips + .map(|peer| { + Ok(wireguard_nt::SetPeer { + public_key: Some(peer.public_key.as_array()), + preshared_key: peer.preshared_key.as_ref().map(|key| key.as_array()), + keep_alive: peer.persistent_keepalive_interval, + allowed_ips: peer + .allowed_ips .iter() - .map(|v| v.to_string()) - .collect::>() - .join(",") - ); - wireguard_configuration.push_str(allowed_ips.as_str()); - } - } - + .filter_map(|ip| match ip.ip { + IpAddr::V4(addr) => Some(IpNet::V4(Ipv4Net::new(addr, ip.cidr).ok()?)), + IpAddr::V6(addr) => Some(IpNet::V6(Ipv6Net::new(addr, ip.cidr).ok()?)), + }) + .collect(), + endpoint: peer.endpoint.ok_or_else(|| { + WindowsError::MissingPeerEndpoint(peer.public_key.to_string()) + })?, + }) + }) + .collect(); + let peers = peers?; + + // Configure the interface + debug!("Applying configuration for adapter {}", self.ifname); + let interface = wireguard_nt::SetInterface { + listen_port: Some(config.port as u16), + public_key: None, // derived from private key + private_key: Some(Key::from_str(&config.prvkey)?.as_array()), + peers, + }; + adapter.set_config(&interface).map_err(WindowsError::from)?; + + // Set adapter addresses debug!( - "WireGuard configuration prepared: {wireguard_configuration}, writing to the file at {file_path}..." + "Assigning addresses to adapter {}: {:?}", + self.ifname, config.addresses ); - 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) - .output() - .map_err(|err| { - 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 - // This might be excessive as closing the application closes the WireGuard tunnel. - sleep(Duration::from_secs(1)); - - let output = Command::new("wg") - .arg("show") - .arg(&self.ifname) - .output() - .map_err(|err| { - error!("Failed to read interface data. Error: {err}"); - WireguardInterfaceError::ReadInterfaceError(err.to_string()) - })?; - - // Service has been removed - if !output.status.success() || counter == 5 { - break; - } - - 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) - .output() - .map_err(|err| { - error!("Failed to create interface. Error: {err}"); - WireguardInterfaceError::ServiceInstallationFailed(err.to_string()) - })?; - - debug!( - "Done installing the new service. Service installation output: {service_installation_output:?}", - ); - - if !service_installation_output.status.success() { - let message = format!( - "Failed to install WireGuard tunnel as a Windows service: {:?}", - service_installation_output.stdout - ); - return Err(WireguardInterfaceError::ServiceInstallationFailed(message)); - } - - debug!( - "Disabling automatic restart for interface {} tunnel service", - self.ifname - ); - let service_update_output = Command::new("sc") - .arg("config") - .arg(format!("WireGuardTunnel${}", self.ifname)) - .arg("start=demand") - .output() - .map_err(|err| { - error!("Failed to configure tunnel service. Error: {err}"); - WireguardInterfaceError::ServiceInstallationFailed(err.to_string()) - })?; - - debug!( - "Done disabling automatic restart for the new service. Service update output: {service_update_output:?}", - ); - if !service_update_output.status.success() { - let message = format!( - "Failed to configure WireGuard tunnel service: {:?}", - service_update_output.stdout - ); - return Err(WireguardInterfaceError::ServiceInstallationFailed(message)); - } - - // TODO: set maximum transfer unit (MTU) + let addresses: Vec<_> = config + .addresses + .iter() + .filter_map(|ip| match ip.ip { + IpAddr::V4(addr) => Some(IpNet::V4(Ipv4Net::new(addr, ip.cidr).ok()?)), + IpAddr::V6(addr) => Some(IpNet::V6(Ipv6Net::new(addr, ip.cidr).ok()?)), + }) + .collect(); + adapter + .set_default_route(&addresses, &interface) + .map_err(WindowsError::from)?; + + // Bring the adapter up + debug!("Bringing up adapter {}", self.ifname); + adapter.up().map_err(WindowsError::from)?; info!( "Interface {} has been successfully configured.", self.ifname ); - debug!( - "Interface {} configured with config: {config:?}", - self.ifname - ); Ok(()) } @@ -247,26 +287,9 @@ impl WireguardInterfaceApi for WGApi { Ok(()) } - fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { + fn remove_interface(&mut self) -> Result<(), WireguardInterfaceError> { debug!("Removing interface {}", self.ifname); - - let command_output = Command::new("wireguard") - .arg("/uninstalltunnelservice") - .arg(&self.ifname) - .output() - .map_err(|err| { - error!("Failed to remove interface. Error: {err}"); - WireguardInterfaceError::CommandExecutionFailed(err) - })?; - - if !command_output.status.success() { - let message = format!( - "Failed to remove WireGuard tunnel service: {:?}", - command_output.stdout - ); - return Err(WireguardInterfaceError::ServiceRemovalFailed(message)); - } - + self.adapter = None; info!("Interface {} removed successfully", self.ifname); Ok(()) } @@ -287,67 +310,11 @@ impl WireguardInterfaceApi for WGApi { fn read_interface_data(&self) -> Result { debug!("Reading host info for interface {}", self.ifname); - let output = Command::new("wg") - .arg("show") - .arg(&self.ifname) - .arg("dump") - .output() - .map_err(|err| { - error!("Failed to read interface. Error: {err}"); - WireguardInterfaceError::CommandExecutionFailed(err) - })?; - - let reader = BufReader::new(Cursor::new(output.stdout)); - let mut host = Host::default(); - let lines = reader.lines(); - - for (index, line_result) in lines.enumerate() { - let line = match &line_result { - Ok(line) => line, - Err(_err) => { - continue; - } - }; - - let data: Vec<&str> = line.split("\t").collect(); - - // First line contains [Interface] section data, every other line is a separate [Peer] - if index == 0 { - // Interface data: private key, public key, listen port, fwmark - host.private_key = Key::from_str(data[0]).ok(); - host.listen_port = data[2].parse().unwrap_or_default(); - - if data[3] != "off" { - host.fwmark = Some(data[3].parse().unwrap()); - } - } else { - // Peer data: public key, preshared key, endpoint, allowed ips, latest handshake, transfer-rx, transfer-tx, persistent-keepalive - if let Ok(public_key) = Key::from_str(data[0]) { - let mut peer = Peer::new(public_key.clone()); - - if data[1] != "(none)" { - peer.preshared_key = Key::from_str(data[0]).ok(); - } - - peer.endpoint = SocketAddr::from_str(data[2]).ok(); - - for allowed_ip in data[3].split(",") { - let addr = IpAddrMask::from_str(allowed_ip.trim())?; - peer.allowed_ips.push(addr); - } - - let handshake = peer.last_handshake.get_or_insert(SystemTime::UNIX_EPOCH); - *handshake += Duration::from_secs(data[4].parse().unwrap_or_default()); - - peer.rx_bytes = data[5].parse().unwrap_or_default(); - peer.tx_bytes = data[6].parse().unwrap_or_default(); - peer.persistent_keepalive_interval = data[7].parse().ok(); - - host.peers.insert(public_key.clone(), peer); - } - } - } - + // Retrieve the adapter - should be created by calling `Self::create_interface` first. + let Some(ref adapter) = self.adapter else { + Err(WindowsError::AdapterNotFound(self.ifname.clone()))? + }; + let host = adapter.get_config().into(); debug!("Read interface data: {host:?}"); Ok(host) } @@ -355,12 +322,63 @@ impl WireguardInterfaceApi for WGApi { fn configure_dns( &self, dns: &[IpAddr], - _search_domains: &[&str], + search_domains: &[&str], ) -> Result<(), WireguardInterfaceError> { debug!( "Configuring DNS for interface {}, using address: {dns:?}", self.ifname ); + let guid = get_adapter_guid(&self.ifname)?; + let (ipv4_dns_ips, ipv6_dns_ips): (Vec<&IpAddr>, Vec<&IpAddr>) = + dns.iter().partition(|ip| ip.is_ipv4()); + let ipv4_dns_servers: Vec = ipv4_dns_ips.iter().map(|ip| ip.to_string()).collect(); + let ipv6_dns_servers: Vec = ipv6_dns_ips.iter().map(|ip| ip.to_string()).collect(); + + let mut search_domains_vec: Vec = + str_to_wide_null_terminated(&search_domains.join(",")); + let search_domains_wide = windows::core::PWSTR(search_domains_vec.as_mut_ptr()); + + if !ipv4_dns_servers.is_empty() { + let dns_str = ipv4_dns_servers.join(","); + let mut wide: Vec = str_to_wide_null_terminated(&dns_str); + let name_server = windows::core::PWSTR(wide.as_mut_ptr()); + + let settings = DNS_INTERFACE_SETTINGS { + Version: DNS_INTERFACE_SETTINGS_VERSION1, + Flags: DNS_SETTING_NAMESERVER as u64, + NameServer: name_server, + SearchList: search_domains_wide, + ..Default::default() + }; + + let status = unsafe { SetInterfaceDnsSettings(guid, &settings) }; + if status != NO_ERROR { + Err(WindowsError::NonZeroReturnValue(status.0))?; + } + } + if !ipv6_dns_servers.is_empty() { + let dns_str = ipv6_dns_servers.join(","); + let mut wide: Vec = str_to_wide_null_terminated(&dns_str); + let name_server = windows::core::PWSTR(wide.as_mut_ptr()); + + let settings = DNS_INTERFACE_SETTINGS { + Version: DNS_INTERFACE_SETTINGS_VERSION1, + Flags: (DNS_SETTING_NAMESERVER | DNS_SETTING_IPV6) as u64, + NameServer: name_server, + SearchList: search_domains_wide, + ..Default::default() + }; + + let status = unsafe { SetInterfaceDnsSettings(guid, &settings) }; + if status != NO_ERROR { + Err(WindowsError::NonZeroReturnValue(status.0))?; + } + } + + info!( + "Configured DNS for interface {}, using address: {dns:?}", + self.ifname + ); Ok(()) } } diff --git a/src/wireguard_interface.rs b/src/wireguard_interface.rs index ace1999..bd6a37f 100644 --- a/src/wireguard_interface.rs +++ b/src/wireguard_interface.rs @@ -24,25 +24,20 @@ pub trait WireguardInterfaceApi { } /// Updates configuration of an existing WireGuard interface. - #[cfg(not(target_os = "windows"))] - fn configure_interface( - &self, - config: &InterfaceConfiguration, - ) -> Result<(), WireguardInterfaceError>; - - #[cfg(target_os = "windows")] fn configure_interface( &self, config: &InterfaceConfiguration, - dns: &[IpAddr], - search_domains: &[&str], ) -> Result<(), WireguardInterfaceError>; /// Removes the WireGuard interface being managed. /// /// Meant to be used in `drop` method for a given API struct. + #[cfg(not(target_os = "windows"))] fn remove_interface(&self) -> Result<(), WireguardInterfaceError>; + #[cfg(target_os = "windows")] + fn remove_interface(&mut self) -> Result<(), WireguardInterfaceError>; + /// Adds a peer or updates peer configuration. fn configure_peer(&self, peer: &Peer) -> Result<(), WireguardInterfaceError>; From 1448c9ab0574ead39f3c1321aec24dfaa6156d29 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 21 Oct 2025 02:12:07 -0700 Subject: [PATCH 03/12] conditional compilation style fixes (#104) --- Cargo.toml | 2 +- src/error.rs | 2 +- src/lib.rs | 2 +- src/wgapi.rs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe11c87..d546c40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde_test = "1.0" tracing = "0.1" tracing-subscriber = "0.3" -[target.'cfg(any(target_family = "unix"))'.dependencies] +[target.'cfg(unix)'.dependencies] boringtun = { path = "boringtun/boringtun", default-features = false, features = [ "device", ] } diff --git a/src/error.rs b/src/error.rs index 7dc7b9b..7442023 100644 --- a/src/error.rs +++ b/src/error.rs @@ -47,7 +47,7 @@ pub enum WireguardInterfaceError { #[cfg(target_os = "windows")] #[error(transparent)] WindowsError(#[from] WindowsError), - #[cfg(target_family = "unix")] + #[cfg(unix)] #[error("BoringTun {0}")] BoringTun(#[from] boringtun::device::Error), } diff --git a/src/lib.rs b/src/lib.rs index 6750b1c..1eaad69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ mod dependencies; mod wgapi_freebsd; #[cfg(target_os = "linux")] mod wgapi_linux; -#[cfg(target_family = "unix")] +#[cfg(unix)] mod wgapi_userspace; #[cfg(target_family = "windows")] mod wgapi_windows; diff --git a/src/wgapi.rs b/src/wgapi.rs index 14f040a..561b1f6 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -1,7 +1,7 @@ //! Shared multi-platform management API abstraction use std::marker::PhantomData; -#[cfg(target_family = "unix")] +#[cfg(unix)] use boringtun::device::DeviceHandle; #[cfg(target_os = "windows")] use wireguard_nt::Adapter; @@ -19,7 +19,7 @@ pub struct Userspace; /// to detect the correct API implementation for most common platforms. pub struct WGApi { pub(super) ifname: String, - #[cfg(target_family = "unix")] + #[cfg(unix)] pub(super) device_handle: Option, #[cfg(target_os = "windows")] pub(super) adapter: Option, @@ -33,7 +33,7 @@ impl WGApi { check_external_dependencies()?; Ok(WGApi { ifname: ifname.into(), - #[cfg(target_family = "unix")] + #[cfg(unix)] device_handle: None, #[cfg(target_os = "windows")] adapter: None, From 0db4ea7bf4a6bd21c449f9ab8fa6676aebf4698f Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Oct 2025 11:47:48 +0100 Subject: [PATCH 04/12] Rename IpAddrMask.ip to .address and use u16 for port (#105) --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- Cargo.toml | 2 +- src/bsd/mod.rs | 20 ++++++++++---------- src/lib.rs | 4 ++-- src/net.rs | 28 ++++++++++++++-------------- src/netlink.rs | 14 ++++++++------ src/utils.rs | 12 ++++++------ 7 files changed, 64 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6143813..9392e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" dependencies = [ "find-msvc-tools", "shlex", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.49" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", "clap_derive", @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.49" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstyle", "clap_lex", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.8.0" +version = "0.9.0" dependencies = [ "base64", "boringtun", @@ -563,9 +563,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", @@ -610,9 +610,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" @@ -812,9 +812,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -903,9 +903,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1171,9 +1171,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1325,9 +1325,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "uniffi" diff --git a/Cargo.toml b/Cargo.toml index d546c40..b18391d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.8.0" +version = "0.9.0" 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 31c9512..d027dcb 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -115,7 +115,7 @@ impl IpAddrMask { .and_then(|ipv6| <[u8; 16]>::try_from(ipv6).ok().map(IpAddr::from)), } .map(|ip| Self { - ip, + address: ip, cidr: cidr as u8, }) }) @@ -129,7 +129,7 @@ impl<'a> IpAddrMask { nvlist.append_number(NV_CIDR, u64::from(self.cidr)); - match self.ip { + match self.address { IpAddr::V4(ipv4) => nvlist.append_bytes(NV_IPV4, ipv4.octets().into()), IpAddr::V6(ipv6) => nvlist.append_bytes(NV_IPV6, ipv6.octets().into()), } @@ -340,7 +340,7 @@ pub fn delete_interface(if_name: &str) -> Result<(), IoError> { } pub fn set_address(if_name: &str, address: &IpAddrMask) -> Result<(), IoError> { - match address.ip { + match address.address { IpAddr::V4(address) => { let ifreq = IfReq::new_with_address(if_name, address); ifreq.set_address() @@ -356,7 +356,7 @@ pub fn assign_address(if_name: &str, address: &IpAddrMask) -> Result<(), IoError let broadcast = address.broadcast(); let mask = address.mask(); - match (address.ip, broadcast, mask) { + match (address.address, broadcast, mask) { (IpAddr::V4(address), IpAddr::V4(broadcast), IpAddr::V4(mask)) => { let inaliasreq = InAliasReq::new(if_name, address, broadcast, mask); inaliasreq.add_address() @@ -370,7 +370,7 @@ pub fn assign_address(if_name: &str, address: &IpAddrMask) -> Result<(), IoError } pub fn remove_address(if_name: &str, address: &IpAddrMask) -> Result<(), IoError> { - match address.ip { + match address.address { IpAddr::V4(address) => { let ifreq = IfReq::new_with_address(if_name, address); ifreq.delete_address() @@ -459,7 +459,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}."); - match (dest.ip, dest.mask(), gateway) { + match (dest.address, dest.mask(), gateway) { (IpAddr::V4(ip), IpAddr::V4(mask), IpAddr::V4(gw)) => { let payload = DestAddrMask::::new(ip.into(), mask.into(), gw.into()); let rtmsg = RtMessage::new_for_add_gateway(payload, dest.is_host(), is_blackhole); @@ -480,7 +480,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}."); - match (dest.ip, dest.mask()) { + match (dest.address, dest.mask()) { (IpAddr::V4(ip), IpAddr::V4(mask)) => { let payload = DestAddrMask::::new(ip.into(), mask.into(), SockAddrIn::default()); @@ -508,7 +508,7 @@ pub fn add_linked_route(dest: &IpAddrMask, if_name: &str) -> Result<(), IoError> if if_index == 0 { return Err(IoError::NetworkInterface); } - match (dest.ip, dest.mask()) { + match (dest.address, dest.mask()) { (IpAddr::V4(ip), IpAddr::V4(mask)) => { let link = SockAddrDl::new(if_index); let payload = GatewayLink::::new(ip.into(), mask.into(), link); @@ -535,7 +535,7 @@ pub fn add_route(dest: &IpAddrMask, if_name: &str) -> Result<(), IoError> { if if_index == 0 { return Err(IoError::NetworkInterface); } - match (dest.ip, dest.mask()) { + match (dest.address, dest.mask()) { (IpAddr::V4(ip), IpAddr::V4(mask)) => { let payload = DestAddrMask::::new_for_interface(ip.into(), mask.into(), if_name); @@ -561,7 +561,7 @@ pub fn delete_route(dest: &IpAddrMask, if_name: &str) -> Result<(), IoError> { if if_index == 0 { return Err(IoError::NetworkInterface); } - match (dest.ip, dest.mask()) { + match (dest.address, dest.mask()) { (IpAddr::V4(ip), IpAddr::V4(mask)) => { let payload = DestAddrMask::::new_for_interface(ip.into(), mask.into(), if_name); diff --git a/src/lib.rs b/src/lib.rs index 1eaad69..5d22214 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,7 +105,7 @@ pub struct InterfaceConfiguration { pub name: String, pub prvkey: String, pub addresses: Vec, - pub port: u32, + pub port: u16, pub peers: Vec, /// Maximum transfer unit. `None` means do not set MTU, but keep the system default. pub mtu: Option, @@ -129,7 +129,7 @@ impl TryFrom<&InterfaceConfiguration> for Host { fn try_from(config: &InterfaceConfiguration) -> Result { let key = config.prvkey.as_str().try_into()?; - let mut host = Host::new(config.port as u16, key); + let mut host = Host::new(config.port, key); for peercfg in &config.peers { let peer = peercfg.clone(); let key: Key = peer.public_key.clone(); diff --git a/src/net.rs b/src/net.rs index e4ac646..c0ba321 100644 --- a/src/net.rs +++ b/src/net.rs @@ -19,31 +19,31 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct IpAddrMask { // IP v4 or v6 - pub ip: IpAddr, + pub address: IpAddr, // Classless Inter-Domain Routing pub cidr: u8, } impl IpAddrMask { #[must_use] - pub fn new(ip: IpAddr, cidr: u8) -> Self { - Self { ip, cidr } + pub fn new(address: IpAddr, cidr: u8) -> Self { + Self { address, cidr } } #[must_use] - pub fn host(ip: IpAddr) -> Self { - let cidr = match ip { + pub fn host(address: IpAddr) -> Self { + let cidr = match address { IpAddr::V4(_) => 32, IpAddr::V6(_) => 128, }; - Self { ip, cidr } + Self { address, cidr } } /// Returns broadcast address as `IpAddr`. /// Note: IPv6 does not really use broadcast. #[must_use] pub fn broadcast(&self) -> IpAddr { - match self.ip { + match self.address { IpAddr::V4(ip) => { let addr = u32::from(ip); let bits = if self.cidr >= 32 { @@ -68,7 +68,7 @@ impl IpAddrMask { /// Returns network mask as `IpAddr`. #[must_use] pub fn mask(&self) -> IpAddr { - match self.ip { + match self.address { IpAddr::V4(_) => { let mask = if self.cidr == 0 { 0 @@ -91,7 +91,7 @@ impl IpAddrMask { /// Returns `true` if the address defines a host, `false` if it is a network. #[must_use] pub fn is_host(&self) -> bool { - if self.ip.is_ipv4() { + if self.address.is_ipv4() { self.cidr == 32 } else { self.cidr == 128 @@ -102,12 +102,12 @@ impl IpAddrMask { #[must_use] pub fn to_nlas_allowed_ip(&self) -> WgAllowedIp { let mut attrs = Vec::new(); - attrs.push(WgAllowedIpAttrs::Family(if self.ip.is_ipv4() { + attrs.push(WgAllowedIpAttrs::Family(if self.address.is_ipv4() { AF_INET } else { AF_INET6 })); - attrs.push(WgAllowedIpAttrs::IpAddr(self.ip)); + attrs.push(WgAllowedIpAttrs::IpAddr(self.address)); attrs.push(WgAllowedIpAttrs::Cidr(self.cidr)); WgAllowedIp(attrs) } @@ -115,7 +115,7 @@ impl IpAddrMask { impl fmt::Display for IpAddrMask { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.ip, self.cidr) + write!(f, "{}/{}", self.address, self.cidr) } } @@ -144,11 +144,11 @@ impl FromStr for IpAddrMask { if cidr > max_cidr { return Err(IpAddrParseError); } - Ok(IpAddrMask { ip, cidr }) + Ok(IpAddrMask { address: ip, cidr }) } else { let ip = ip_str.parse().map_err(|_| IpAddrParseError)?; Ok(IpAddrMask { - ip, + address: ip, cidr: if ip.is_ipv4() { 32 } else { 128 }, }) } diff --git a/src/netlink.rs b/src/netlink.rs index c68365a..2b416a1 100644 --- a/src/netlink.rs +++ b/src/netlink.rs @@ -102,7 +102,7 @@ impl Key { impl IpAddrMask { #[must_use] fn address_family(&self) -> AddressFamily { - match self.ip { + match self.address { IpAddr::V4(_) => AddressFamily::Inet, IpAddr::V6(_) => AddressFamily::Inet6, } @@ -260,18 +260,20 @@ fn set_address(index: u32, address: &IpAddrMask) -> NetlinkResult<()> { message.header.index = index; message.header.family = address.address_family(); - if address.ip.is_multicast() { - if let IpAddr::V6(addr) = address.ip { + if address.address.is_multicast() { + if let IpAddr::V6(addr) = address.address { message.attributes.push(AddressAttribute::Multicast(addr)); } } else { message .attributes - .push(AddressAttribute::Address(address.ip)); + .push(AddressAttribute::Address(address.address)); // For IPv4 the Local address can be set to the same value as // Address. - message.attributes.push(AddressAttribute::Local(address.ip)); + message + .attributes + .push(AddressAttribute::Local(address.address)); // Set the broadcast address as well (IPv6 does not support // broadcast). @@ -527,7 +529,7 @@ pub(crate) fn add_route( }; header.address_family = address.address_family(); header.destination_prefix_length = address.cidr; - let route_address = match address.ip { + let route_address = match address.address { IpAddr::V4(ipv4) => RouteAddress::Inet(ipv4), IpAddr::V6(ipv6) => RouteAddress::Inet6(ipv6), }; diff --git a/src/utils.rs b/src/utils.rs index 52c96da..8f8852f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -188,7 +188,7 @@ fn setup_default_route( addr: &crate::IpAddrMask, ) -> Result<(), WireguardInterfaceError> { debug!("Found default route in AllowedIPs: {addr:?}"); - let is_ipv6 = addr.ip.is_ipv6(); + let is_ipv6 = addr.address.is_ipv6(); let proto = if is_ipv6 { "-6" } else { "-4" }; debug!("Using the following IP version: {proto}"); @@ -271,9 +271,9 @@ pub(crate) fn add_peer_routing( // Gather allowed IPs and default routes for peer in peers { for addr in &peer.allowed_ips { - if addr.ip.is_unspecified() { + if addr.address.is_unspecified() { // Default route - store for later - if addr.ip.is_ipv4() { + if addr.address.is_ipv4() { default_routes.0 = Some(addr); } else { default_routes.1 = Some(addr); @@ -281,7 +281,7 @@ pub(crate) fn add_peer_routing( continue; } // Regular route - add to set - if addr.ip.is_ipv4() { + if addr.address.is_ipv4() { allowed_ips.0.insert(addr); } else { allowed_ips.1.insert(addr); @@ -339,14 +339,14 @@ pub(crate) fn add_peer_routing( debug!("Processing route for allowed IP: {addr}, interface: {ifname}"); // FIXME: currently it is impossible to add another default route, so use the hack from // wg-quick for Darwin. - if addr.ip.is_unspecified() && addr.cidr == 0 { + if addr.address.is_unspecified() && addr.cidr == 0 { debug!( "Found following default route in the allowed IPs: {addr}, interface: \ {ifname}, proceeding with default route initial setup." ); let default1; let default2; - if addr.ip.is_ipv4() { + if addr.address.is_ipv4() { // 0.0.0.0/1 default1 = IpAddrMask::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 1); // 128.0.0.0/1 From 61805f0d3921a1ff4d83367385a35b95d4dd62b8 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 3 Nov 2025 14:44:01 +0100 Subject: [PATCH 05/12] Fix build on Windows (#106) --- src/wgapi_windows.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs index 782e662..0633bf2 100644 --- a/src/wgapi_windows.rs +++ b/src/wgapi_windows.rs @@ -232,7 +232,7 @@ impl WireguardInterfaceApi for WGApi { allowed_ips: peer .allowed_ips .iter() - .filter_map(|ip| match ip.ip { + .filter_map(|ip| match ip.address { IpAddr::V4(addr) => Some(IpNet::V4(Ipv4Net::new(addr, ip.cidr).ok()?)), IpAddr::V6(addr) => Some(IpNet::V6(Ipv6Net::new(addr, ip.cidr).ok()?)), }) @@ -248,7 +248,7 @@ impl WireguardInterfaceApi for WGApi { // Configure the interface debug!("Applying configuration for adapter {}", self.ifname); let interface = wireguard_nt::SetInterface { - listen_port: Some(config.port as u16), + listen_port: Some(config.port), public_key: None, // derived from private key private_key: Some(Key::from_str(&config.prvkey)?.as_array()), peers, @@ -263,7 +263,7 @@ impl WireguardInterfaceApi for WGApi { let addresses: Vec<_> = config .addresses .iter() - .filter_map(|ip| match ip.ip { + .filter_map(|ip| match ip.address { IpAddr::V4(addr) => Some(IpNet::V4(Ipv4Net::new(addr, ip.cidr).ok()?)), IpAddr::V6(addr) => Some(IpNet::V6(Ipv6Net::new(addr, ip.cidr).ok()?)), }) From 886186c1e088e4805ab8049436c28cf3ea26d727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Tue, 4 Nov 2025 13:02:29 +0100 Subject: [PATCH 06/12] Update BoringTun submodule and fix build on FreeBSD --- Cargo.lock | 99 +++++++++++++++++++++++++++++++------------- Cargo.toml | 5 +-- boringtun | 2 +- src/wgapi_freebsd.rs | 2 +- 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9392e8c..fc83b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.43" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "shlex", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.50" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.50" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstyle", "clap_lex", @@ -569,6 +569,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -1099,6 +1101,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_test" version = "1.0.177" @@ -1253,13 +1264,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "serde", + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", ] +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tracing" version = "0.1.41" @@ -1325,15 +1366,15 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "uniffi" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d968cb62160c11f2573e6be724ef8b1b18a277aededd17033f8a912d73e2b4" +checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" dependencies = [ "anyhow", "camino", @@ -1348,9 +1389,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b39ef1acbe1467d5d210f274fae344cb6f8766339330cb4c9688752899bf6b" +checksum = "7c8ca600167641ebe7c8ba9254af40492dda3397c528cc3b2f511bd23e8541a5" dependencies = [ "anyhow", "askama", @@ -1374,9 +1415,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6683e6b665423cddeacd89a3f97312cf400b2fb245a26f197adaf65c45d505b2" +checksum = "3e55c05228f4858bb258f651d21d743fcc1fe5a2ec20d3c0f9daefddb105ee4d" dependencies = [ "anyhow", "camino", @@ -1385,9 +1426,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d990b553d6b9a7ee9c3ae71134674739913d52350b56152b0e613595bb5a6f" +checksum = "7e7a5a038ebffe8f4cf91416b154ef3c2468b18e828b7009e01b1b99938089f9" dependencies = [ "anyhow", "bytes", @@ -1397,9 +1438,9 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f4f224becf14885c10e6e400b95cc4d1985738140cb194ccc2044563f8a56b" +checksum = "e3c2a6f93e7b73726e2015696ece25ca0ac5a5f1cf8d6a7ab5214dd0a01d2edf" dependencies = [ "anyhow", "indexmap", @@ -1410,9 +1451,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b481d385af334871d70904e6a5f129be7cd38c18fcf8dd8fd1f646b426a56d58" +checksum = "64c6309fc36c7992afc03bc0c5b059c656bccbef3f2a4bc362980017f8936141" dependencies = [ "camino", "fs-err", @@ -1427,9 +1468,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f817868a3b171bb7bf259e882138d104deafde65684689b4694c846d322491" +checksum = "0a138823392dba19b0aa494872689f97d0ee157de5852e2bec157ce6de9cdc22" dependencies = [ "anyhow", "siphasher", @@ -1439,9 +1480,9 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b147e133ad7824e32426b90bc41fda584363563f2ba747f590eca1fd6fd14e6" +checksum = "8c27c4b515d25f8e53cc918e238c39a79c3144a40eaf2e51c4a7958973422c29" dependencies = [ "anyhow", "heck", @@ -1452,9 +1493,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.29.4" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caed654fb73da5abbc7a7e9c741532284532ba4762d6fe5071372df22a41730a" +checksum = "d0adacdd848aeed7af4f5af7d2f621d5e82531325d405e29463482becfdeafca" dependencies = [ "anyhow", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index b18391d..78d3c58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,10 +31,9 @@ boringtun = { path = "boringtun/boringtun", default-features = false, features = libc = { version = "0.2", default-features = false } nix = { version = "0.30", features = ["ioctl", "socket"] } - [target.'cfg(target_os = "windows")'.dependencies] -ipnet = "2.11.0" -windows = { version = "0.62.1", features = [ +ipnet = "2.11" +windows = { version = "0.62", features = [ "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", diff --git a/boringtun b/boringtun index 69b2649..fdf0148 160000 --- a/boringtun +++ b/boringtun @@ -1 +1 @@ -Subproject commit 69b2649515ca8c2c948172577bbdc5d183c5892b +Subproject commit fdf0148834b8fd7ec6c15389ea0f5f4112ece6dc diff --git a/src/wgapi_freebsd.rs b/src/wgapi_freebsd.rs index 290c28f..add25ab 100644 --- a/src/wgapi_freebsd.rs +++ b/src/wgapi_freebsd.rs @@ -12,7 +12,7 @@ use crate::{ /// Requires FreeBSD version 13+. impl WireguardInterfaceApi for WGApi { /// Creates a WireGuard network interface. - fn create_interface(&self) -> Result<(), WireguardInterfaceError> { + fn create_interface(&mut self) -> Result<(), WireguardInterfaceError> { let _ = bsd::load_wireguard_kernel_module(); debug!("Creating interface {}", &self.ifname); bsd::create_interface(&self.ifname)?; From c99c0b209b19d9ce82e0f5d0d727261f8f9916b3 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 7 Nov 2025 14:00:33 +0100 Subject: [PATCH 07/12] Handle setting MTU on windows (#107) --- Cargo.lock | 12 +++---- src/wgapi_windows.rs | 77 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc83b8e..fd14d6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.44" +version = "1.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" dependencies = [ "find-msvc-tools", "shlex", @@ -914,9 +914,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1182,9 +1182,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs index 0633bf2..0f2efa1 100644 --- a/src/wgapi_windows.rs +++ b/src/wgapi_windows.rs @@ -1,6 +1,8 @@ use std::{ collections::HashMap, + ffi::OsStr, net::IpAddr, + os::windows::ffi::OsStrExt, str::FromStr, sync::{LazyLock, Mutex}, }; @@ -10,12 +12,17 @@ use thiserror::Error; use windows::{ Win32::{ Foundation::{ERROR_BUFFER_OVERFLOW, NO_ERROR}, - NetworkManagement::IpHelper::{ - DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, DNS_SETTING_IPV6, - DNS_SETTING_NAMESERVER, GAA_FLAG_INCLUDE_PREFIX, GetAdaptersAddresses, - IP_ADAPTER_ADDRESSES_LH, SetInterfaceDnsSettings, + NetworkManagement::{ + IpHelper::{ + ConvertInterfaceGuidToLuid, DNS_INTERFACE_SETTINGS, + DNS_INTERFACE_SETTINGS_VERSION1, DNS_SETTING_IPV6, DNS_SETTING_NAMESERVER, + GAA_FLAG_INCLUDE_PREFIX, GetAdaptersAddresses, GetIpInterfaceEntry, + IP_ADAPTER_ADDRESSES_LH, InitializeIpInterfaceEntry, MIB_IPINTERFACE_ROW, + SetInterfaceDnsSettings, SetIpInterfaceEntry, + }, + Ndis::NET_LUID_LH, }, - Networking::WinSock::AF_UNSPEC, + Networking::WinSock::{ADDRESS_FAMILY, AF_INET, AF_INET6, AF_UNSPEC}, System::Com::CLSIDFromString, }, core::{GUID, PCSTR, PCWSTR, PSTR}, @@ -136,6 +143,55 @@ fn get_adapter_guid(adapter_name: &str) -> Result { guid.ok_or_else(|| WindowsError::AdapterNotFound(adapter_name.to_string())) } +/// Sets both IPv4 and IPv6 MTU on specified interface. +fn set_interface_mtu(interface_name: &str, mtu: u32) -> Result<(), WindowsError> { + debug!("Setting interface {interface_name} MTU to {mtu}"); + let guid = get_adapter_guid(interface_name)?; + + // Convert interface GUID to LUID. + let mut luid = NET_LUID_LH::default(); + let res = unsafe { ConvertInterfaceGuidToLuid(&guid, &mut luid) }; + if res.0 != 0 { + error!( + "ConvertInterfaceGuidToLuid call failed, error value: {}", + res.0 + ); + return Err(WindowsError::NonZeroReturnValue(res.0)); + } + + // Helper function, sets MTU for given IP family. + fn set_mtu_for_family(luid: NET_LUID_LH, family: u16, mtu: u32) -> Result<(), WindowsError> { + // InitializeIpInterfaceEntry has to be called before get/set operations. + let mut row = MIB_IPINTERFACE_ROW::default(); + unsafe { InitializeIpInterfaceEntry(&mut row) }; + + // Load current configuration. + row.InterfaceLuid = luid; + row.Family = ADDRESS_FAMILY(family); + let res = unsafe { GetIpInterfaceEntry(&mut row) }; + if res.0 != 0 { + error!("GetIpInterfaceEntry call failed, error value: {}", res.0); + return Err(WindowsError::NonZeroReturnValue(res.0)); + } + + // Modify the configuration and apply. + row.NlMtu = mtu; + let res = unsafe { SetIpInterfaceEntry(&mut row) }; + if res.0 != 0 { + error!("SetIpInterfaceEntry call failed, error value: {}", res.0); + return Err(WindowsError::NonZeroReturnValue(res.0)); + } + Ok(()) + } + + // Set MTU for both IP addr families. + set_mtu_for_family(luid, AF_INET.0, mtu)?; + set_mtu_for_family(luid, AF_INET6.0, mtu)?; + + info!("Set interface {interface_name} MTU to {mtu}"); + Ok(()) +} + impl From for Peer { fn from(peer: wireguard_nt::WireguardPeer) -> Self { Self { @@ -173,7 +229,7 @@ impl From for Host { /// Converts an str to wide (u16), null-terminated fn str_to_wide_null_terminated(s: &str) -> Vec { - s.encode_utf16().chain(std::iter::once(0)).collect() + OsStr::new(s).encode_wide().chain(Some(0)).collect() } /// Manages interfaces created with Windows kernel using https://git.zx2c4.com/wireguard-nt. @@ -272,7 +328,14 @@ impl WireguardInterfaceApi for WGApi { .set_default_route(&addresses, &interface) .map_err(WindowsError::from)?; - // Bring the adapter up + // Set MTU + if let Some(mtu) = config.mtu { + set_interface_mtu(&self.ifname, mtu)?; + // Turn it off and on again. + adapter.down().map_err(WindowsError::from)?; + } + + // Bring the adapter up. debug!("Bringing up adapter {}", self.ifname); adapter.up().map_err(WindowsError::from)?; From 9af5e79f60fbcf8ffa1460364d719cb1eec9f360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 14 Nov 2025 18:44:58 +0100 Subject: [PATCH 08/12] Update dependencies --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- boringtun | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd14d6c..d66e682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,22 +53,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -196,9 +196,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "camino" @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "shlex", @@ -345,9 +345,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core", @@ -471,9 +471,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fs-err" @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -624,22 +624,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -1182,9 +1182,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.109" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", diff --git a/boringtun b/boringtun index fdf0148..20d92e5 160000 --- a/boringtun +++ b/boringtun @@ -1 +1 @@ -Subproject commit fdf0148834b8fd7ec6c15389ea0f5f4112ece6dc +Subproject commit 20d92e555303ae2d7c54f759c3cb4b222eec9d54 From 93f174deced6ba9577c24812b16e2f756da22b01 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:51:52 +0100 Subject: [PATCH 09/12] Fix resolvconf on debian 13 (#108) * fix resolvconf * Update Cargo.toml * Update utils.rs * Update src/utils.rs Co-authored-by: Adam --------- Co-authored-by: Adam --- Cargo.lock | 1 + Cargo.toml | 5 ++- src/dependencies.rs | 46 +++++++--------------- src/utils.rs | 94 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 109 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d66e682..7a355cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,7 @@ dependencies = [ "netlink-packet-wireguard", "netlink-sys", "nix", + "regex", "serde", "serde_test", "thiserror 2.0.17", diff --git a/Cargo.toml b/Cargo.toml index 78d3c58..c6f3bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ windows = { version = "0.62", features = [ "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_Com", -]} +] } wireguard-nt = "0.5.0" [target.'cfg(target_os = "linux")'.dependencies] @@ -49,6 +49,9 @@ netlink-packet-utils = "0.6" netlink-packet-wireguard = "0.2" netlink-sys = "0.8" +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))'.dependencies] +regex = "1.12" + [features] default = ["serde"] check_dependencies = [] diff --git a/src/dependencies.rs b/src/dependencies.rs index 2e0d33c..0dbf577 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -1,6 +1,6 @@ use std::env; -use crate::error::WireguardInterfaceError; +use crate::{error::WireguardInterfaceError, utils::get_command_path}; #[cfg(target_os = "linux")] const COMMANDS: [&str; 2] = ["resolvconf", "ip"]; @@ -16,37 +16,19 @@ 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).any(|dir| { - trace!("Trying to find {cmd} in {dir:?}"); - match dir.join(cmd).try_exists() { - Ok(true) => { - debug!("{cmd} found in {dir:?}"); - true - } - Ok(false) => { - trace!("{cmd} not found in {dir:?}"); - false - } - Err(err) => { - warn!("Error while checking for {cmd} in {dir:?}: {err}"); - false - } - } - }) + let paths = env::var_os("PATH").ok_or_else(|| { + WireguardInterfaceError::MissingDependency("Environment variable `PATH` not found".into()) }); - if let Some(cmd) = missing { - Err(WireguardInterfaceError::MissingDependency(format!( - "Command `{cmd}` required by wireguard-rs couldn't be found. The following directories were checked: {paths:?}" - ))) - } else { - debug!("All commands required by wireguard-rs are available"); - Ok(()) - } + // Find the missing command to provide a more informative error message later. + let missing_command = COMMANDS + .iter() + .find(|cmd| get_command_path(cmd).map_or(true, |path_opt| path_opt.is_none())); + + missing_command.map_or_else(|| { + debug!("All commands required by wireguard-rs are available"); + Ok(()) + }, |cmd| Err(WireguardInterfaceError::MissingDependency(format!( + "Command `{cmd}` required by wireguard-rs couldn't be found. The following directories were checked: {paths:?}" + )))) } diff --git a/src/utils.rs b/src/utils.rs index 8f8852f..6826af8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,17 @@ #[cfg(target_os = "macos")] -use std::io::{BufRead, BufReader, Cursor, Error as IoError}; +use std::io::{Cursor, Error as IoError}; #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] use std::net::{Ipv4Addr, Ipv6Addr}; -use std::net::{SocketAddr, ToSocketAddrs}; +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] +use std::path::Path; #[cfg(target_os = "linux")] use std::{collections::HashSet, fs::OpenOptions}; #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] use std::{io::Write, process::Stdio}; +use std::{ + io::{BufRead, BufReader}, + net::{SocketAddr, ToSocketAddrs}, +}; #[cfg(not(target_os = "windows"))] use std::{net::IpAddr, process::Command}; @@ -24,6 +29,55 @@ use crate::{ #[cfg(target_os = "linux")] use crate::{IpVersion, check_command_output_status, netlink}; +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] +const IFACE_ORDER_PATH: &str = "/etc/resolvconf/interface-order"; + +/// Constructs the resolvconf interface name for manipulating DNS settings. +/// Resolvconf may be symlinked to resolvectl on some systems that use systemd-resolved. +/// In such cases there is no need to prefix the interface name and this function just returns +/// the base interface name. +/// +/// On other systems, especially those that don't use systemd-resolved (Debian 13) +/// resolvconf may be a binary from the "resolvconf" package. In such cases, this function +/// reads the interface-order file to find a highest priority interface prefix and constructs +/// the full interface name prefixing the base interface name with the found prefix. +#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] +fn construct_resolvconf_ifname(base_ifname: &str) -> String { + let iface_order = Path::new(IFACE_ORDER_PATH); + if iface_order.exists() { + // Check if resolvconf command is a symlink or a binary + if let Ok(Some(resolvconf_path)) = get_command_path("resolvconf") { + if let Ok(metadata) = std::fs::symlink_metadata(&resolvconf_path) { + if !metadata.file_type().is_symlink() { + // It's a binary, proceed to read interface_order file + let iface_regex = regex::Regex::new(r"^([A-Za-z0-9-]+)\*$").unwrap(); + if let Ok(file) = std::fs::File::open(iface_order) { + let reader = BufReader::new(file); + if let Some(constructed_ifname) = reader.lines().map_while(Result::ok).find_map(|line| { + let iface = line.trim(); + iface_regex.captures(iface).and_then(|captures| { + captures.get(1).map(|matched_iface| { + // Output format: . + let constructed_ifname = + format!("{}.{base_ifname}", matched_iface.as_str()); + debug!( + "Constructed interface name from interface_order: {constructed_ifname}" + ); + constructed_ifname + }) + }) + }) { + return constructed_ifname; + } + } + } + } + } + } + + base_ifname.into() +} + #[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))] pub(crate) fn configure_dns( ifname: &str, @@ -36,7 +90,8 @@ pub(crate) fn configure_dns( domains: {search_domains:?}" ); let mut cmd = Command::new("resolvconf"); - let mut args = vec!["-a", ifname, "-m", "0"]; + let ifname = construct_resolvconf_ifname(ifname); + let mut args = vec!["-a", &ifname, "-m", "0"]; // Set the exclusive flag if no search domains are provided, // making the DNS servers a preferred route for any domain if search_domains.is_empty() { @@ -170,7 +225,8 @@ 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> { debug!("Removing DNS configuration for interface {ifname}"); - let args = ["-d", ifname, "-f"]; + let ifname = construct_resolvconf_ifname(ifname); + let args = ["-d", &ifname, "-f"]; debug!("Executing resolvconf with args: {args:?}"); let mut cmd = Command::new("resolvconf"); let output = cmd.args(args).output()?; @@ -537,3 +593,33 @@ pub(crate) fn resolve(addr: &str) -> Result .next() .ok_or_else(error) } + +pub(crate) fn get_command_path( + command: &str, +) -> Result, WireguardInterfaceError> { + use std::env; + + debug!("Searching for command {command} in PATH"); + let paths = env::var_os("PATH").ok_or_else(|| { + WireguardInterfaceError::MissingDependency("Environment variable `PATH` not found".into()) + })?; + debug!("PATH variable: {paths:?}"); + + Ok(env::split_paths(&paths).find_map(|dir| { + let full_path = dir.join(command); + match full_path.try_exists() { + Ok(true) => { + debug!("Command {command} found in {dir:?}"); + Some(full_path) + } + Ok(false) => { + debug!("Command {command} not found in {dir:?}"); + None + } + Err(err) => { + warn!("Error while checking for {command} in {dir:?}: {err}"); + None + } + } + })) +} From c4d1f69585b5fc5c4f21d9d98b1fd9cc96d4d24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 5 Dec 2025 14:41:18 +0100 Subject: [PATCH 10/12] Update to borintun with proper UAPI handing --- Cargo.lock | 52 ++++++++++++++++++++++++++-------------------------- boringtun | 2 +- src/host.rs | 3 +++ 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a355cc..b3891f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.46" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "shlex", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -537,9 +537,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -655,9 +655,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -686,9 +686,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -1183,9 +1183,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1304,9 +1304,9 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1315,9 +1315,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1326,9 +1326,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -1347,9 +1347,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1845,9 +1845,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] diff --git a/boringtun b/boringtun index 20d92e5..fd82bf9 160000 --- a/boringtun +++ b/boringtun @@ -1 +1 @@ -Subproject commit 20d92e555303ae2d7c54f759c3cb4b222eec9d54 +Subproject commit fd82bf92f1a3901cd307923670ded2a244b564bc diff --git a/src/host.rs b/src/host.rs index 0dccdac..c0fc4d0 100644 --- a/src/host.rs +++ b/src/host.rs @@ -1,4 +1,7 @@ //! Host interface configuration +//! +//! Reference: +//! * WireGuard [Cross-platform Userspace Implementation](https://www.wireguard.com/xplatform/) use std::{ collections::HashMap, From 407beaa5c036bf7da522336e0f7ba922e56f9fe5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 8 Dec 2025 10:31:23 +0100 Subject: [PATCH 11/12] Fix statistics with a lot of routes (#101) (#110) When there are a lot of routes on the tunnel, the data does not fit into a single NLA. And multiple NLA's are used to pass the data. We need to merge this data in order to have the complete data. Signed-off-by: Jean-Louis Dupond Co-authored-by: Jean-Louis Dupond --- src/host.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/host.rs b/src/host.rs index c0fc4d0..ae3c16d 100644 --- a/src/host.rs +++ b/src/host.rs @@ -358,11 +358,10 @@ impl Host { WgDeviceAttrs::Peers(nlas) => { for nla in nlas { let peer = Peer::from_nlas(nla); - // On some systems, a dual-stack (IPv4 + IPv6) configuration creates separate peer entries - // for each address family. These entries share the same public key, so inserting a new - // peer would overwrite the existing one. To avoid this, we first check if the peer - // already exists and, if it does, update its statistics instead of replacing it. + // On tunnels with a lot of routes, the peer info may be split across multiple + // entries. We need to merge them here. // https://github.com/DefGuard/client/issues/617 + // https://github.com/DefGuard/wireguard-rs/issues/101 if let Some(existing_peer) = self.peers.get_mut(&peer.public_key) { existing_peer.rx_bytes += peer.rx_bytes; existing_peer.tx_bytes += peer.tx_bytes; @@ -373,6 +372,20 @@ impl Host { (None, Some(y)) => Some(y), (None, None) => None, }; + if peer.preshared_key.is_some() { + existing_peer.preshared_key = peer.preshared_key; + } + if peer.protocol_version.is_some() { + existing_peer.protocol_version = peer.protocol_version; + } + if peer.endpoint.is_some() { + existing_peer.endpoint = peer.endpoint; + } + if peer.persistent_keepalive_interval.is_some() { + existing_peer.persistent_keepalive_interval = + peer.persistent_keepalive_interval; + } + existing_peer.allowed_ips.extend(peer.allowed_ips); } else { self.peers.insert(peer.public_key.clone(), peer); } From 2fd9d19c5f27f3a6ab19c2bb7495d9ff76b484c5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 8 Dec 2025 13:42:40 +0100 Subject: [PATCH 12/12] v0.8.0 (#112) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3891f0..2410bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.9.0" +version = "0.8.0" dependencies = [ "base64", "boringtun", diff --git a/Cargo.toml b/Cargo.toml index c6f3bcb..e269e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.9.0" +version = "0.8.0" edition = "2024" rust-version = "1.85" description = "A unified multi-platform high-level API for managing WireGuard interfaces"