fix(windows): handle missing registry values during TUN name allocation (#2777)

* fix(windows): reuse TUN names with incomplete registry records

A leftover Connection key must not force a new adapter identity after
EasyTier has reserved the original name. Skip that identity before
reading registry values and let rust-tun reopen or recreate the adapter.

Keep conflict checks for other GUIDs: Wintun can otherwise rename an
unrelated adapter that already uses the candidate name. Share the GUID
calculation with VirtualNic so allocation and creation stay consistent.

Verified on Windows 11 with concurrent processes, normal and forced
exit, missing Name/PnpInstanceID values, and a different-GUID collision.
The original reservation tests, Linux workspace Clippy, formatting,
and Windows core checks pass.

Co-authored-by: Daniel <2116528714@qq.com>

---------

Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
This commit is contained in:
Tangweirui2021
2026-10-30 18:18:31 +08:00
committed by GitHub
co-authored by Daniel KKRainbow
parent c66076b8f3
commit 18f8e41fa1
3 changed files with 32 additions and 10 deletions
+1 -1
View File
@@ -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<S: AsRawSocket>(socket: &S) -> io::Result<()> {
let handle = SOCKET(socket.as_raw_socket() as usize);
+30 -5
View File
@@ -52,24 +52,49 @@ fn reserve_name(name: &str) -> anyhow::Result<Option<OwnedHandle>> {
Ok((!existed).then_some(handle))
}
fn interface_exists(name: &str) -> anyhow::Result<bool> {
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<bool> {
// 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));
}
}
+1 -4
View File
@@ -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,