mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
feat(windows): new module to listen for network changes (#3137)
This isn't hooked up to the GUI yet, it's a debug subcommand. I overheard that the other clients rebuild the tunnel when they change networks, I think? And this might be useful for debugging the issue where Chrome / other browsers don't flush their TCP connections when the tunnel comes up. It's also reference code for how to use COM interfaces in Rust. The official samples are a little sparse. So I wanted to get this checked in. 
This commit is contained in:
33
rust/Cargo.lock
generated
33
rust/Cargo.lock
generated
@@ -2073,6 +2073,7 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"windows 0.52.0",
|
||||
"windows-implement 0.52.0",
|
||||
"winreg 0.51.0",
|
||||
"wintun",
|
||||
"zip",
|
||||
@@ -5933,7 +5934,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
@@ -7324,7 +7325,7 @@ dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7430,7 +7431,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
"windows_aarch64_msvc 0.39.0",
|
||||
"windows_i686_gnu 0.39.0",
|
||||
"windows_i686_msvc 0.39.0",
|
||||
@@ -7464,6 +7465,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-implement 0.52.0",
|
||||
"windows-interface",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
@@ -7505,6 +7508,28 @@ dependencies = [
|
||||
"windows-tokens",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.41",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-metadata"
|
||||
version = "0.39.0"
|
||||
@@ -7876,7 +7901,7 @@ dependencies = [
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"windows-implement 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -36,6 +36,7 @@ url = { version = "2.5.0", features = ["serde"] }
|
||||
uuid = { version = "1.5.0", features = ["v4"] }
|
||||
tracing-panic = "0.1.1"
|
||||
zip = { version = "0.6.6", features = ["deflate", "time"], default-features = false }
|
||||
windows-implement = "0.52.0"
|
||||
|
||||
# These dependencies are locked behind `cfg(windows)` because they either can't compile at all on Linux, or they need native dependencies like glib that are difficult to get. Try not to add more here.
|
||||
|
||||
@@ -49,10 +50,16 @@ wintun = "0.3.2"
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.52.0"
|
||||
features = [
|
||||
# For implementing COM interfaces
|
||||
"implement",
|
||||
"Win32_Foundation",
|
||||
# Needed for deep_link module
|
||||
# For listening for network change events
|
||||
"Win32_Networking_NetworkListManager",
|
||||
# For deep_link module
|
||||
"Win32_Security",
|
||||
# Needed for deep_link module
|
||||
# COM is needed to listen for network change events
|
||||
"Win32_System_Com",
|
||||
# For deep_link module
|
||||
"Win32_System_SystemServices",
|
||||
]
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ mod device_id;
|
||||
mod elevation;
|
||||
mod gui;
|
||||
mod logging;
|
||||
mod network_changes;
|
||||
mod resolvers;
|
||||
mod settings;
|
||||
mod wintun_install;
|
||||
@@ -69,6 +70,7 @@ pub(crate) fn run() -> Result<()> {
|
||||
}
|
||||
Some(Cmd::DebugCrash) => debug_commands::crash(),
|
||||
Some(Cmd::DebugHostname) => debug_commands::hostname(),
|
||||
Some(Cmd::DebugNetworkChanges) => debug_commands::network_changes(),
|
||||
Some(Cmd::DebugPipeServer) => debug_commands::pipe_server(),
|
||||
Some(Cmd::DebugWintun) => debug_commands::wintun(cli),
|
||||
// If we already tried to elevate ourselves, don't try again
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum CliCommands {
|
||||
Debug,
|
||||
DebugCrash,
|
||||
DebugHostname,
|
||||
DebugNetworkChanges,
|
||||
DebugPipeServer,
|
||||
DebugWintun,
|
||||
Elevated,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::client::cli::Cli;
|
||||
use anyhow::Result;
|
||||
use tokio::runtime::Runtime;
|
||||
use windows::Win32::System::Com::{CoInitializeEx, CoUninitialize, COINIT_MULTITHREADED};
|
||||
|
||||
// TODO: In tauri-plugin-deep-link, this is the identifier in tauri.conf.json
|
||||
const PIPE_NAME: &str = "dev.firezone.client";
|
||||
@@ -24,6 +25,28 @@ pub fn hostname() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Listen for network change events from Windows
|
||||
pub fn network_changes() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Must be called for each thread that will do COM stuff
|
||||
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }?;
|
||||
|
||||
{
|
||||
let _listener = crate::client::network_changes::Listener::new()?;
|
||||
println!("Listening for network events for 1 minute");
|
||||
std::thread::sleep(std::time::Duration::from_secs(60));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Required, per CoInitializeEx docs
|
||||
// Safety: Make sure all the COM objects are dropped before we call
|
||||
// CoUninitialize or the program might segfault.
|
||||
CoUninitialize();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn open_deep_link(path: &url::Url) -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
|
||||
90
rust/windows-client/src-tauri/src/client/network_changes.rs
Executable file
90
rust/windows-client/src-tauri/src/client/network_changes.rs
Executable file
@@ -0,0 +1,90 @@
|
||||
use windows::{
|
||||
core::{ComInterface, Result as WinResult, GUID},
|
||||
Win32::{
|
||||
Networking::NetworkListManager::{
|
||||
INetworkEvents, INetworkEvents_Impl, INetworkListManager, NetworkListManager,
|
||||
NLM_CONNECTIVITY, NLM_NETWORK_PROPERTY_CHANGE,
|
||||
},
|
||||
System::Com::{CoCreateInstance, IConnectionPoint, IConnectionPointContainer, CLSCTX_ALL},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct Listener {
|
||||
/// The cookie we get back from `Advise`. Can be None if the owner called `close`
|
||||
advise_cookie: Option<u32>,
|
||||
/// An IConnectionPoint is where we register our CallbackHandler
|
||||
cxn_point: IConnectionPoint,
|
||||
}
|
||||
|
||||
impl Drop for Listener {
|
||||
fn drop(&mut self) {
|
||||
self.close().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
/// Pre-req: CoInitializeEx must have been called on the calling thread to
|
||||
/// initialize COM.
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
// `windows-rs` automatically releases (de-refs) COM objects on Drop:
|
||||
// https://github.com/microsoft/windows-rs/issues/2123#issuecomment-1293194755
|
||||
// https://github.com/microsoft/windows-rs/blob/cefdabd15e4a7a7f71b7a2d8b12d5dc148c99adb/crates/samples/windows/wmi/src/main.rs#L22
|
||||
let network_list_manager: INetworkListManager =
|
||||
unsafe { CoCreateInstance(&NetworkListManager, None, CLSCTX_ALL) }?;
|
||||
let cpc: IConnectionPointContainer = network_list_manager.cast()?;
|
||||
let cxn_point = unsafe { cpc.FindConnectionPoint(&INetworkEvents::IID) }?;
|
||||
let listener: INetworkEvents = CallbackHandler {}.into();
|
||||
// TODO: Make sure to call Unadvise later to avoid leaks
|
||||
let advise_cookie = Some(unsafe { cxn_point.Advise(&listener) }?);
|
||||
|
||||
Ok(Self {
|
||||
advise_cookie,
|
||||
cxn_point,
|
||||
})
|
||||
}
|
||||
|
||||
/// This is the same as Drop, but you can catch errors from it
|
||||
/// Calling this multiple times is idempotent
|
||||
fn close(&mut self) -> anyhow::Result<()> {
|
||||
if let Some(advise_cookie) = self.advise_cookie.take() {
|
||||
// SAFETY: I don't see any memory safety issues.
|
||||
unsafe { self.cxn_point.Unadvise(advise_cookie) }?;
|
||||
tracing::debug!("Unadvised");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// https://kennykerr.ca/rust-getting-started/how-to-implement-com-interface.html
|
||||
#[windows_implement::implement(INetworkEvents)]
|
||||
struct CallbackHandler {}
|
||||
impl INetworkEvents_Impl for CallbackHandler {
|
||||
fn NetworkAdded(&self, networkid: &GUID) -> WinResult<()> {
|
||||
// TODO: Send these events over a Tokio mpsc channel if we need them in the GUI
|
||||
println!("NetworkAdded {networkid:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn NetworkDeleted(&self, networkid: &GUID) -> WinResult<()> {
|
||||
println!("NetworkDeleted {networkid:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn NetworkConnectivityChanged(
|
||||
&self,
|
||||
networkid: &GUID,
|
||||
newconnectivity: NLM_CONNECTIVITY,
|
||||
) -> WinResult<()> {
|
||||
println!("NetworkConnectivityChanged {networkid:?} {newconnectivity:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn NetworkPropertyChanged(
|
||||
&self,
|
||||
networkid: &GUID,
|
||||
flags: NLM_NETWORK_PROPERTY_CHANGE,
|
||||
) -> WinResult<()> {
|
||||
println!("NetworkPropertyChanged {networkid:?} {flags:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user