diff --git a/rust/bin-shared/src/network_changes/windows.rs b/rust/bin-shared/src/network_changes/windows.rs index 6a01a7d95..5bfe8b0c7 100644 --- a/rust/bin-shared/src/network_changes/windows.rs +++ b/rust/bin-shared/src/network_changes/windows.rs @@ -64,6 +64,8 @@ use crate::platform::DnsControlMethod; use anyhow::{Context as _, Result, anyhow}; +use std::collections::HashMap; +use std::sync::Mutex; use std::thread; use tokio::sync::{ mpsc::{self, error::TrySendError}, @@ -251,6 +253,8 @@ struct Listener<'a> { #[windows_implement::implement(INetworkEvents)] struct Callback { tx: NotifySender, + firezone_network_profile_id: Option, + network_states: Mutex>, } impl Drop for Listener<'_> { @@ -294,7 +298,13 @@ impl<'a> Listener<'a> { _com: com, }; - let cb = Callback { tx: tx.clone() }; + let cb = Callback { + tx: tx.clone(), + firezone_network_profile_id: get_network_id_of_firezone_adapter() + .inspect_err(|e| tracing::warn!("{e:#}")) + .ok(), + network_states: Default::default(), + }; let callbacks: INetworkEvents = cb.into(); @@ -340,6 +350,35 @@ impl<'a> Listener<'a> { } } +fn get_network_id_of_firezone_adapter() -> Result { + let profiles = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE) + .open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles") + .context("Failed to open registry key")?; + + for key in profiles.enum_keys() { + let guid = key.context("Failed to enumerate key")?; + + let profile_name = profiles + .open_subkey(&guid) + .with_context(|| format!("Failed to open key `{guid}`"))? + .get_value::("ProfileName") + .context("Failed to get profile name")?; + + if profile_name == "Firezone" { + let uuid = guid.trim_start_matches("{").trim_end_matches("}"); + let uuid = uuid + .parse::() + .context("Failed to parse key as UUID")?; + + tracing::debug!(%uuid, "Found `Firezone` network profile id"); + + return Ok(GUID::from_u128(uuid.as_u128())); + } + } + + anyhow::bail!("Unable to find `Firezone` profile network GUID") +} + // impl INetworkEvents_Impl for Callback_Impl { fn NetworkAdded(&self, _networkid: &GUID) -> WinResult<()> { @@ -352,9 +391,34 @@ impl INetworkEvents_Impl for Callback_Impl { fn NetworkConnectivityChanged( &self, - _networkid: &GUID, - _newconnectivity: NLM_CONNECTIVITY, + networkid: &GUID, + newconnectivity: NLM_CONNECTIVITY, ) -> WinResult<()> { + if self + .firezone_network_profile_id + .is_some_and(|firezone| networkid == &firezone) + { + tracing::debug!("Ignoring network change for `Firezone` adapter"); + return Ok(()); + } + + let mut network_states = self + .network_states + .lock() + .unwrap_or_else(|e| e.into_inner()); + + if network_states + .get(networkid) + .is_some_and(|state| *state == newconnectivity) + { + tracing::debug!(?networkid, "Ignoring duplicate network change"); + return Ok(()); + } + + network_states.insert(*networkid, newconnectivity); + + tracing::debug!(?networkid, ?newconnectivity, "Network connectivity changed"); + // No reasonable way to translate this error into a Windows error self.tx.notify().ok(); Ok(()) diff --git a/website/src/components/Changelog/GUI.tsx b/website/src/components/Changelog/GUI.tsx index b8a2e7bfd..b7c716895 100644 --- a/website/src/components/Changelog/GUI.tsx +++ b/website/src/components/Changelog/GUI.tsx @@ -8,7 +8,13 @@ export default function GUI({ os }: { os: OS }) { return ( {/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */} - + + {os === OS.Windows && ( + + Optimizes network change detection. + + )} + {os === OS.Linux && ( diff --git a/website/src/components/Changelog/Headless.tsx b/website/src/components/Changelog/Headless.tsx index ab754e577..0db66c2d2 100644 --- a/website/src/components/Changelog/Headless.tsx +++ b/website/src/components/Changelog/Headless.tsx @@ -9,7 +9,13 @@ export default function Headless({ os }: { os: OS }) { return ( {/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */} - + + {os === OS.Windows && ( + + Optimizes network change detection. + + )} + Improves performance of relayed connections on IPv4-only systems.