diff --git a/.gitignore b/.gitignore index c403c34..a5a321e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .idea/ +.vscode/ diff --git a/Cargo.lock b/Cargo.lock index e84d24a..cd94f7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.76" @@ -91,6 +100,7 @@ name = "defguard_wireguard_rs" version = "0.3.2" dependencies = [ "base64", + "env_logger", "libc", "log", "netlink-packet-core", @@ -105,6 +115,29 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fiat-crypto" version = "0.2.5" @@ -122,18 +155,53 @@ dependencies = [ "wasi", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "libc" version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + [[package]] name = "memoffset" version = "0.9.0" @@ -268,6 +336,35 @@ dependencies = [ "getrandom", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustc_version" version = "0.4.0" @@ -277,6 +374,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "semver" version = "1.0.20" @@ -320,6 +430,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.51" @@ -352,6 +471,169 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "x25519-dalek" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index 9399a9f..55f787b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" [dev-dependencies] +env_logger = "0.10" x25519-dalek = { version = "2.0", features = ["getrandom", "static_secrets"] } [target.'cfg(target_os = "freebsd")'.dependencies] diff --git a/examples/client.rs b/examples/client.rs index 2c84449..9c02a0f 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -43,7 +43,10 @@ fn main() -> Result<(), Box> { peers: vec![peer], }; + #[cfg(not(windows))] wgapi.configure_interface(&interface_config)?; + #[cfg(windows)] + wgapi.configure_interface(&interface_config, &[])?; wgapi.configure_peer_routing(&interface_config.peers)?; Ok(()) diff --git a/examples/server.rs b/examples/server.rs index e1014ee..2d6538e 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -43,7 +43,10 @@ fn main() -> Result<(), Box> { println!("Prepared interface configuration: {interface_config:?}"); // apply initial interface configuration + #[cfg(not(windows))] wgapi.configure_interface(&interface_config)?; + #[cfg(windows)] + wgapi.configure_interface(&interface_config, &[])?; // read current interface status let host = wgapi.read_interface_data()?; diff --git a/examples/userspace.rs b/examples/userspace.rs index 9c5d6b3..6ae0843 100644 --- a/examples/userspace.rs +++ b/examples/userspace.rs @@ -1,7 +1,6 @@ -use defguard_wireguard_rs::{ - host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WireguardApiUserspace, - WireguardInterfaceApi, -}; +#[cfg(target_os = "mac_os")] +use defguard_wireguard_rs::WireguardApiUserspace; +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration}; use std::{ io::{stdin, stdout, Read, Write}, net::SocketAddr, @@ -16,6 +15,7 @@ fn pause() { stdin().read_exact(&mut [0]).unwrap(); } +#[cfg(target_os = "mac_os")] fn main() -> Result<(), Box> { // Setup API struct for interface management let ifname: String = if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") { @@ -58,7 +58,10 @@ fn main() -> Result<(), Box> { peers: vec![peer], }; + #[cfg(not(windows))] api.configure_interface(&interface_config)?; + #[cfg(windows)] + api.configure_interface(&interface_config, &[])?; println!("Interface {ifname} configured."); pause(); @@ -69,3 +72,6 @@ fn main() -> Result<(), Box> { Ok(()) } + +#[cfg(not(mac_os))] +fn main() {} diff --git a/src/error.rs b/src/error.rs index 62e564a..0d60326 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,4 +32,9 @@ pub enum WireguardInterfaceError { KernelNotSupported, #[error("DNS error")] DnsError, + #[error("Service installation failed")] + ServiceInstallationFailed { + err: std::io::Error, + message: String, + }, } diff --git a/src/lib.rs b/src/lib.rs index cec8872..1197aae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,8 @@ mod wgapi_freebsd; mod wgapi_linux; #[cfg(target_family = "unix")] mod wgapi_userspace; +#[cfg(target_family = "windows")] +mod wgapi_windows; mod wireguard_interface; #[macro_use] @@ -92,6 +94,8 @@ pub use wgapi_freebsd::WireguardApiFreebsd; pub use wgapi_linux::WireguardApiLinux; #[cfg(target_family = "unix")] pub use wgapi_userspace::WireguardApiUserspace; +#[cfg(target_os = "windows")] +pub use wgapi_windows::WireguardApiWindows; pub use wireguard_interface::WireguardInterfaceApi; /// Host WireGuard interface configuration diff --git a/src/utils.rs b/src/utils.rs index 18b4732..f12f5ba 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -284,6 +284,15 @@ pub(crate) fn add_peer_routing( Ok(()) } +/// Helper function to add routing. +#[cfg(target_os = "windows")] +pub(crate) fn add_peer_routing( + peers: &[Peer], + ifname: &str, +) -> Result<(), WireguardInterfaceError> { + Ok(()) +} + pub enum IpVersion { IPv4, IPv6, @@ -316,6 +325,11 @@ pub(crate) fn get_gateway(ip_version: &IpVersion) -> Result Result { + Ok(String::new()) +} + /// Clean fwmark rules while removing interface same as in wg-quick #[cfg(target_os = "linux")] pub(crate) fn clean_fwmark_rules(fwmark: u32) -> Result<(), WireguardInterfaceError> { diff --git a/src/wgapi.rs b/src/wgapi.rs index f3cb150..63bfb01 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -7,6 +7,8 @@ use crate::WireguardApiFreebsd; use crate::WireguardApiLinux; #[cfg(target_family = "unix")] use crate::WireguardApiUserspace; +#[cfg(target_os = "windows")] +use crate::WireguardApiWindows; use crate::{ Host, InterfaceConfiguration, IpAddrMask, Key, Peer, WireguardInterfaceApi, WireguardInterfaceError, @@ -25,12 +27,18 @@ impl WGApi { /// Will return `WireguardInterfaceError` is platform is not supported. pub fn new(ifname: String, userspace: bool) -> Result { if userspace { - if cfg!(target_family = "unix") { - Ok(Self(Box::new(WireguardApiUserspace::new(ifname)?))) - } else { - Err(WireguardInterfaceError::UserspaceNotSupported) - } + #[cfg(target_family = "unix")] + return Ok(Self(Box::new(WireguardApiUserspace::new(ifname)?))); + + #[cfg(not(target_family = "unix"))] + return Err(WireguardInterfaceError::UserspaceNotSupported); } else { + #[cfg(target_os = "windows")] + return Ok(Self(Box::new(WireguardApiWindows::new(ifname)))); + + #[cfg(target_os = "macos")] + return Ok(Self(Box::new(WireguardApiUserspace::new(ifname)?))); + #[cfg(target_os = "linux")] return Ok(Self(Box::new(WireguardApiLinux::new(ifname)))); @@ -56,6 +64,7 @@ impl WireguardInterfaceApi for WGApi { self.0.configure_peer_routing(peers) } + #[cfg(not(target_os = "windows"))] fn configure_interface( &self, config: &InterfaceConfiguration, @@ -63,6 +72,15 @@ impl WireguardInterfaceApi for WGApi { self.0.configure_interface(config) } + #[cfg(target_os = "windows")] + fn configure_interface( + &self, + config: &InterfaceConfiguration, + dns: &[IpAddr], + ) -> Result<(), WireguardInterfaceError> { + self.0.configure_interface(config, dns) + } + fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { self.0.remove_interface() } diff --git a/src/wgapi_windows.rs b/src/wgapi_windows.rs new file mode 100644 index 0000000..5bdf1d3 --- /dev/null +++ b/src/wgapi_windows.rs @@ -0,0 +1,286 @@ +use std::{ + env, + fs::File, + io::{self, BufRead, BufReader, Cursor, Write}, + net::{IpAddr, SocketAddr}, + process::Command, + str::FromStr, + thread::sleep, + time::{Duration, SystemTime}, +}; + +use crate::{ + error::WireguardInterfaceError, + host::{Host, Peer}, + key::Key, + net::IpAddrMask, + InterfaceConfiguration, WireguardInterfaceApi, +}; + +/// Manages interfaces created with Windows kernel using https://git.zx2c4.com/wireguard-nt. +#[derive(Clone)] +pub struct WireguardApiWindows { + ifname: String, +} + +impl WireguardApiWindows { + pub fn new(ifname: String) -> Self { + Self { ifname } + } +} + +impl WireguardInterfaceApi for WireguardApiWindows { + fn create_interface(&self) -> Result<(), WireguardInterfaceError> { + info!("Opening/creating interface {}", self.ifname); + Ok(()) + } + + fn assign_address(&self, address: &IpAddrMask) -> Result<(), WireguardInterfaceError> { + debug!("Assigning address {address} to interface {}", self.ifname); + Ok(()) + } + + fn configure_interface( + &self, + config: &InterfaceConfiguration, + dns: &[IpAddr], + ) -> Result<(), WireguardInterfaceError> { + info!( + "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)?; + + let mut wireguard_configuration = format!( + "[Interface]\nPrivateKey = {}\nAddress = {}\n", + config.prvkey, config.address + ); + + if !dns.is_empty() { + let dns_addresses = format!( + "\nDNS = {}", + dns.iter() + .map(|v| v.to_string()) + .collect::>() + .join(",") + ); + wireguard_configuration.push_str(dns_addresses.as_str()); + } + + for peer in &config.peers { + wireguard_configuration.push_str( + format!("\n[Peer]\nPublicKey = {}", peer.public_key.to_string()).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()); + } + + let allowed_ips = format!( + "\nAllowedIPs = {}", + peer.allowed_ips + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(",") + ); + wireguard_configuration.push_str(allowed_ips.as_str()); + } + + info!("Prepared WireGuard configuration: {wireguard_configuration}",); + file.write_all(wireguard_configuration.as_bytes())?; + + // Check for existing service and remove it + 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 already exists + if output.status.success() { + Command::new("wireguard") + .arg("/uninstalltunnelservice") + .arg(&self.ifname) + .output()?; + + 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 = counter + 1; + } + } + + let service_installation_output = Command::new("wireguard") + .arg("/installtunnelservice") + .arg(file_path) + .output() + .map_err(|err| { + error!("Failed to create interface. Error: {err}"); + let message = err.to_string(); + WireguardInterfaceError::ServiceInstallationFailed { err, message } + })?; + + info!("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 { + err: io::Error::new(io::ErrorKind::Other, "Cannot create service"), + message, + }); + } + + Ok(()) + } + + fn configure_peer_routing(&self, _peers: &[Peer]) -> Result<(), WireguardInterfaceError> { + Ok(()) + } + + fn remove_interface(&self) -> Result<(), WireguardInterfaceError> { + info!("Removing interface {}", self.ifname); + + Command::new("wireguard") + .arg("/uninstalltunnelservice") + .arg(&self.ifname) + .output() + .map_err(|err| { + error!("Failed to remove interface. Error: {err}"); + WireguardInterfaceError::CommandExecutionFailed(err) + })?; + + Ok(()) + } + + fn configure_peer(&self, peer: &Peer) -> Result<(), WireguardInterfaceError> { + info!("Configuring peer {peer:?} on interface {}", self.ifname); + Ok(()) + } + + fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> { + info!( + "Removing peer with public key {peer_pubkey} from interface {}", + self.ifname + ); + Ok(()) + } + + 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); + } + } + } + + debug!("Read interface data: {host:?}"); + Ok(host) + } + + fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> { + info!( + "Configuring DNS for interface {}, using address: {dns:?}", + self.ifname + ); + Ok(()) + } +} diff --git a/src/wireguard_interface.rs b/src/wireguard_interface.rs index ac24524..c4493b9 100644 --- a/src/wireguard_interface.rs +++ b/src/wireguard_interface.rs @@ -17,11 +17,19 @@ pub trait WireguardInterfaceApi { fn configure_peer_routing(&self, peers: &[Peer]) -> Result<(), WireguardInterfaceError>; /// 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], + ) -> Result<(), WireguardInterfaceError>; + /// Removes the WireGuard interface being managed. /// /// Meant to be used in `drop` method for a given API struct.