use std::{ fmt, io, net::{IpAddr, Ipv4Addr, Ipv6Addr}, ptr::{null, null_mut}, slice, }; use anyhow::{Context, Result, bail}; use windows_sys::Win32::{ Foundation::{ERROR_OBJECT_ALREADY_EXISTS, ERROR_SUCCESS}, NetworkManagement::{ IpHelper::{ ConvertInterfaceGuidToLuid, ConvertInterfaceLuidToIndex, CreateIpForwardEntry2, DeleteIpForwardEntry2, FreeMibTable, GetBestRoute2, GetIpInterfaceEntry, GetUnicastIpAddressTable, IP_ADDRESS_PREFIX, InitializeIpForwardEntry, InitializeIpInterfaceEntry, MIB_IPFORWARD_ROW2, MIB_IPINTERFACE_ROW, MIB_UNICASTIPADDRESS_ROW, MIB_UNICASTIPADDRESS_TABLE, SetIpInterfaceEntry, }, Ndis::NET_LUID_LH, }, Networking::WinSock::{ AF_INET, AF_INET6, AF_UNSPEC, IN_ADDR, IN_ADDR_0, IN6_ADDR, IN6_ADDR_0, RouteProtocolNetMgmt, SOCKADDR_IN, SOCKADDR_IN6, SOCKADDR_IN6_0, SOCKADDR_INET, }, }; use windows_sys::core::GUID; use crate::{ InterfaceMetricSnapshot, InterfaceMtuSnapshot, InterfaceUnicastAddress, IpInterfaceFamily, }; use crate::{NetworkInterfaceIdentity, RouteSnapshot}; pub fn interface_identity_from_guid(interface_guid: &str) -> Result { let guid = parse_interface_guid(interface_guid)?; let mut luid = NET_LUID_LH::default(); let status = unsafe { // SAFETY: guid and luid point to valid initialized storage. ConvertInterfaceGuidToLuid(&guid, &mut luid) }; windows_status(status) .with_context(|| format!("failed to resolve interface LUID for {interface_guid}"))?; let mut index = 0_u32; let status = unsafe { // SAFETY: luid was returned by Windows and index points to writable storage. ConvertInterfaceLuidToIndex(&luid, &mut index) }; windows_status(status).with_context(|| { format!( "failed to resolve interface index for interface LUID {}", luid_value(luid) ) })?; Ok(NetworkInterfaceIdentity::new(index, luid_value(luid))) } pub fn interface_metric( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, ) -> Result { let row = get_interface_row(identity, family)?; Ok(metric_snapshot(identity, family, row)) } pub fn interface_mtu( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, ) -> Result { let row = get_interface_row(identity, family)?; Ok(mtu_snapshot(identity, family, row)) } pub fn interface_unicast_addresses( identity: NetworkInterfaceIdentity, ) -> Result> { let mut table = null_mut(); let status = unsafe { // SAFETY: table points to writable storage for the returned heap table pointer. GetUnicastIpAddressTable(AF_UNSPEC, &mut table) }; windows_status(status).with_context(|| { format!( "failed to read unicast IP address table for interface index {} LUID {}", identity.index(), identity.luid() ) })?; if table.is_null() { bail!("Windows returned a null unicast IP address table"); } let table = MibUnicastAddressTable(table); let rows = table.rows(); Ok(rows .iter() .filter_map(|row| unicast_address_snapshot(identity, row)) .collect()) } pub fn set_scoped_interface_metric( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, metric: u32, ) -> Result { let previous = interface_metric(identity, family)?; let mut row = get_interface_row(identity, family)?; row.UseAutomaticMetric = false; row.Metric = metric; set_interface_row(&mut row) .with_context(|| format!("failed to set {family:?} interface metric to {metric}"))?; Ok(ScopedInterfaceMetric { previous, active: true, }) } pub fn set_scoped_default_routes_disabled( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, disabled: bool, ) -> Result { let previous = interface_metric(identity, family)?; let mut row = get_interface_row(identity, family)?; row.DisableDefaultRoutes = disabled; set_interface_row(&mut row).with_context(|| { format!("failed to set {family:?} default-route disabled state to {disabled}") })?; Ok(ScopedDefaultRoutes { previous, active: true, }) } pub fn set_scoped_interface_mtu( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, mtu: u32, ) -> Result { if mtu == 0 { bail!("interface MTU must be nonzero"); } let previous = interface_mtu(identity, family)?; let mut row = get_interface_row(identity, family)?; row.NlMtu = mtu; set_interface_row(&mut row) .with_context(|| format!("failed to set {family:?} interface MTU to {mtu}"))?; Ok(ScopedInterfaceMtu { previous, active: true, }) } pub struct ScopedInterfaceMetric { previous: InterfaceMetricSnapshot, active: bool, } impl ScopedInterfaceMetric { #[must_use] pub const fn previous(&self) -> InterfaceMetricSnapshot { self.previous } } impl fmt::Debug for ScopedInterfaceMetric { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ScopedInterfaceMetric") .field("previous", &self.previous) .field("active", &self.active) .finish() } } impl Drop for ScopedInterfaceMetric { fn drop(&mut self) { if !self.active { return; } if let Err(error) = restore_interface_metric(self.previous) { eprintln!( "failed to restore {:?} interface metric for index {} LUID {}: {error:#}", self.previous.family(), self.previous.identity().index(), self.previous.identity().luid(), ); } } } pub struct ScopedDefaultRoutes { previous: InterfaceMetricSnapshot, active: bool, } impl ScopedDefaultRoutes { #[must_use] pub const fn previous(&self) -> InterfaceMetricSnapshot { self.previous } } impl fmt::Debug for ScopedDefaultRoutes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ScopedDefaultRoutes") .field("previous", &self.previous) .field("active", &self.active) .finish() } } impl Drop for ScopedDefaultRoutes { fn drop(&mut self) { if !self.active { return; } if let Err(error) = restore_default_routes(self.previous) { eprintln!( "failed to restore {:?} default-route state for index {} LUID {}: {error:#}", self.previous.family(), self.previous.identity().index(), self.previous.identity().luid(), ); } } } pub struct ScopedInterfaceMtu { previous: InterfaceMtuSnapshot, active: bool, } impl ScopedInterfaceMtu { #[must_use] pub const fn previous(&self) -> InterfaceMtuSnapshot { self.previous } } impl fmt::Debug for ScopedInterfaceMtu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ScopedInterfaceMtu") .field("previous", &self.previous) .field("active", &self.active) .finish() } } impl Drop for ScopedInterfaceMtu { fn drop(&mut self) { if !self.active { return; } if let Err(error) = restore_interface_mtu(self.previous) { eprintln!( "failed to restore {:?} interface MTU for index {} LUID {}: {error:#}", self.previous.family(), self.previous.identity().index(), self.previous.identity().luid(), ); } } } pub fn best_route_to(destination: IpAddr) -> Result { let destination_sockaddr = sockaddr_from_ip(destination); let mut route = MIB_IPFORWARD_ROW2::default(); let mut source = SOCKADDR_INET::default(); let status = unsafe { // SAFETY: destination_sockaddr, route, and source point to valid initialized storage. // Null interface/source inputs ask Windows to choose the best current route. GetBestRoute2( null(), 0, null(), &destination_sockaddr, 0, &mut route, &mut source, ) }; windows_status(status).with_context(|| format!("failed to find route to {destination}"))?; let source = ip_from_sockaddr(&source).context("best route returned no source address")?; let route_prefix = ip_from_sockaddr(&route.DestinationPrefix.Prefix) .context("best route returned no route prefix")?; let next_hop = ip_from_sockaddr(&route.NextHop).filter(|next_hop| !next_hop.is_unspecified()); let interface_luid = luid_value(route.InterfaceLuid); Ok(RouteSnapshot::new( destination, source, next_hop, route_prefix, route.DestinationPrefix.PrefixLength, route.InterfaceIndex, interface_luid, route.Metric, )) } fn get_interface_row( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, ) -> Result { let mut row = interface_row_key(identity, family); let status = unsafe { // SAFETY: row is initialized with the family and interface identity Windows needs to // retrieve the IP interface entry. GetIpInterfaceEntry(&mut row) }; windows_status(status).with_context(|| { format!( "failed to read {family:?} interface row for index {} LUID {}", identity.index(), identity.luid() ) })?; Ok(row) } fn set_interface_row(row: &mut MIB_IPINTERFACE_ROW) -> Result<()> { let status = unsafe { // SAFETY: row was obtained from GetIpInterfaceEntry and only mutable configuration fields // are changed before calling SetIpInterfaceEntry. SetIpInterfaceEntry(row) }; windows_status(status).context("failed to update IP interface row") } fn restore_interface_metric(snapshot: InterfaceMetricSnapshot) -> Result<()> { let mut row = get_interface_row(snapshot.identity(), snapshot.family())?; row.UseAutomaticMetric = snapshot.automatic_metric(); row.Metric = snapshot.metric(); set_interface_row(&mut row) } fn restore_default_routes(snapshot: InterfaceMetricSnapshot) -> Result<()> { let mut row = get_interface_row(snapshot.identity(), snapshot.family())?; row.DisableDefaultRoutes = snapshot.disable_default_routes(); set_interface_row(&mut row) } fn restore_interface_mtu(snapshot: InterfaceMtuSnapshot) -> Result<()> { let mut row = get_interface_row(snapshot.identity(), snapshot.family())?; row.NlMtu = snapshot.mtu(); set_interface_row(&mut row) } fn interface_row_key( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, ) -> MIB_IPINTERFACE_ROW { let mut row = MIB_IPINTERFACE_ROW::default(); unsafe { // SAFETY: row points to valid writable storage for Windows to initialize. InitializeIpInterfaceEntry(&mut row); } row.Family = address_family(family); row.InterfaceLuid = NET_LUID_LH { Value: identity.luid(), }; row.InterfaceIndex = identity.index(); row } fn metric_snapshot( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, row: MIB_IPINTERFACE_ROW, ) -> InterfaceMetricSnapshot { InterfaceMetricSnapshot::new( identity, family, row.UseAutomaticMetric, row.Metric, row.DisableDefaultRoutes, ) } fn mtu_snapshot( identity: NetworkInterfaceIdentity, family: IpInterfaceFamily, row: MIB_IPINTERFACE_ROW, ) -> InterfaceMtuSnapshot { InterfaceMtuSnapshot::new(identity, family, row.NlMtu) } fn unicast_address_snapshot( identity: NetworkInterfaceIdentity, row: &MIB_UNICASTIPADDRESS_ROW, ) -> Option { if row.InterfaceIndex != identity.index() || luid_value(row.InterfaceLuid) != identity.luid() { return None; } let address = ip_from_sockaddr(&row.Address)?; if address.is_unspecified() { return None; } let family = match address { IpAddr::V4(_) => IpInterfaceFamily::Ipv4, IpAddr::V6(_) => IpInterfaceFamily::Ipv6, }; Some(InterfaceUnicastAddress::new( identity, family, address, row.OnLinkPrefixLength, )) } struct MibUnicastAddressTable(*mut MIB_UNICASTIPADDRESS_TABLE); impl MibUnicastAddressTable { fn rows(&self) -> &[MIB_UNICASTIPADDRESS_ROW] { let table = unsafe { // SAFETY: self.0 is checked non-null after GetUnicastIpAddressTable succeeds and is // owned by this guard until Drop frees it. &*self.0 }; unsafe { // SAFETY: Windows allocates NumEntries contiguous rows starting at Table. slice::from_raw_parts(table.Table.as_ptr(), table.NumEntries as usize) } } } impl Drop for MibUnicastAddressTable { fn drop(&mut self) { unsafe { // SAFETY: self.0 was allocated by GetUnicastIpAddressTable and is freed exactly once. FreeMibTable(self.0.cast()); } } } const fn address_family(family: IpInterfaceFamily) -> u16 { match family { IpInterfaceFamily::Ipv4 => AF_INET, IpInterfaceFamily::Ipv6 => AF_INET6, } } pub fn pin_relay_route(route: &RouteSnapshot) -> Result { let mut pinned = pinned_route_row(route); let status = unsafe { // SAFETY: pinned.row was initialized with InitializeIpForwardEntry and populated with a // valid host destination, next hop, and interface identity. CreateIpForwardEntry2(&pinned.row) }; let disposition = route_create_disposition(status).with_context(|| { format!( "failed to pin relay route to {} via interface index {}", route.destination(), route.interface_index() ) })?; pinned.delete_on_drop = disposition == RouteCreateDisposition::Created; Ok(pinned) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum RouteCreateDisposition { Created, AlreadyExists, } fn route_create_disposition(status: u32) -> io::Result { match status { ERROR_SUCCESS => Ok(RouteCreateDisposition::Created), ERROR_OBJECT_ALREADY_EXISTS => Ok(RouteCreateDisposition::AlreadyExists), _ => Err(io::Error::from_raw_os_error(status as i32)), } } pub struct PinnedRelayRoute { row: MIB_IPFORWARD_ROW2, destination: IpAddr, next_hop: Option, interface_index: u32, interface_luid: u64, delete_on_drop: bool, } impl PinnedRelayRoute { #[must_use] pub const fn destination(&self) -> IpAddr { self.destination } #[must_use] pub const fn next_hop(&self) -> Option { self.next_hop } #[must_use] pub const fn interface_index(&self) -> u32 { self.interface_index } #[must_use] pub const fn interface_luid(&self) -> u64 { self.interface_luid } #[must_use] pub const fn created_by_client(&self) -> bool { self.delete_on_drop } } impl fmt::Debug for PinnedRelayRoute { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PinnedRelayRoute") .field("destination", &self.destination) .field("next_hop", &self.next_hop) .field("interface_index", &self.interface_index) .field("interface_luid", &self.interface_luid) .field("delete_on_drop", &self.delete_on_drop) .finish_non_exhaustive() } } impl Drop for PinnedRelayRoute { fn drop(&mut self) { if !self.delete_on_drop { return; } let status = unsafe { // SAFETY: self.row is the same route row that was successfully created for this guard. DeleteIpForwardEntry2(&self.row) }; if let Err(error) = windows_status(status) { eprintln!( "failed to delete relay host route to {} on interface index {} LUID {}: {error:#}", self.destination, self.interface_index, self.interface_luid, ); } } } fn pinned_route_row(route: &RouteSnapshot) -> PinnedRelayRoute { let destination = route.destination(); let next_hop = route.next_hop(); let mut row = MIB_IPFORWARD_ROW2::default(); unsafe { // SAFETY: row points to valid writable storage for Windows to initialize. InitializeIpForwardEntry(&mut row); } row.InterfaceLuid = NET_LUID_LH { Value: route.interface_luid(), }; row.InterfaceIndex = route.interface_index(); row.DestinationPrefix = host_prefix(destination); row.NextHop = sockaddr_from_ip(next_hop.unwrap_or_else(|| unspecified_for(destination))); row.SitePrefixLength = host_prefix_len(destination); row.Metric = 0; row.Protocol = RouteProtocolNetMgmt; PinnedRelayRoute { row, destination, next_hop, interface_index: route.interface_index(), interface_luid: route.interface_luid(), delete_on_drop: false, } } fn parse_interface_guid(interface_guid: &str) -> Result { let value = interface_guid.trim(); let value = value .strip_prefix('{') .and_then(|value| value.strip_suffix('}')) .unwrap_or(value); let mut hex = String::with_capacity(32); for ch in value.chars() { if ch == '-' { continue; } if !ch.is_ascii_hexdigit() { bail!("interface GUID contains non-hex character {ch:?}"); } hex.push(ch); } if hex.len() != 32 { bail!("interface GUID must contain 32 hex digits"); } let value = u128::from_str_radix(&hex, 16).context("failed to parse interface GUID")?; Ok(GUID::from_u128(value)) } fn luid_value(luid: NET_LUID_LH) -> u64 { unsafe { // SAFETY: Reading the Value view is valid for the NET_LUID_LH union returned by Windows // or initialized from a Value. luid.Value } } fn host_prefix(addr: IpAddr) -> IP_ADDRESS_PREFIX { IP_ADDRESS_PREFIX { Prefix: sockaddr_from_ip(addr), PrefixLength: host_prefix_len(addr), } } const fn host_prefix_len(addr: IpAddr) -> u8 { match addr { IpAddr::V4(_) => 32, IpAddr::V6(_) => 128, } } const fn unspecified_for(addr: IpAddr) -> IpAddr { match addr { IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::UNSPECIFIED), IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::UNSPECIFIED), } } fn sockaddr_from_ip(addr: IpAddr) -> SOCKADDR_INET { match addr { IpAddr::V4(addr) => SOCKADDR_INET { Ipv4: SOCKADDR_IN { sin_family: AF_INET, sin_port: 0, sin_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_ne_bytes(addr.octets()), }, }, sin_zero: [0; 8], }, }, IpAddr::V6(addr) => SOCKADDR_INET { Ipv6: SOCKADDR_IN6 { sin6_family: AF_INET6, sin6_port: 0, sin6_flowinfo: 0, sin6_addr: IN6_ADDR { u: IN6_ADDR_0 { Byte: addr.octets(), }, }, Anonymous: SOCKADDR_IN6_0 { sin6_scope_id: 0 }, }, }, } } fn ip_from_sockaddr(sockaddr: &SOCKADDR_INET) -> Option { match unsafe { // SAFETY: Reading the discriminator is valid for any SOCKADDR_INET initialized by us or // by Windows APIs. sockaddr.si_family } { AF_INET => { let ipv4 = unsafe { // SAFETY: si_family reports that the IPv4 union field is active. sockaddr.Ipv4 }; let octets = unsafe { // SAFETY: S_addr is the compact IPv4 representation in network byte order. ipv4.sin_addr.S_un.S_addr.to_ne_bytes() }; Some(IpAddr::V4(Ipv4Addr::from(octets))) } AF_INET6 => { let ipv6 = unsafe { // SAFETY: si_family reports that the IPv6 union field is active. sockaddr.Ipv6 }; let octets = unsafe { // SAFETY: Byte is the compact IPv6 representation in network byte order. ipv6.sin6_addr.u.Byte }; Some(IpAddr::V6(Ipv6Addr::from(octets))) } _ => None, } } fn windows_status(status: u32) -> io::Result<()> { if status == ERROR_SUCCESS { Ok(()) } else { Err(io::Error::from_raw_os_error(status as i32)) } } #[cfg(test)] mod tests { use super::*; #[test] fn builds_ipv4_host_route_row() { let route = RouteSnapshot::new( ip("203.0.113.10"), ip("192.0.2.44"), Some(ip("192.0.2.1")), ip("0.0.0.0"), 0, 12, 34, 25, ); let pinned = pinned_route_row(&route); assert_eq!(pinned.destination(), ip("203.0.113.10")); assert_eq!(pinned.next_hop(), Some(ip("192.0.2.1"))); assert_eq!(pinned.interface_index(), 12); assert_eq!(pinned.interface_luid(), 34); assert_eq!(pinned.row.DestinationPrefix.PrefixLength, 32); assert_eq!(pinned.row.SitePrefixLength, 32); assert_eq!(pinned.row.Metric, 0); assert_eq!(pinned.row.Protocol, RouteProtocolNetMgmt); assert!(!pinned.created_by_client()); } #[test] fn classifies_route_create_statuses() { use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; assert_eq!( route_create_disposition(ERROR_SUCCESS).unwrap(), RouteCreateDisposition::Created ); assert_eq!( route_create_disposition(ERROR_OBJECT_ALREADY_EXISTS).unwrap(), RouteCreateDisposition::AlreadyExists ); assert!(route_create_disposition(ERROR_INVALID_PARAMETER).is_err()); } #[test] fn parses_interface_guid_strings() { let guid = parse_interface_guid("{00112233-4455-6677-8899-AABBCCDDEEFF}").unwrap(); assert_eq!(guid.data1, 0x0011_2233); assert_eq!(guid.data2, 0x4455); assert_eq!(guid.data3, 0x6677); assert_eq!(guid.data4, [0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]); assert!(parse_interface_guid("00112233-4455-6677-8899-aabbccddeeff").is_ok()); assert!(parse_interface_guid("{00112233-4455-6677-8899-AABBCCDDEE}").is_err()); assert!(parse_interface_guid("{00112233-4455-6677-8899-AABBCCDDEEGG}").is_err()); } #[test] fn builds_interface_row_keys() { let identity = NetworkInterfaceIdentity::new(12, 34); let row = interface_row_key(identity, IpInterfaceFamily::Ipv4); assert_eq!(row.Family, AF_INET); assert_eq!(row.InterfaceIndex, 12); assert_eq!(luid_value(row.InterfaceLuid), 34); } #[test] fn builds_metric_snapshots_from_rows() { let identity = NetworkInterfaceIdentity::new(12, 34); let mut row = interface_row_key(identity, IpInterfaceFamily::Ipv6); row.UseAutomaticMetric = true; row.Metric = 500; row.DisableDefaultRoutes = true; let snapshot = metric_snapshot(identity, IpInterfaceFamily::Ipv6, row); assert_eq!(snapshot.identity(), identity); assert_eq!(snapshot.family(), IpInterfaceFamily::Ipv6); assert!(snapshot.automatic_metric()); assert_eq!(snapshot.metric(), 500); assert!(snapshot.disable_default_routes()); } #[test] fn builds_mtu_snapshots_from_rows() { let identity = NetworkInterfaceIdentity::new(12, 34); let mut row = interface_row_key(identity, IpInterfaceFamily::Ipv4); row.NlMtu = 1200; let snapshot = mtu_snapshot(identity, IpInterfaceFamily::Ipv4, row); assert_eq!(snapshot.identity(), identity); assert_eq!(snapshot.family(), IpInterfaceFamily::Ipv4); assert_eq!(snapshot.mtu(), 1200); } #[test] fn builds_unicast_address_snapshots_from_rows() { let identity = NetworkInterfaceIdentity::new(12, 34); let row = unicast_row(identity, ip("10.73.42.51"), 24); let snapshot = unicast_address_snapshot(identity, &row).unwrap(); assert_eq!(snapshot.identity(), identity); assert_eq!(snapshot.family(), IpInterfaceFamily::Ipv4); assert_eq!(snapshot.address(), ip("10.73.42.51")); assert_eq!(snapshot.prefix_len(), 24); } #[test] fn filters_unicast_address_rows_for_other_interfaces() { let identity = NetworkInterfaceIdentity::new(12, 34); let mut row = unicast_row(identity, ip("10.73.42.51"), 24); row.InterfaceIndex = 99; assert_eq!(unicast_address_snapshot(identity, &row), None); } #[test] fn filters_unspecified_unicast_addresses() { let identity = NetworkInterfaceIdentity::new(12, 34); let row = unicast_row(identity, ip("0.0.0.0"), 0); assert_eq!(unicast_address_snapshot(identity, &row), None); } #[test] fn builds_ipv6_on_link_host_route_row() { let route = RouteSnapshot::new( ip("2001:db8::10"), ip("2001:db8::44"), None, ip("2001:db8::"), 64, 12, 34, 25, ); let pinned = pinned_route_row(&route); assert_eq!(pinned.destination(), ip("2001:db8::10")); assert_eq!(pinned.next_hop(), None); assert_eq!(pinned.row.DestinationPrefix.PrefixLength, 128); assert_eq!(pinned.row.SitePrefixLength, 128); assert_eq!(ip_from_sockaddr(&pinned.row.NextHop), Some(ip("::"))); } fn ip(value: &str) -> IpAddr { value.parse().unwrap() } fn unicast_row( identity: NetworkInterfaceIdentity, address: IpAddr, prefix_len: u8, ) -> MIB_UNICASTIPADDRESS_ROW { let mut row = unsafe { // SAFETY: MIB_UNICASTIPADDRESS_ROW is a plain Win32 data structure; tests populate // the fields read by unicast_address_snapshot before using it. std::mem::zeroed::() }; row.Address = sockaddr_from_ip(address); row.InterfaceLuid = NET_LUID_LH { Value: identity.luid(), }; row.InterfaceIndex = identity.index(); row.OnLinkPrefixLength = prefix_len; row } }