From e1c38b0ea74d76c342ae812bb302b0d5e3f17f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Thu, 1 Feb 2024 13:29:33 +0100 Subject: [PATCH 1/6] Key can be converted from base16 string; other debugging improvements --- Cargo.lock | 24 +++++++------- examples/userspace.rs | 10 +++--- src/bsd/ifconfig.rs | 4 ++- src/bsd/route.rs | 71 ++++++++++++++++++++++++++++++++++++++++ src/bsd/wgio.rs | 15 +++++++-- src/key.rs | 75 ++++++++++++++++++++----------------------- src/wgapi.rs | 2 +- src/wgapi_freebsd.rs | 2 +- 8 files changed, 139 insertions(+), 64 deletions(-) create mode 100644 src/bsd/route.rs diff --git a/Cargo.lock b/Cargo.lock index 774cd4c..d2c07bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" [[package]] name = "anstyle-parse" @@ -181,9 +181,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" dependencies = [ "anstream", "anstyle", @@ -217,9 +217,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "log" @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -413,18 +413,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", diff --git a/examples/userspace.rs b/examples/userspace.rs index 650fbdd..74a2b82 100644 --- a/examples/userspace.rs +++ b/examples/userspace.rs @@ -1,6 +1,6 @@ -#[cfg(target_os = "mac_os")] -use defguard_wireguard_rs::WireguardApiUserspace; use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration}; +#[cfg(target_os = "macos")] +use defguard_wireguard_rs::{WireguardApiUserspace, WireguardInterfaceApi}; use std::{ io::{stdin, stdout, Read, Write}, net::SocketAddr, @@ -15,7 +15,7 @@ fn pause() { stdin().read_exact(&mut [0]).unwrap(); } -#[cfg(target_os = "mac_os")] +#[cfg(target_os = "macos")] fn main() -> Result<(), Box> { // Setup API struct for interface management let ifname: String = if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") { @@ -35,7 +35,7 @@ fn main() -> Result<(), Box> { let peer_key: Key = key.as_ref().try_into().unwrap(); let mut peer = Peer::new(peer_key.clone()); - log::info!("endpoint"); + println!("endpoint"); // Your WireGuard server endpoint which peer connects too let endpoint: SocketAddr = "10.20.30.40:55001".parse().unwrap(); // Peer endpoint and interval @@ -73,5 +73,5 @@ fn main() -> Result<(), Box> { Ok(()) } -#[cfg(not(mac_os))] +#[cfg(not(target_os = "macos"))] fn main() {} diff --git a/src/bsd/ifconfig.rs b/src/bsd/ifconfig.rs index 3ea3544..dfe899c 100644 --- a/src/bsd/ifconfig.rs +++ b/src/bsd/ifconfig.rs @@ -50,7 +50,9 @@ impl IfReq { .for_each(|(i, b)| ifr_name[i] = b); // First, try to load a kernel module for this type of network interface. - let mod_name = format!("if_{if_name}"); + // Omit digits at the end of interface name, e.g. "wg0" -> "if_wg". + let index = if_name.find(|c: char| c.is_ascii_digit()).unwrap_or(if_name.len()); + let mod_name = format!("if_{}", &if_name[0..index]); unsafe { // Ignore the return value for the time being. // Do the cast because `c_char` differs across platforms. diff --git a/src/bsd/route.rs b/src/bsd/route.rs new file mode 100644 index 0000000..0e27388 --- /dev/null +++ b/src/bsd/route.rs @@ -0,0 +1,71 @@ +use std::{mem::size_of, os::fd::AsRawFd, slice::from_raw_parts}; + +use libc::{ + c_uchar, getpid, in_addr, rt_metrics, rt_msghdr, sockaddr_dl, sockaddr_in, AF_INET, AF_LINK, + RTA_DST, RTA_IFP, RTA_NETMASK, RTF_GATEWAY, RTF_STATIC, RTF_UP, RTM_GET, RTM_VERSION, +}; +use nix::{ + sys::socket::{shutdown, socket, AddressFamily, Shutdown, SockFlag, SockType}, + unistd::{read, write}, +}; + +use crate::net::IpAddrMask; + +use super::{cast_bytes, sockaddr::SockAddrIn}; + +struct SockAddr { + +} + +struct RouteMessage { + header: rt_msghdr, + buffer: Vec, +} + +impl RouteMessage { + fn get() -> Self { + let header = rt_msghdr { + rtm_msglen: size_of::() as u16, + rtm_version: RTM_VERSION as u8, + rtm_type: RTM_GET as u8, + rtm_index: 0, // interface index if RTF_IFSCOPE + rtm_flags: RTF_UP | RTF_GATEWAY | RTF_STATIC, + rtm_addrs: RTA_DST | RTA_NETMASK | RTA_IFP, + rtm_pid: unsafe { getpid() }, + rtm_seq: 1, + rtm_errno: 0, + rtm_use: 0, + rtm_inits: 0, + rtm_rmx: rt_metrics { + rmx_locks: 0, + rmx_mtu: 0, + rmx_hopcount: 0, + rmx_expire: 0, + rmx_recvpipe: 0, + rmx_sendpipe: 0, + rmx_ssthresh: 0, + rmx_rtt: 0, + rmx_rttvar: 0, + rmx_pksent: 0, + rmx_state: 0, + rmx_filler: [0u32; 3], + }, + }; + Self { header, buffer: Vec::new() } + } + + fn add_dst(&mut self, addr: &SockAddrIn) { + self.header.rtm_flags |= RTA_DST; + let bytes = unsafe { cast_bytes(addr) }; + self.buffer.extend_from_slice(bytes); + } +} + +/// Get route to the given address. +pub fn get_route() { + //dest: &IpAddrMask) -> IpAddrMask { + let pid = unsafe { getpid() }; + println!("Pid {pid}"); + let mut rtmsg = RouteMessage::get(); + rtmsg.add_dst(&SockAddrIn::default()); +} diff --git a/src/bsd/wgio.rs b/src/bsd/wgio.rs index 2dd9c08..88e4621 100644 --- a/src/bsd/wgio.rs +++ b/src/bsd/wgio.rs @@ -62,11 +62,17 @@ impl WgDataIo { let socket = create_socket(AddressFamily::Unix).map_err(IoError::ReadIo)?; unsafe { // First do ioctl with empty `wg_data` to obtain buffer size. - read_wireguard_data(socket.as_raw_fd(), self).map_err(IoError::ReadIo)?; + if let Err(err) = read_wireguard_data(socket.as_raw_fd(), self) { + error!("WgDataIo first read error {err}"); + return Err(IoError::ReadIo(err)); + } // Allocate buffer. self.alloc_data()?; // Second call to ioctl with allocated buffer. - read_wireguard_data(socket.as_raw_fd(), self).map_err(IoError::ReadIo)?; + if let Err(err) = read_wireguard_data(socket.as_raw_fd(), self) { + error!("WgDataIo second read error {err}"); + return Err(IoError::ReadIo(err)); + } } Ok(()) @@ -75,7 +81,10 @@ impl WgDataIo { pub(super) fn write_data(&mut self) -> Result<(), IoError> { let socket = create_socket(AddressFamily::Unix).map_err(IoError::WriteIo)?; unsafe { - write_wireguard_data(socket.as_raw_fd(), self).map_err(IoError::WriteIo)?; + if let Err(err) = write_wireguard_data(socket.as_raw_fd(), self) { + error!("WgDataIo write error {err}"); + return Err(IoError::WriteIo(err)); + } } Ok(()) diff --git a/src/key.rs b/src/key.rs index 1979bc3..1b5d2ab 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,7 +1,7 @@ //! Public key utilities use std::{ - error, fmt, + fmt, hash::{Hash, Hasher}, str::FromStr, }; @@ -11,29 +11,21 @@ use serde::{Deserialize, Serialize}; const KEY_LENGTH: usize = 32; +/// Returns value of hex digit, if possible. +fn hex_value(char: u8) -> Option { + match char { + b'A'..=b'F' => Some(char - b'A' + 10), + b'a'..=b'f' => Some(char - b'a' + 10), + b'0'..=b'9' => Some(char - b'0'), + _ => None, + } +} + /// WireGuard key representation in binary form. #[derive(Clone, Default, Serialize, Deserialize)] +#[serde(try_from = "&str")] pub struct Key([u8; KEY_LENGTH]); -#[derive(Debug)] -pub enum KeyError { - InvalidCharacter(u8), - InvalidStringLength(usize), -} - -impl error::Error for KeyError {} - -impl fmt::Display for KeyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidCharacter(char) => { - write!(f, "Invalid character {char}") - } - Self::InvalidStringLength(length) => write!(f, "Invalid string length {length}"), - } - } -} - impl Key { /// Create a new key from buffer. #[must_use] @@ -71,28 +63,22 @@ impl Key { /// Converts a text string of hexadecimal digits to `Key`. /// /// # Errors - /// Will return `KeyError` if text string has wrong length, + /// Will return `DecodeError` if text string has wrong length, /// or contains an invalid character. - pub fn decode>(hex: T) -> Result { + pub fn decode>(hex: T) -> Result { let hex = hex.as_ref(); - let length = hex.len(); - if length != 64 { - return Err(KeyError::InvalidStringLength(length)); + if hex.len() != KEY_LENGTH * 2 { + return Err(DecodeError::InvalidLength); } - let hex_value = |char: u8| -> Result { - match char { - b'A'..=b'F' => Ok(char - b'A' + 10), - b'a'..=b'f' => Ok(char - b'a' + 10), - b'0'..=b'9' => Ok(char - b'0'), - _ => Err(KeyError::InvalidCharacter(char)), - } - }; - let mut key = [0; KEY_LENGTH]; for (index, chunk) in hex.chunks(2).enumerate() { - let msd = hex_value(chunk[0])?; - let lsd = hex_value(chunk[1])?; + let Some(msd) = hex_value(chunk[0]) else { + return Err(DecodeError::InvalidByte(index, chunk[0])); + }; + let Some(lsd) = hex_value(chunk[1]) else { + return Err(DecodeError::InvalidByte(index, chunk[1])); + }; key[index] = msd << 4 | lsd; } Ok(Self(key)) @@ -102,13 +88,20 @@ impl Key { impl TryFrom<&str> for Key { type Error = DecodeError; + /// Try to decode `Key` from base16 or base64 encoded string. fn try_from(value: &str) -> Result { - let v = BASE64_STANDARD.decode(value)?; - if v.len() == KEY_LENGTH { - let buf = v.try_into().map_err(|_| Self::Error::InvalidLength)?; - Ok(Self::new(buf)) + if value.len() == KEY_LENGTH * 2 { + // Try base16 + Key::decode(value) } else { - Err(Self::Error::InvalidLength) + // Try base64 + let v = BASE64_STANDARD.decode(value)?; + if v.len() == KEY_LENGTH { + let buf = v.try_into().map_err(|_| Self::Error::InvalidLength)?; + Ok(Self::new(buf)) + } else { + Err(Self::Error::InvalidLength) + } } } } diff --git a/src/wgapi.rs b/src/wgapi.rs index 63bfb01..375ba2b 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -45,7 +45,7 @@ impl WGApi { #[cfg(target_os = "freebsd")] return Ok(Self(Box::new(WireguardApiFreebsd::new(ifname)))); - #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] + #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "windows")))] Err(WireguardInterfaceError::KernelNotSupported) } } diff --git a/src/wgapi_freebsd.rs b/src/wgapi_freebsd.rs index 0e86b88..6576550 100644 --- a/src/wgapi_freebsd.rs +++ b/src/wgapi_freebsd.rs @@ -9,7 +9,7 @@ use crate::{ /// Manages interfaces created with FreeBSD kernel WireGuard module. /// -/// Requires FreeBSD version 14+. +/// Requires FreeBSD version 13+. #[derive(Clone)] pub struct WireguardApiFreebsd { ifname: String, From d6af4a0a9ac51dce99f378b447dc31e5664d0477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 5 Feb 2024 07:38:08 +0100 Subject: [PATCH 2/6] Format and update Cargo.lock --- Cargo.lock | 4 ++-- src/bsd/ifconfig.rs | 4 +++- src/wgapi.rs | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2c07bc..3c73721 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" [[package]] name = "getrandom" diff --git a/src/bsd/ifconfig.rs b/src/bsd/ifconfig.rs index dfe899c..b1485b7 100644 --- a/src/bsd/ifconfig.rs +++ b/src/bsd/ifconfig.rs @@ -51,7 +51,9 @@ impl IfReq { // First, try to load a kernel module for this type of network interface. // Omit digits at the end of interface name, e.g. "wg0" -> "if_wg". - let index = if_name.find(|c: char| c.is_ascii_digit()).unwrap_or(if_name.len()); + let index = if_name + .find(|c: char| c.is_ascii_digit()) + .unwrap_or(if_name.len()); let mod_name = format!("if_{}", &if_name[0..index]); unsafe { // Ignore the return value for the time being. diff --git a/src/wgapi.rs b/src/wgapi.rs index 375ba2b..91e3e1d 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -45,7 +45,12 @@ impl WGApi { #[cfg(target_os = "freebsd")] return Ok(Self(Box::new(WireguardApiFreebsd::new(ifname)))); - #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "windows")))] + #[cfg(not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "macos", + target_os = "windows" + )))] Err(WireguardInterfaceError::KernelNotSupported) } } From 37675448449a1416941742f2ff4ffdc2587216a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 5 Feb 2024 08:38:55 +0100 Subject: [PATCH 3/6] Remove unused code --- src/bsd/route.rs | 71 ------------------------------------------------ 1 file changed, 71 deletions(-) delete mode 100644 src/bsd/route.rs diff --git a/src/bsd/route.rs b/src/bsd/route.rs deleted file mode 100644 index 0e27388..0000000 --- a/src/bsd/route.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{mem::size_of, os::fd::AsRawFd, slice::from_raw_parts}; - -use libc::{ - c_uchar, getpid, in_addr, rt_metrics, rt_msghdr, sockaddr_dl, sockaddr_in, AF_INET, AF_LINK, - RTA_DST, RTA_IFP, RTA_NETMASK, RTF_GATEWAY, RTF_STATIC, RTF_UP, RTM_GET, RTM_VERSION, -}; -use nix::{ - sys::socket::{shutdown, socket, AddressFamily, Shutdown, SockFlag, SockType}, - unistd::{read, write}, -}; - -use crate::net::IpAddrMask; - -use super::{cast_bytes, sockaddr::SockAddrIn}; - -struct SockAddr { - -} - -struct RouteMessage { - header: rt_msghdr, - buffer: Vec, -} - -impl RouteMessage { - fn get() -> Self { - let header = rt_msghdr { - rtm_msglen: size_of::() as u16, - rtm_version: RTM_VERSION as u8, - rtm_type: RTM_GET as u8, - rtm_index: 0, // interface index if RTF_IFSCOPE - rtm_flags: RTF_UP | RTF_GATEWAY | RTF_STATIC, - rtm_addrs: RTA_DST | RTA_NETMASK | RTA_IFP, - rtm_pid: unsafe { getpid() }, - rtm_seq: 1, - rtm_errno: 0, - rtm_use: 0, - rtm_inits: 0, - rtm_rmx: rt_metrics { - rmx_locks: 0, - rmx_mtu: 0, - rmx_hopcount: 0, - rmx_expire: 0, - rmx_recvpipe: 0, - rmx_sendpipe: 0, - rmx_ssthresh: 0, - rmx_rtt: 0, - rmx_rttvar: 0, - rmx_pksent: 0, - rmx_state: 0, - rmx_filler: [0u32; 3], - }, - }; - Self { header, buffer: Vec::new() } - } - - fn add_dst(&mut self, addr: &SockAddrIn) { - self.header.rtm_flags |= RTA_DST; - let bytes = unsafe { cast_bytes(addr) }; - self.buffer.extend_from_slice(bytes); - } -} - -/// Get route to the given address. -pub fn get_route() { - //dest: &IpAddrMask) -> IpAddrMask { - let pid = unsafe { getpid() }; - println!("Pid {pid}"); - let mut rtmsg = RouteMessage::get(); - rtmsg.add_dst(&SockAddrIn::default()); -} From 91a2346d70a53695bb68541ce40dcc154c02591a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 5 Feb 2024 08:43:04 +0100 Subject: [PATCH 4/6] Rename KernelNotSupported to PlatformNotSupported --- src/error.rs | 2 +- src/wgapi.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 0d60326..20e9c6b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,7 +29,7 @@ pub enum WireguardInterfaceError { #[error("Userspace support is not available on this platform")] UserspaceNotSupported, #[error("Kernel support is not available on this platform")] - KernelNotSupported, + PlatformNotSupported, #[error("DNS error")] DnsError, #[error("Service installation failed")] diff --git a/src/wgapi.rs b/src/wgapi.rs index 91e3e1d..3229246 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -51,7 +51,7 @@ impl WGApi { target_os = "macos", target_os = "windows" )))] - Err(WireguardInterfaceError::KernelNotSupported) + Err(WireguardInterfaceError::PlatformNotSupported) } } } From 5a9126cb70d69143af949f31dd8f01fa3f193225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 5 Feb 2024 10:09:36 +0100 Subject: [PATCH 5/6] Revert KernelNotSupported --- src/error.rs | 2 +- src/wgapi.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 20e9c6b..0d60326 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,7 +29,7 @@ pub enum WireguardInterfaceError { #[error("Userspace support is not available on this platform")] UserspaceNotSupported, #[error("Kernel support is not available on this platform")] - PlatformNotSupported, + KernelNotSupported, #[error("DNS error")] DnsError, #[error("Service installation failed")] diff --git a/src/wgapi.rs b/src/wgapi.rs index 3229246..91e3e1d 100644 --- a/src/wgapi.rs +++ b/src/wgapi.rs @@ -51,7 +51,7 @@ impl WGApi { target_os = "macos", target_os = "windows" )))] - Err(WireguardInterfaceError::PlatformNotSupported) + Err(WireguardInterfaceError::KernelNotSupported) } } } From 2a7e0477a9fef406a9ddc303b9d221f17a51087e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 5 Feb 2024 14:45:44 +0100 Subject: [PATCH 6/6] Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c73721..7f35e26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.4.0" +version = "0.4.1" dependencies = [ "base64", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 0d1cfbe..bc74aba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.4.0" +version = "0.4.1" edition = "2021" description = "A unified multi-platform high-level API for managing WireGuard interfaces" license = "Apache-2.0"