ndisapi/netlib/ip_helper/
network_adapter_info.rs

1use crate::{GuidWrapper, IfLuid, IpGatewayInfo, MacAddress, SockAddrStorage};
2use std::net::IpAddr;
3use windows::{
4    core::{w, Result, PCWSTR},
5    Win32::{
6        NetworkManagement::{
7            IpHelper::{
8                ResolveIpNetEntry2, IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211,
9                IP_ADAPTER_ADDRESSES_LH, MIB_IF_ROW2, MIB_IPNET_ROW2,
10            },
11            Ndis::{NDIS_MEDIUM, NDIS_PHYSICAL_MEDIUM},
12        },
13        Networking::WinSock::{AF_INET, AF_INET6},
14        System::Registry::{
15            RegCloseKey, RegOpenKeyExW, RegSetValueExW, HKEY, HKEY_LOCAL_MACHINE, KEY_WRITE, REG_SZ,
16        },
17    },
18};
19
20// Submodules
21pub mod address;
22pub mod gateway;
23pub mod getters;
24pub mod ndp;
25pub mod routing;
26pub mod statics;
27pub mod util;
28
29/// Represents information about a network adapter.
30///
31/// This struct provides an easy-to-use and safe interface for working with network adapter
32/// information, such as IP addresses, DNS server addresses, gateway addresses, and more.
33#[derive(Debug, Clone)]
34pub struct IphlpNetworkAdapterInfo {
35    /// The index of the IPv4 interface.
36    if_index: u32,
37    /// The interface index for the IPv6 IP address. This member is zero if IPv6 is not available on the interface.
38    ipv6_if_index: u32,
39    /// Contains the name of the adapter. Unlike an adapter's friendly name, the adapter name specified in `adapter_name_` is permanent and cannot be modified by the user.
40    adapter_name: String,
41    /// Contains the name of the underlying hardware adapter.
42    true_adapter_name: GuidWrapper,
43    /// A description for the adapter.
44    description: String,
45    /// A user-friendly name for the adapter.
46    friendly_name: String,
47    /// List of IP unicast addresses for the adapter.
48    unicast_address_list: Vec<IpAddr>,
49    /// List of DNS server addresses for the adapter.
50    dns_server_address_list: Vec<IpAddr>,
51    /// List of gateways for the adapter.
52    gateway_address_list: Vec<IpGatewayInfo>,
53    /// The Media Access Control (MAC) address for the adapter.
54    physical_address: MacAddress,
55    /// The maximum transmission unit (MTU) size, in bytes.
56    mtu: u16,
57    /// The interface type as defined by the Internet Assigned Names Authority (IANA).
58    /// Possible values for the interface type are listed in the `Ipifcons.h` header file.
59    if_type: u32,
60    /// The current speed in bits per second of the transmit link for the adapter.
61    transmit_link_speed: u64,
62    /// The current speed in bits per second of the receive link for the adapter.
63    receive_link_speed: u64,
64    /// The interface LUID for the adapter address.
65    luid: IfLuid,
66    /// The NDIS media type for the interface. This member can be one of the values from the `NDIS_MEDIUM`
67    /// enumeration type defined in the `Ntddndis.h` header file.
68    media_type: NDIS_MEDIUM,
69    /// The NDIS physical medium type. This member can be one of the values from the `NDIS_PHYSICAL_MEDIUM`
70    /// enumeration type defined in the `Ntddndis.h` header file.
71    physical_medium_type: NDIS_PHYSICAL_MEDIUM,
72    /// If `physical_medium_type_` is `NdisPhysicalMediumUnspecified` (virtual network interface on top of the real one),
73    /// this one may contain real physical media.
74    true_medium_type: NDIS_PHYSICAL_MEDIUM,
75    /// NDISWANIP associated MAC address.
76    ndis_wan_ip_link: MacAddress,
77    /// NDISWANIPV6 associated MAC address.
78    ndis_wan_ipv6_link: MacAddress,
79}
80
81impl IphlpNetworkAdapterInfo {
82    /// Constructs a new `IphlpNetworkAdapterInfo` instance from raw pointers to `IP_ADAPTER_ADDRESSES_LH`
83    /// and `MIB_IF_ROW2` structures.
84    ///
85    /// This method is marked as `unsafe` because it dereferences raw pointers, which can lead
86    /// to undefined behavior if not used correctly.
87    ///
88    /// # Safety
89    ///
90    /// The caller must ensure that `address` and `if_row` are valid pointers to
91    /// `IP_ADAPTER_ADDRESSES_LH` and `MIB_IF_ROW2` structures, respectively, and that the
92    /// structures remain valid for the duration of the method call.
93    ///
94    /// # Arguments
95    ///
96    /// * `address` - A pointer to an `IP_ADAPTER_ADDRESSES_LH` structure containing information
97    ///   about the network adapter.
98    /// * `if_row` - A pointer to a `MIB_IF_ROW2` structure containing information about the
99    ///   network interface.
100    ///
101    /// # Examples
102    ///
103    /// ```rust,ignore
104    /// use my_crate::IphlpNetworkAdapterInfo;
105    /// use my_crate::{IP_ADAPTER_ADDRESSES_LH, MIB_IF_ROW2};
106    ///
107    /// unsafe {
108    ///     let address = /* ... */;
109    ///     let if_row = /* ... */;
110    ///
111    ///     let network_adapter_info = IphlpNetworkAdapterInfo::new(address, if_row);
112    /// }
113    ///
114    pub unsafe fn new(address: *const IP_ADAPTER_ADDRESSES_LH, if_row: *const MIB_IF_ROW2) -> Self {
115        let address = unsafe { &*address };
116
117        let mut unicast_address_list = Vec::new();
118        let mut unicast_address = address.FirstUnicastAddress;
119        while !unicast_address.is_null() {
120            unicast_address_list.push(
121                SockAddrStorage::from_sockaddr(unsafe { *(*unicast_address).Address.lpSockaddr })
122                    .into(),
123            );
124            unicast_address = unsafe { (*unicast_address).Next };
125        }
126
127        let mut dns_server_address_list = Vec::new();
128        let mut dns_address = address.FirstDnsServerAddress;
129        while !dns_address.is_null() {
130            dns_server_address_list.push(
131                SockAddrStorage::from_sockaddr(unsafe { *(*dns_address).Address.lpSockaddr })
132                    .into(),
133            );
134            dns_address = unsafe { (*dns_address).Next };
135        }
136
137        let mut gateway_address_list = Vec::new();
138        let mut gateway_address = address.FirstGatewayAddress;
139        while !gateway_address.is_null() {
140            gateway_address_list.push(IpGatewayInfo::new(
141                SockAddrStorage::from_sockaddr(unsafe { *(*gateway_address).Address.lpSockaddr })
142                    .into(),
143                None,
144            ));
145            gateway_address = unsafe { (*gateway_address).Next };
146        }
147
148        let mut result = Self {
149            if_index: unsafe { address.Anonymous1.Anonymous.IfIndex },
150            ipv6_if_index: address.Ipv6IfIndex,
151            adapter_name: unsafe { address.AdapterName.to_string() }.unwrap_or_default(),
152            description: unsafe { address.Description.to_string() }.unwrap_or_default(),
153            friendly_name: unsafe { address.FriendlyName.to_string() }.unwrap_or_default(),
154            physical_address: MacAddress::from_slice(&address.PhysicalAddress).unwrap_or_default(),
155            mtu: address.Mtu as u16,
156            if_type: address.IfType,
157            transmit_link_speed: address.TransmitLinkSpeed,
158            receive_link_speed: address.ReceiveLinkSpeed,
159            luid: IfLuid::new(&address.Luid),
160            media_type: (*if_row).MediaType,
161            physical_medium_type: (*if_row).PhysicalMediumType,
162            unicast_address_list,
163            dns_server_address_list,
164            gateway_address_list,
165            true_adapter_name: GuidWrapper::new(),
166            true_medium_type: NDIS_PHYSICAL_MEDIUM::default(),
167            ndis_wan_ip_link: MacAddress::default(),
168            ndis_wan_ipv6_link: MacAddress::default(),
169        };
170
171        // For each ethernet of wi-fi interface initialize the gateway hardware address list by resolving
172        // the hardware addresses for each gateway IP address in the list.
173        if address.IfType == IF_TYPE_ETHERNET_CSMACD || address.IfType == IF_TYPE_IEEE80211 {
174            result.initialize_gateway_hw_address_list();
175        }
176
177        result
178    }
179
180    /// Initializes the gateway hardware address list by resolving the hardware addresses
181    /// for each gateway IP address in the list.
182    ///
183    /// This function iterates over the `gateway_address_list` and resolves the hardware addresses
184    /// by updating the `MIB_IPNET_ROW2` structure and calling the `ResolveIpNetEntry2` function.
185    /// If successful, the hardware address is assigned to the `hardware_address` field of the
186    /// corresponding `Address` structure.
187    ///
188    /// # Safety
189    ///
190    /// This function contains unsafe code blocks due to the use of FFI calls to the WinAPI, direct memory access,
191    /// and pointer casting. Make sure that the data structures are properly initialized and that the
192    /// WinAPI functions are used correctly to ensure memory safety.
193    ///
194    unsafe fn initialize_gateway_hw_address_list(&mut self) {
195        if !self.gateway_address_list.is_empty() {
196            // check if the address list is empty
197            for address in &mut self.gateway_address_list {
198                // iterate through each address in the list
199                let mut row: MIB_IPNET_ROW2 = unsafe { std::mem::zeroed() }; // create a new row object with default values
200
201                // assign values to new row object
202                row.Address.si_family = if address.ip_address.is_ipv4() {
203                    AF_INET
204                } else {
205                    AF_INET6
206                }; // assign si_family value
207                row.InterfaceLuid = self.luid.into(); // assign InterfaceLuid value
208
209                // match against address family and assign to proper field in row
210                match address.ip_address {
211                    IpAddr::V4(ip_address) => {
212                        // for IPv4 addresses
213                        row.Address.Ipv4.sin_addr = ip_address.into();
214                    }
215                    IpAddr::V6(ip_address) => {
216                        // for IPv6 addresses
217                        row.Address.Ipv6.sin6_addr = ip_address.into();
218                    }
219                }
220
221                // resolve IP address to MAC address using the current row and store it in the address object
222                if unsafe { ResolveIpNetEntry2(&mut row, None) }.is_ok() {
223                    address.hardware_address =
224                        MacAddress::from_slice(&row.PhysicalAddress).unwrap_or_default();
225                }
226            }
227        }
228    }
229
230    /// Sets the friendly name of the network adapter.
231    ///
232    /// This function sets the friendly name of the network adapter by updating the Windows registry.
233    /// The registry key for the adapter is located at:
234    /// `SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\{adapter_name}\Connection`.
235    ///
236    /// # Arguments
237    ///
238    /// * `friendly_name` - A string or any type that can be converted into a `String` representing the friendly name of the adapter.
239    ///
240    /// # Returns
241    ///
242    /// * `Result<()>` - Returns `Ok(())` if the friendly name is set successfully. Returns an `Err` containing the error code if there is a failure.
243    ///
244    // This function sets the friendly name of a network adapter and updates the registry accordingly
245    pub fn set_friendly_name(&mut self, friendly_name: impl Into<String>) -> Result<()> {
246        self.friendly_name = friendly_name.into(); // set the friendly name of the adapter to the input value
247
248        // create the registry key path for the adapter's connection settings
249        let friendly_name_key = format!(
250        "SYSTEM\\CurrentControlSet\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{}\\Connection",
251        &self.adapter_name
252    );
253
254        // Convert the string to UTF16 array and get a pointer to it as PCWSTR
255        let mut friendly_name_key = friendly_name_key.encode_utf16().collect::<Vec<u16>>();
256        friendly_name_key.push(0); // add null terminator to end of key string
257
258        let mut hkey = HKEY::default();
259
260        // open the registry key for the adapter's connection settings with write access
261        let mut result = unsafe {
262            RegOpenKeyExW(
263                HKEY_LOCAL_MACHINE, // handle to a pre-defined registry key
264                PCWSTR::from_raw(friendly_name_key.as_ptr()), // registry key path as a PCWSTR pointer
265                0,                                            // reserved (ignored)
266                KEY_WRITE,                                    // desired security access level
267                &mut hkey, // pointer to a variable to receive the handle to the opened key
268            )
269        };
270
271        if result.is_ok() {
272            // if the key was successfully opened
273            // set the AdapterName registry value to the friendly name of the adapter
274            result = unsafe {
275                RegSetValueExW(
276                    hkey,                                // handle to an open registry key
277                    w!("Name"),                          // name of the value to be set
278                    0,                                   // reserved (ignored)
279                    REG_SZ,                              // data type of the value
280                    Some(self.friendly_name.as_bytes()), // pointer to the buffer containing the value's data
281                )
282            };
283
284            _ = unsafe {
285                RegCloseKey(hkey) // close the registry key handle
286            };
287        }
288
289        result.ok()
290    }
291
292    /// Checks if IP address information in the provided network_adapter_info is different
293    /// from the current one.
294    ///
295    /// # Arguments
296    ///
297    /// * `rhs`: IphlpNetworkAdapterInfo to compare to
298    /// * `check_gateway`: If true, also checks the gateway information
299    ///
300    /// # Returns
301    ///
302    /// * `bool`: true if provided IphlpNetworkAdapterInfo contains the same IP addresses, false otherwise
303    pub fn is_same_address_info(&self, rhs: &IphlpNetworkAdapterInfo, check_gateway: bool) -> bool {
304        if self.unicast_address_list.len() != rhs.unicast_address_list.len() {
305            return false;
306        }
307
308        if check_gateway && self.gateway_address_list.len() != rhs.gateway_address_list.len() {
309            return false;
310        }
311
312        // Check if any of the unicast addresses have changed
313        let ret_val = rhs
314            .unicast_address_list
315            .iter()
316            .all(|address| self.unicast_address_list.contains(address));
317
318        if !ret_val {
319            return ret_val;
320        }
321
322        // Check if any of the gateways have changed
323        if check_gateway {
324            rhs.gateway_address_list
325                .iter()
326                .all(|address| self.gateway_address_list.contains(address))
327        } else {
328            ret_val
329        }
330    }
331
332    /// Resets the adapter's addresses and routes.
333    ///
334    /// # Returns
335    ///
336    /// * `bool`: true if successful, false otherwise.
337    pub fn reset_adapter(&self) -> bool {
338        self.reset_unicast_addresses() && self.reset_adapter_routes()
339    }
340}