Skip to main content

wintun_bindings/
adapter.rs

1/// Representation of a winton adapter with safe idiomatic bindings to the functionality provided by
2/// the WintunAdapter* C functions.
3///
4/// The [`Adapter::create`] and [`Adapter::open`] functions serve as the entry point to using
5/// wintun functionality
6use crate::{
7    Wintun,
8    error::{Error, OutOfRangeData},
9    handle::{SafeEvent, UnsafeHandle},
10    session::Session,
11    util::{self},
12    wintun_raw,
13};
14use std::{
15    ffi::OsStr,
16    net::{IpAddr, Ipv4Addr},
17    os::windows::prelude::OsStrExt,
18    ptr,
19    sync::Arc,
20    sync::OnceLock,
21};
22use windows_sys::{
23    Win32::{
24        Foundation::ERROR_NOT_FOUND,
25        NetworkManagement::{IpHelper::ConvertLengthToIpv4Mask, Ndis::NET_LUID_LH},
26    },
27    core::GUID,
28};
29
30/// Wrapper around a <https://git.zx2c4.com/wintun/about/#wintun_adapter_handle>
31pub struct Adapter {
32    adapter: UnsafeHandle<wintun_raw::WINTUN_ADAPTER_HANDLE>,
33    pub(crate) wintun: Wintun,
34    requested_guid: Option<u128>,
35    guid: OnceLock<u128>,
36    index: OnceLock<u32>,
37    luid: NET_LUID_LH,
38}
39
40impl Adapter {
41    /// Returns the `Friendly Name` of this adapter,
42    /// which is the human readable name shown in Windows
43    pub fn get_name(&self) -> Result<String, Error> {
44        Ok(crate::ffi::luid_to_alias(&self.luid)?)
45    }
46
47    /// Sets the `Friendly Name` of this adapter,
48    /// which is the human readable name shown in Windows
49    ///
50    /// Note: This is different from `Adapter Name`, which is a GUID.
51    pub fn set_name(&self, name: &str) -> Result<(), Error> {
52        // use command `netsh interface set interface name="oldname" newname="mynewname"`
53
54        let args = &[
55            "interface",
56            "set",
57            "interface",
58            &format!("name=\"{}\"", self.get_name()?),
59            &format!("newname=\"{}\"", name),
60        ];
61        util::run_command("netsh", args)?;
62
63        Ok(())
64    }
65
66    pub fn get_guid(&self) -> u128 {
67        if let Some(guid) = self.guid.get() {
68            return *guid;
69        }
70
71        let real_guid = match resolve_with_retry(|| crate::ffi::luid_to_guid(&self.luid)) {
72            Ok(g) => util::win_guid_to_u128(&g),
73            Err(_) => return self.requested_guid.unwrap_or(0),
74        };
75
76        if let Some(req) = self.requested_guid
77            && req != real_guid
78            && let (Ok(real_s), Ok(req_s), Ok((major, minor, build))) = (
79                util::guid_to_win_style_string(&GUID::from_u128(real_guid)),
80                util::guid_to_win_style_string(&GUID::from_u128(req)),
81                util::get_windows_version(),
82            )
83        {
84            log::warn!(
85                "Windows {major}.{minor}.{build}: an internal bug causes the GUID mismatch: Expected {req_s}, got {real_s}"
86            );
87        }
88
89        match self.guid.set(real_guid) {
90            Ok(()) => real_guid,
91            Err(_) => *self
92                .guid
93                .get()
94                .expect("guid should be initialized by this thread or another"),
95        }
96    }
97
98    /// Creates a new wintun adapter inside the name `name` with tunnel type `tunnel_type`
99    ///
100    /// Optionally a GUID can be specified that will become the GUID of this adapter once created.
101    pub fn create(wintun: &Wintun, name: &str, tunnel_type: &str, guid: Option<u128>) -> Result<Arc<Adapter>, Error> {
102        let name_utf16: Vec<_> = name.encode_utf16().chain(std::iter::once(0)).collect();
103        let tunnel_type_utf16: Vec<u16> = tunnel_type.encode_utf16().chain(std::iter::once(0)).collect();
104
105        let requested_guid = guid.unwrap_or_else(|| {
106            let mut guid: GUID = unsafe { std::mem::zeroed() };
107            unsafe { windows_sys::Win32::System::Rpc::UuidCreate(&mut guid as *mut GUID) };
108            util::win_guid_to_u128(&guid)
109        });
110
111        crate::log::set_default_logger_if_unset(wintun);
112
113        let guid_s: GUID = GUID::from_u128(requested_guid);
114        let result = unsafe { wintun.WintunCreateAdapter(name_utf16.as_ptr(), tunnel_type_utf16.as_ptr(), &guid_s) };
115
116        if result.is_null() {
117            return crate::log::extract_wintun_log_error("WintunCreateAdapter failed")?;
118        }
119
120        let mut luid: NET_LUID_LH = unsafe { std::mem::zeroed() };
121        unsafe { wintun.WintunGetAdapterLUID(result, &mut luid) };
122
123        Ok(Arc::new(Adapter {
124            adapter: UnsafeHandle(result),
125            wintun: wintun.clone(),
126            luid,
127            requested_guid: Some(requested_guid),
128            guid: OnceLock::new(),
129            index: OnceLock::new(),
130        }))
131    }
132
133    /// Attempts to open an existing wintun interface name `name`.
134    pub fn open(wintun: &Wintun, name: &str) -> Result<Arc<Adapter>, Error> {
135        let name_utf16: Vec<u16> = OsStr::new(name).encode_wide().chain(std::iter::once(0)).collect();
136
137        crate::log::set_default_logger_if_unset(wintun);
138
139        let result = unsafe { wintun.WintunOpenAdapter(name_utf16.as_ptr()) };
140
141        if result.is_null() {
142            return crate::log::extract_wintun_log_error("WintunOpenAdapter failed")?;
143        }
144
145        let mut luid: NET_LUID_LH = unsafe { std::mem::zeroed() };
146        unsafe { wintun.WintunGetAdapterLUID(result, &mut luid) };
147
148        Ok(Arc::new(Adapter {
149            adapter: UnsafeHandle(result),
150            wintun: wintun.clone(),
151            luid,
152            requested_guid: None,
153            guid: OnceLock::new(),
154            index: OnceLock::new(),
155        }))
156    }
157
158    /// Delete an adapter, consuming it in the process
159    pub fn delete(self) -> Result<(), Error> {
160        //Dropping an adapter closes it
161        drop(self);
162        // Return a result here so that if later the API changes to be fallible, we can support it
163        // without making a breaking change
164        Ok(())
165    }
166
167    fn validate_capacity(capacity: u32) -> Result<(), Error> {
168        let range = crate::MIN_RING_CAPACITY..=crate::MAX_RING_CAPACITY;
169        if !range.contains(&capacity) {
170            return Err(Error::CapacityOutOfRange(OutOfRangeData { range, value: capacity }));
171        }
172        if !capacity.is_power_of_two() {
173            return Err(Error::CapacityNotPowerOfTwo(capacity));
174        }
175        Ok(())
176    }
177
178    /// Initiates a new wintun session on the given adapter.
179    ///
180    /// Capacity is the size in bytes of the ring buffer used internally by the driver. Must be
181    /// a power of two between [`crate::MIN_RING_CAPACITY`] and [`crate::MAX_RING_CAPACITY`] inclusive.
182    pub fn start_session(self: &Arc<Self>, capacity: u32) -> Result<Arc<Session>, Error> {
183        Self::validate_capacity(capacity)?;
184
185        let result = unsafe { self.wintun.WintunStartSession(self.adapter.0, capacity) };
186
187        if result.is_null() {
188            return crate::log::extract_wintun_log_error("WintunStartSession failed")?;
189        }
190        // Manual reset, because we use this event once and it must fire on all threads
191        let shutdown_event = SafeEvent::new(true, false)?;
192        Ok(Arc::new(Session {
193            inner: UnsafeHandle(result),
194            read_event: OnceLock::new(),
195            shutdown_event: Arc::new(shutdown_event),
196            adapter: self.clone(),
197        }))
198    }
199
200    /// Returns the Win32 LUID for this adapter
201    pub fn get_luid(&self) -> NET_LUID_LH {
202        self.luid
203    }
204
205    /// Set `MTU` of this adapter
206    pub fn set_mtu(&self, mtu: usize) -> Result<(), Error> {
207        util::set_adapter_mtu(&self.luid, mtu, false)?;
208        // IPv6 MTU is best-effort: a host with IPv6 disabled has no IPv6
209        // interface row. Skip that case rather than fail, since the adapter
210        // may be driven IPv4-only.
211        if let Err(e) = util::set_adapter_mtu(&self.luid, mtu, true) {
212            if e.raw_os_error() != Some(ERROR_NOT_FOUND as i32) {
213                return Err(e.into());
214            }
215            log::warn!("skipping IPv6 MTU, no IPv6 interface row");
216        }
217        Ok(())
218    }
219
220    /// Returns `MTU` of this adapter
221    pub fn get_mtu(&self) -> Result<usize, Error> {
222        // FIXME: Here we get the IPv4 MTU only, but for some users it may not be expected.
223        Ok(util::get_adapter_mtu(&self.luid, false)? as _)
224    }
225
226    /// Returns the Win32 interface index of this adapter. Useful for specifying the interface
227    /// when executing `netsh interface ip` commands
228    pub fn get_adapter_index(&self) -> Result<u32, Error> {
229        if let Some(idx) = self.index.get() {
230            return Ok(*idx);
231        }
232        let idx = resolve_with_retry(|| crate::ffi::luid_to_index(&self.luid))?;
233        Ok(*self.index.get_or_init(|| idx))
234    }
235
236    /// Sets the IP address for this adapter, using command `netsh`.
237    pub fn set_address(&self, address: Ipv4Addr) -> Result<(), Error> {
238        let binding = self.get_addresses()?;
239        let old_address = binding.iter().find(|addr| matches!(addr, IpAddr::V4(_)));
240        let mask = match old_address {
241            Some(IpAddr::V4(addr)) => self.get_netmask_of_address(&(*addr).into())?,
242            _ => "255.255.255.0".parse()?,
243        };
244        let gateway = self
245            .get_gateways()?
246            .iter()
247            .find(|addr| matches!(addr, IpAddr::V4(_)))
248            .cloned();
249        self.set_network_addresses_tuple(address.into(), mask, gateway)?;
250        Ok(())
251    }
252
253    /// Sets the gateway for this adapter, using command `netsh`.
254    pub fn set_gateway(&self, gateway: Option<Ipv4Addr>) -> Result<(), Error> {
255        let binding = self.get_addresses()?;
256        let address = binding.iter().find(|addr| matches!(addr, IpAddr::V4(_)));
257        let address = match address {
258            Some(IpAddr::V4(addr)) => addr,
259            _ => return Err("Unable to find IPv4 address".into()),
260        };
261        let mask = self.get_netmask_of_address(&(*address).into())?;
262        let gateway = gateway.map(|addr| addr.into());
263        self.set_network_addresses_tuple((*address).into(), mask, gateway)?;
264        Ok(())
265    }
266
267    /// Sets the subnet mask for this adapter, using command `netsh`.
268    pub fn set_netmask(&self, mask: Ipv4Addr) -> Result<(), Error> {
269        let binding = self.get_addresses()?;
270        let address = binding.iter().find(|addr| matches!(addr, IpAddr::V4(_)));
271        let address = match address {
272            Some(IpAddr::V4(addr)) => addr,
273            _ => return Err("Unable to find IPv4 address".into()),
274        };
275        let gateway = self
276            .get_gateways()?
277            .iter()
278            .find(|addr| matches!(addr, IpAddr::V4(_)))
279            .cloned();
280        self.set_network_addresses_tuple((*address).into(), mask.into(), gateway)?;
281        Ok(())
282    }
283
284    /// Sets the DNS servers for this adapter
285    pub fn set_dns_servers(&self, dns_servers: &[IpAddr]) -> Result<(), Error> {
286        let interface = GUID::from_u128(self.get_guid());
287        if let Err(e) = util::set_interface_dns_servers(interface, dns_servers) {
288            log::debug!("Failed to set DNS servers in first attempt: \"{}\", try another...", e);
289            if let Err(e) = crate::dns_via_reg::set_dns_via_registry(&interface, dns_servers) {
290                log::debug!("Failed to set DNS servers via registry: \"{}\", try another...", e);
291                util::set_interface_dns_servers_via_cmd(&self.get_name()?, dns_servers)?;
292            }
293        }
294        Ok(())
295    }
296
297    /// Sets the network addresses of this adapter, including network address, subnet mask, and gateway
298    pub fn set_network_addresses_tuple(
299        &self,
300        address: IpAddr,
301        mask: IpAddr,
302        gateway: Option<IpAddr>,
303    ) -> Result<(), Error> {
304        let name = self.get_name()?;
305        // command line: `netsh interface ipv4 set address name="YOUR_INTERFACE_NAME" source=static address=IP_ADDRESS mask=SUBNET_MASK gateway=GATEWAY`
306        // or shorter command: `netsh interface ipv4 set address name="YOUR_INTERFACE_NAME" static IP_ADDRESS SUBNET_MASK GATEWAY`
307        // for example: `netsh interface ipv4 set address name="Wi-Fi" static 192.168.3.8 255.255.255.0 192.168.3.1`
308        let mut args: Vec<String> = vec![
309            "interface".into(),
310            if address.is_ipv4() {
311                "ipv4".into()
312            } else {
313                "ipv6".into()
314            },
315            "set".into(),
316            "address".into(),
317            format!("name=\"{}\"", name),
318            "source=static".into(),
319            format!("address={}", address),
320            format!("mask={}", mask),
321        ];
322        if let Some(gateway) = gateway {
323            args.push(format!("gateway={}", gateway));
324        }
325        util::run_command("netsh", &args.iter().map(|s| s.as_str()).collect::<Vec<&str>>())?;
326        Ok(())
327    }
328
329    /// Returns the IP addresses of this adapter, including IPv4 and IPv6 addresses
330    pub fn get_addresses(&self) -> Result<Vec<IpAddr>, Error> {
331        let name = util::guid_to_win_style_string(&GUID::from_u128(self.get_guid()))?;
332
333        let mut adapter_addresses = vec![];
334
335        util::get_adapters_addresses(|adapter| {
336            let name_iter = match unsafe { util::win_pstr_to_string(adapter.AdapterName) } {
337                Ok(name) => name,
338                Err(err) => {
339                    log::error!("Failed to parse adapter name: {}", err);
340                    return false;
341                }
342            };
343            if name_iter == name {
344                let mut current_address = adapter.FirstUnicastAddress;
345                while !current_address.is_null() {
346                    let address = unsafe { (*current_address).Address };
347                    match util::retrieve_ipaddr_from_socket_address(&address) {
348                        Ok(addr) => adapter_addresses.push(addr),
349                        Err(err) => {
350                            log::error!("Failed to parse address: {}", err);
351                        }
352                    }
353                    unsafe { current_address = (*current_address).Next };
354                }
355            }
356            true
357        })?;
358
359        Ok(adapter_addresses)
360    }
361
362    /// Returns the gateway addresses of this adapter, including IPv4 and IPv6 addresses
363    pub fn get_gateways(&self) -> Result<Vec<IpAddr>, Error> {
364        let name = util::guid_to_win_style_string(&GUID::from_u128(self.get_guid()))?;
365        let mut gateways = vec![];
366        util::get_adapters_addresses(|adapter| {
367            let name_iter = match unsafe { util::win_pstr_to_string(adapter.AdapterName) } {
368                Ok(name) => name,
369                Err(err) => {
370                    log::error!("Failed to parse adapter name: {}", err);
371                    return false;
372                }
373            };
374            if name_iter == name {
375                let mut current_gateway = adapter.FirstGatewayAddress;
376                while !current_gateway.is_null() {
377                    let gateway = unsafe { (*current_gateway).Address };
378                    match util::retrieve_ipaddr_from_socket_address(&gateway) {
379                        Ok(addr) => gateways.push(addr),
380                        Err(err) => {
381                            log::error!("Failed to parse gateway: {}", err);
382                        }
383                    }
384                    unsafe { current_gateway = (*current_gateway).Next };
385                }
386            }
387            true
388        })?;
389        Ok(gateways)
390    }
391
392    /// Returns the subnet mask of the given address
393    pub fn get_netmask_of_address(&self, target_address: &IpAddr) -> Result<IpAddr, Error> {
394        let name = util::guid_to_win_style_string(&GUID::from_u128(self.get_guid()))?;
395        let mut subnet_mask = None;
396        util::get_adapters_addresses(|adapter| {
397            let name_iter = match unsafe { util::win_pstr_to_string(adapter.AdapterName) } {
398                Ok(name) => name,
399                Err(err) => {
400                    log::warn!("Failed to parse adapter name: {}", err);
401                    return false;
402                }
403            };
404            if name_iter == name {
405                let mut current_address = adapter.FirstUnicastAddress;
406                while !current_address.is_null() {
407                    let address = unsafe { (*current_address).Address };
408                    let address = match util::retrieve_ipaddr_from_socket_address(&address) {
409                        Ok(addr) => addr,
410                        Err(err) => {
411                            log::warn!("Failed to parse address: {}", err);
412                            return false;
413                        }
414                    };
415                    if address == *target_address {
416                        let masklength = unsafe { (*current_address).OnLinkPrefixLength };
417                        match address {
418                            IpAddr::V4(_) => {
419                                let mut mask = 0_u32;
420                                match unsafe { ConvertLengthToIpv4Mask(masklength as u32, &mut mask as *mut u32) } {
421                                    0 => {}
422                                    err => {
423                                        log::warn!("Failed to convert length to mask: {}", err);
424                                        return false;
425                                    }
426                                }
427                                subnet_mask = Some(IpAddr::V4(Ipv4Addr::from(mask.to_le_bytes())));
428                            }
429                            IpAddr::V6(_) => match util::ipv6_netmask_for_prefix(masklength) {
430                                Ok(v) => subnet_mask = Some(IpAddr::V6(v)),
431                                Err(err) => {
432                                    log::warn!("Failed to convert length to mask: {}", err);
433                                    return false;
434                                }
435                            },
436                        }
437                        break;
438                    }
439                    unsafe { current_address = (*current_address).Next };
440                }
441            }
442            true
443        })?;
444
445        Ok(subnet_mask.ok_or("Unable to find matching address")?)
446    }
447}
448
449impl Drop for Adapter {
450    fn drop(&mut self) {
451        let _name = self.get_name();
452        //Close adapter on drop
453        //This is why we need an Arc of wintun
454        unsafe { self.wintun.WintunCloseAdapter(self.adapter.0) };
455        self.adapter = UnsafeHandle(ptr::null_mut());
456        #[cfg(feature = "winreg")]
457        if let Ok(name) = _name {
458            // Delete registry related to network card
459            _ = delete_adapter_info_from_reg(&name);
460        }
461    }
462}
463
464/// This function is used to avoid the adapter name and guid being recorded in the registry
465#[cfg(feature = "winreg")]
466pub(crate) fn delete_adapter_info_from_reg(dev_name: &str) -> std::io::Result<()> {
467    use windows_sys::Win32::Foundation::ERROR_NO_MORE_ITEMS;
468    use windows_sys::Win32::System::Registry::{
469        HKEY, HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, KEY_READ, REG_SZ, RegCloseKey, RegDeleteTreeW, RegEnumKeyExW,
470        RegOpenKeyExW, RegQueryValueExW,
471    };
472
473    fn to_wide_null(value: &str) -> Vec<u16> {
474        value.encode_utf16().chain(std::iter::once(0)).collect()
475    }
476
477    fn open_registry_key(parent: HKEY, subkey: &str, access: u32) -> std::io::Result<HKEY> {
478        let subkey_wide = to_wide_null(subkey);
479        let mut handle: HKEY = std::ptr::null_mut();
480        let status = unsafe { RegOpenKeyExW(parent, subkey_wide.as_ptr(), 0, access, &mut handle) };
481        if status != 0 {
482            return Err(std::io::Error::from_raw_os_error(status as i32));
483        }
484        Ok(handle)
485    }
486
487    fn enum_registry_subkeys(hkey: HKEY) -> std::io::Result<Vec<String>> {
488        let mut subkeys = Vec::new();
489        let mut index = 0;
490        loop {
491            let mut name = vec![0u16; 260];
492            let mut name_len = name.len() as u32;
493            let status = unsafe {
494                RegEnumKeyExW(
495                    hkey,
496                    index,
497                    name.as_mut_ptr(),
498                    &mut name_len,
499                    std::ptr::null_mut(),
500                    std::ptr::null_mut(),
501                    std::ptr::null_mut(),
502                    std::ptr::null_mut(),
503                )
504            };
505            if status == ERROR_NO_MORE_ITEMS {
506                break;
507            }
508            if status != 0 {
509                return Err(std::io::Error::from_raw_os_error(status as i32));
510            }
511            subkeys.push(String::from_utf16_lossy(&name[..name_len as usize]));
512            index += 1;
513        }
514        Ok(subkeys)
515    }
516
517    fn query_registry_string_value(hkey: HKEY, value_name: &str) -> std::io::Result<String> {
518        let value_name_wide = to_wide_null(value_name);
519        let mut value_type = 0_u32;
520        let mut data_len = 0_u32;
521        let status = unsafe {
522            RegQueryValueExW(
523                hkey,
524                value_name_wide.as_ptr(),
525                std::ptr::null_mut(),
526                &mut value_type,
527                std::ptr::null_mut(),
528                &mut data_len,
529            )
530        };
531        if status != 0 {
532            return Err(std::io::Error::from_raw_os_error(status as i32));
533        }
534        if value_type != REG_SZ {
535            return Err(std::io::Error::new(
536                std::io::ErrorKind::InvalidData,
537                "Registry value is not a string",
538            ));
539        }
540        let mut buffer = vec![0u16; (data_len as usize / 2).max(1)];
541        let status = unsafe {
542            RegQueryValueExW(
543                hkey,
544                value_name_wide.as_ptr(),
545                std::ptr::null_mut(),
546                std::ptr::null_mut(),
547                buffer.as_mut_ptr().cast(),
548                &mut data_len,
549            )
550        };
551        if status != 0 {
552            return Err(std::io::Error::from_raw_os_error(status as i32));
553        }
554        if let Some(&0) = buffer.last() {
555            buffer.pop();
556        }
557        String::from_utf16(&buffer).map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
558    }
559
560    fn read_subkey_string_value(parent_key: HKEY, subkey_name: &str, value_name: &str) -> std::io::Result<String> {
561        let subkey_handle = open_registry_key(parent_key, subkey_name, KEY_READ)?;
562        let value = query_registry_string_value(subkey_handle, value_name);
563        unsafe { RegCloseKey(subkey_handle) };
564        value
565    }
566
567    fn delete_registry_tree(parent_key: HKEY, subkey_name: &str) -> std::io::Result<()> {
568        let subkey_wide = to_wide_null(subkey_name);
569        let status = unsafe { RegDeleteTreeW(parent_key, subkey_wide.as_ptr()) };
570        if status != 0 {
571            return Err(std::io::Error::from_raw_os_error(status as i32));
572        }
573        Ok(())
574    }
575
576    let profiles_key = open_registry_key(
577        HKEY_LOCAL_MACHINE,
578        "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
579        KEY_ALL_ACCESS,
580    )?;
581    for sub_key_name in enum_registry_subkeys(profiles_key)? {
582        match read_subkey_string_value(profiles_key, &sub_key_name, "ProfileName") {
583            Ok(profile_name) => {
584                if dev_name == profile_name {
585                    match delete_registry_tree(profiles_key, &sub_key_name) {
586                        Ok(_) => log::info!("Successfully deleted Profiles sub_key: {}", sub_key_name),
587                        Err(e) => log::warn!("Failed to delete Profiles sub_key {}: {}", sub_key_name, e),
588                    }
589                }
590            }
591            Err(e) => log::warn!("Failed to read ProfileName for sub_key {}: {}", sub_key_name, e),
592        }
593    }
594    unsafe { RegCloseKey(profiles_key) };
595
596    let unmanaged_key = open_registry_key(
597        HKEY_LOCAL_MACHINE,
598        "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged",
599        KEY_ALL_ACCESS,
600    )?;
601    for sub_key_name in enum_registry_subkeys(unmanaged_key)? {
602        match read_subkey_string_value(unmanaged_key, &sub_key_name, "Description") {
603            Ok(description) => {
604                if dev_name == description {
605                    match delete_registry_tree(unmanaged_key, &sub_key_name) {
606                        Ok(_) => log::info!("Successfully deleted Unmanaged sub_key: {}", sub_key_name),
607                        Err(e) => log::warn!("Failed to delete Unmanaged sub_key {}: {}", sub_key_name, e),
608                    }
609                }
610            }
611            Err(e) => log::warn!("Failed to read Description for sub_key {}: {}", sub_key_name, e),
612        }
613    }
614    unsafe { RegCloseKey(unmanaged_key) };
615
616    Ok(())
617}
618
619fn resolve_with_retry<T>(mut f: impl FnMut() -> std::io::Result<T>) -> std::io::Result<T> {
620    const NSI_RETRY_ATTEMPTS: usize = 3;
621    const NSI_RETRY_DELAY_MS: u64 = 25;
622
623    for attempt in 1..=NSI_RETRY_ATTEMPTS {
624        match f() {
625            Ok(v) => return Ok(v),
626            Err(e) if e.raw_os_error() == Some(ERROR_NOT_FOUND as i32) => {
627                if attempt == NSI_RETRY_ATTEMPTS {
628                    return Err(e);
629                }
630                log::warn!("NSI race, retry {attempt}/{NSI_RETRY_ATTEMPTS}");
631                std::thread::sleep(std::time::Duration::from_millis(NSI_RETRY_DELAY_MS));
632            }
633            Err(e) => return Err(e),
634        }
635    }
636    unreachable!();
637}