diff --git a/easytier/src/arch/windows.rs b/easytier/src/arch/windows.rs index b638fcdf..2fb4e1db 100644 --- a/easytier/src/arch/windows.rs +++ b/easytier/src/arch/windows.rs @@ -24,7 +24,7 @@ use windows::{ }; mod tun_name; -pub(crate) use tun_name::reserve_tun_name; +pub(crate) use tun_name::{reserve_tun_name, tun_device_guid}; pub fn disable_connection_reset(socket: &S) -> io::Result<()> { let handle = SOCKET(socket.as_raw_socket() as usize); diff --git a/easytier/src/arch/windows/tun_name.rs b/easytier/src/arch/windows/tun_name.rs index 9ba4d40e..29d2cb26 100644 --- a/easytier/src/arch/windows/tun_name.rs +++ b/easytier/src/arch/windows/tun_name.rs @@ -52,24 +52,49 @@ fn reserve_name(name: &str) -> anyhow::Result> { Ok((!existed).then_some(handle)) } -fn interface_exists(name: &str) -> anyhow::Result { +pub(crate) fn tun_device_guid(name: &str) -> uuid::Uuid { + let identity = format!("https://easytier.rs/wintun/{}", name.to_lowercase()); + uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, identity.as_bytes()) +} + +fn interface_name_conflicts(name: &str) -> anyhow::Result { // IP interface tables can retain removed adapters after process exit. // Check the device tree: disabled devices still occupy their names, but // nonpresent devices left by a terminated process do not. let network = RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey( r"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}", )?; + let expected_guid = format!("{{{}}}", tun_device_guid(name)); for guid in network.enum_keys() { - let connection = match network.open_subkey(format!(r"{}\Connection", guid?)) { + let guid = guid?; + // The caller holds the name reservation, so another EasyTier instance + // cannot use this identity. Let rust-tun reopen or recreate it even if + // Windows retained an incomplete Connection key. + if guid.eq_ignore_ascii_case(&expected_guid) { + continue; + } + let connection = match network.open_subkey(format!(r"{}\Connection", guid)) { Ok(key) => key, Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue, Err(err) => return Err(err.into()), }; - let existing_name: String = connection.get_value("Name")?; + let existing_name: String = match connection.get_value("Name") { + Ok(name) => name, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }; if !existing_name.eq_ignore_ascii_case(name) { continue; } - let instance_id: String = connection.get_value("PnpInstanceID")?; + let instance_id: String = match connection.get_value("PnpInstanceID") { + Ok(id) => id, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // The name exists, but device presence cannot be verified. + // Keep it occupied rather than risk a name collision. + return Ok(true); + } + Err(err) => return Err(err.into()), + }; let mut dev_inst = 0; let result = unsafe { CM_Locate_DevNodeW( @@ -99,7 +124,7 @@ pub(crate) fn reserve_tun_name(requested: &str) -> anyhow::Result<(String, Owned let Some(handle) = reserve_name(&name)? else { continue; }; - if !interface_exists(&name)? { + if !interface_name_conflicts(&name)? { return Ok((name, handle)); } } diff --git a/easytier/src/instance/virtual_nic.rs b/easytier/src/instance/virtual_nic.rs index f37266f7..1fee2ac4 100644 --- a/easytier/src/instance/virtual_nic.rs +++ b/easytier/src/instance/virtual_nic.rs @@ -581,10 +581,7 @@ impl VirtualNic { config.platform_config(|config| { // Wintun uses the GUID for Windows network profile identity. // Include the name so explicitly shared NICs use the same GUID. - let identity = format!("https://easytier.rs/wintun/{}", dev_name.to_lowercase()); - config.device_guid( - uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, identity.as_bytes()).as_u128(), - ); + config.device_guid(crate::arch::windows::tun_device_guid(&dev_name).as_u128()); config.skip_config(true); config.ring_cap(Some(std::cmp::min( config.min_ring_cap() * 32,