From ab15be88b68040db19b37404dbb796b022ef2709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 30 Jun 2025 14:30:29 +0200 Subject: [PATCH 1/2] Flush interface on BSD --- Cargo.lock | 60 ++++++++++++++++++------------------ src/bsd/mod.rs | 72 ++++++++++++++++++++++++++++++++++++++++++-- src/bsd/route.rs | 6 ++-- src/bsd/sockaddr.rs | 23 ++++++++------ src/wgapi_freebsd.rs | 11 +++++++ src/wgapi_linux.rs | 2 +- 6 files changed, 130 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca2447e..1b814c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -28,33 +28,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -69,9 +69,9 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -99,9 +99,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -111,9 +111,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "cpufeatures" @@ -219,9 +219,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "jiff" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -243,9 +243,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "log" @@ -255,9 +255,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -370,9 +370,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -491,9 +491,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -554,9 +554,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "windows-sys" diff --git a/src/bsd/mod.rs b/src/bsd/mod.rs index fb2815b..b57a706 100644 --- a/src/bsd/mod.rs +++ b/src/bsd/mod.rs @@ -6,7 +6,12 @@ mod timespec; mod wgio; use std::{ - collections::HashMap, ffi::CString, mem::size_of, net::IpAddr, os::fd::OwnedFd, ptr::from_ref, + collections::HashMap, + ffi::{CStr, CString}, + mem::{size_of, MaybeUninit}, + net::IpAddr, + os::fd::OwnedFd, + ptr::from_ref, slice::from_raw_parts, }; @@ -15,7 +20,7 @@ use nix::{ sys::socket::{socket, AddressFamily, SockFlag, SockType}, }; use route::{DestAddrMask, GatewayLink}; -use sockaddr::{SockAddrDl, SockAddrIn, SockAddrIn6}; +use sockaddr::{SockAddrDl, SockAddrIn, SockAddrIn6, SocketFromRaw}; use thiserror::Error; use self::{ @@ -32,6 +37,13 @@ use crate::{ IpVersion, Key, WireguardInterfaceError, }; +// Note: these values differ across different platforms. +const AF_INET: u8 = libc::AF_INET as u8; +const AF_INET6: u8 = libc::AF_INET6 as u8; +const AF_LINK: u8 = libc::AF_LINK as u8; +const SA_IN_SIZE: u8 = size_of::() as u8; +const SA_IN6_SIZE: u8 = size_of::() as u8; + // nvlist key names static NV_LISTEN_PORT: &str = "listen-port"; static NV_FWMARK: &str = "user-cookie"; @@ -370,6 +382,62 @@ pub fn remove_address(if_name: &str, address: &IpAddrMask) -> Result<(), IoError } } +pub fn flush_interface(if_name: &str) -> Result<(), IoError> { + let ifname_c = CString::new(if_name).unwrap(); + let mut addr_to_remove = Vec::new(); + + let mut addrs = MaybeUninit::<*mut libc::ifaddrs>::uninit(); + let errno = unsafe { libc::getifaddrs(addrs.as_mut_ptr()) }; + if errno == 0 { + let addrs = unsafe { addrs.assume_init() }; + let mut addr = addrs; + while !addr.is_null() { + unsafe { + let name = CStr::from_ptr((*addr).ifa_name); + if name == ifname_c.as_c_str() { + let ifa_addr = (*addr).ifa_addr; + if ifa_addr.is_null() { + continue; + } + // Convert `ifa_addr` to `IpAddr`. + // Note: `ifa_addr` is actually `sockaddr_in` or `sockaddr_in6` depending on + // `sa_len` and `sa_family`. + if (*ifa_addr).sa_len == SA_IN_SIZE && (*ifa_addr).sa_family == AF_INET { + if let Some(sockaddr) = SockAddrIn::from_raw(ifa_addr) { + addr_to_remove.push(sockaddr.ip_addr()); + } + } else if (*ifa_addr).sa_len == SA_IN6_SIZE + && (*ifa_addr).sa_family == libc::AF_INET6 as u8 + { + if let Some(sockaddr) = SockAddrIn6::from_raw(ifa_addr) { + addr_to_remove.push(sockaddr.ip_addr()); + } + } + } + addr = (*addr).ifa_next; + }; + } + unsafe { libc::freeifaddrs(addrs) }; + } else { + debug!("getifaddrs returned {errno}"); + } + + for address in addr_to_remove { + match address { + IpAddr::V4(address) => { + let ifreq = IfReq::new_with_address(if_name, address); + ifreq.delete_address()?; + } + IpAddr::V6(address) => { + let ifreq6 = IfReq6::new_with_address(if_name, address); + ifreq6.delete_address()?; + } + } + } + + Ok(()) +} + pub fn get_mtu(if_name: &str) -> Result { let mut ifmtu = IfMtu::new(if_name); ifmtu.get_mtu() diff --git a/src/bsd/route.rs b/src/bsd/route.rs index 4981015..7e03c34 100644 --- a/src/bsd/route.rs +++ b/src/bsd/route.rs @@ -1,4 +1,5 @@ use std::{ + ffi::{CStr, CString}, mem::{size_of, MaybeUninit}, net::IpAddr, os::fd::{AsFd, AsRawFd}, @@ -214,14 +215,15 @@ pub(super) struct GatewayLink { /// Get an address for a given interface. First address is returned. fn if_addr(if_name: &str) -> Option { + let ifname_c = CString::new(if_name).unwrap(); let mut addrs = MaybeUninit::<*mut libc::ifaddrs>::uninit(); let errno = unsafe { libc::getifaddrs(addrs.as_mut_ptr()) }; if errno == 0 { let addrs = unsafe { addrs.assume_init() }; let mut addr = addrs; while !addr.is_null() { - let name = unsafe { std::ffi::CStr::from_ptr((*addr).ifa_name) }; - if name.to_str().unwrap() == if_name { + let name = unsafe { CStr::from_ptr((*addr).ifa_name) }; + if name == ifname_c.as_c_str() { if let Some(sockaddr) = unsafe { S::from_raw((*addr).ifa_addr) } { return Some(sockaddr); } diff --git a/src/bsd/sockaddr.rs b/src/bsd/sockaddr.rs index 5f2125b..c6bed8b 100644 --- a/src/bsd/sockaddr.rs +++ b/src/bsd/sockaddr.rs @@ -1,18 +1,11 @@ //! Convert binary `sockaddr_in` or `sockaddr_in6` (see netinet/in.h) to `SocketAddr`. use std::{ mem::{size_of, zeroed}, - net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, ptr::{copy, from_mut}, }; -use super::{cast_bytes, cast_ref}; - -// Note: these values differ across different platforms. -const AF_INET: u8 = libc::AF_INET as u8; -const AF_INET6: u8 = libc::AF_INET6 as u8; -const AF_LINK: u8 = libc::AF_LINK as u8; -const SA_IN_SIZE: u8 = size_of::() as u8; -const SA_IN6_SIZE: u8 = size_of::() as u8; +use super::{cast_bytes, cast_ref, AF_INET, AF_INET6, AF_LINK, SA_IN6_SIZE, SA_IN_SIZE}; pub(super) trait SocketFromRaw { unsafe fn from_raw(addr: *const libc::sockaddr) -> Option @@ -30,6 +23,13 @@ pub(super) struct SockAddrIn { zero: [u8; 8], } +impl SockAddrIn { + #[must_use] + pub(super) fn ip_addr(&self) -> IpAddr { + IpAddr::from(self.addr) + } +} + impl SocketFromRaw for SockAddrIn { /// Construct `SockAddrIn` from `libc::sockaddr`. unsafe fn from_raw(addr: *const libc::sockaddr) -> Option { @@ -117,6 +117,11 @@ impl SockAddrIn6 { scope_id: 0, } } + + #[must_use] + pub(super) fn ip_addr(&self) -> IpAddr { + IpAddr::from(self.addr) + } } impl SocketFromRaw for SockAddrIn6 { diff --git a/src/wgapi_freebsd.rs b/src/wgapi_freebsd.rs index fa76e22..acf69cc 100644 --- a/src/wgapi_freebsd.rs +++ b/src/wgapi_freebsd.rs @@ -64,6 +64,17 @@ impl WireguardInterfaceApi for WGApi { self.ifname ); + // Flush all IP addresses from WireGuard interface. + debug!( + "Flushing all existing IP addresses from interface {} before assigning a new one", + self.ifname + ); + bsd::flush_interface(&self.ifname)?; + debug!( + "All existing IP addresses flushed from interface {}", + self.ifname + ); + // Assign IP address to the interface. for address in &config.addresses { self.assign_address(address)?; diff --git a/src/wgapi_linux.rs b/src/wgapi_linux.rs index 2319108..a7e8f5a 100644 --- a/src/wgapi_linux.rs +++ b/src/wgapi_linux.rs @@ -39,7 +39,7 @@ impl WireguardInterfaceApi for WGApi { self.ifname ); - // flush all IP addresses + // Flush all IP addresses from WireGuard interface. debug!( "Flushing all existing IP addresses from interface {} before assigning a new one", self.ifname From bee8a39a0eb55691f224eef2df8448c3e97662d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 30 Jun 2025 14:55:52 +0200 Subject: [PATCH 2/2] Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b814c7..79bf09d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,7 +152,7 @@ dependencies = [ [[package]] name = "defguard_wireguard_rs" -version = "0.7.4" +version = "0.7.5" dependencies = [ "base64", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 560ebcf..0aaa63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard_wireguard_rs" -version = "0.7.4" +version = "0.7.5" edition = "2021" rust-version = "1.80" description = "A unified multi-platform high-level API for managing WireGuard interfaces"