diff --git a/src/lib.rs b/src/lib.rs index e5b56d5..c9c7bad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,8 @@ mod os; mod os; mod iter; +use std::net::IpAddr; + pub use iter::MacAddressIterator; /// Possible errors when attempting to retrieve a MAC address. @@ -128,6 +130,13 @@ pub fn name_by_mac_address(mac: &MacAddress) -> Result, MacAddres os::get_ifname(&mac.bytes) } +/// Attempts to look up the local MAC address by matching the Apapter's IP Address +pub fn get_mac_address_by_ip(ip: &IpAddr) -> Result, MacAddressError> { + let bytes = os::get_mac_address_by_ip(ip)?; + + Ok(bytes.map(|b| MacAddress { bytes: b })) +} + impl MacAddress { /// Returns the array of MAC address bytes. pub fn bytes(self) -> [u8; 6] { diff --git a/src/windows.rs b/src/windows.rs index 521517e..7faaa74 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -2,16 +2,88 @@ use std::{ convert::{TryFrom, TryInto}, ffi::CStr, ffi::OsString, + net::IpAddr, os::windows::ffi::OsStringExt, ptr, slice, }; -use winapi::shared::{ntdef::ULONG, winerror::ERROR_SUCCESS, ws2def::AF_UNSPEC}; +use winapi::shared::{ + ntdef::ULONG, + winerror::ERROR_SUCCESS, + ws2def::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_IN}, + ws2ipdef::SOCKADDR_IN6, +}; use winapi::um::{iphlpapi::GetAdaptersAddresses, iptypes::IP_ADAPTER_ADDRESSES_LH}; use crate::MacAddressError; const GAA_FLAG_NONE: ULONG = 0x0000; +/// Similar to get_mac(); walk this list of adapters and in each +/// adapter, walk the list of UnicastIpAddresses and return the mac address +/// of the first one that matches the given IP +pub(crate) fn get_mac_address_by_ip(ip: &IpAddr) -> Result, MacAddressError> { + let adapters = get_adapters()?; + // Safety: We don't use the pointer after `adapters` is dropped + let mut ptr = unsafe { adapters.ptr() }; + + // for each adapter on the machine + while !ptr.is_null() { + let bytes = unsafe { convert_mac_bytes(ptr) }; + + let mut ip_ptr = unsafe { (*ptr).FirstUnicastAddress }; + // for each IP on the adapter + while !ip_ptr.is_null() { + let sock_addr = unsafe { (*ip_ptr).Address.lpSockaddr }; + let adapter_ip = match unsafe { (*sock_addr).sa_family } as i32 { + AF_INET => unsafe { + let addr = (*(sock_addr as *mut SOCKADDR_IN)).sin_addr; + Some(IpAddr::from([ + addr.S_un.S_un_b().s_b1, + addr.S_un.S_un_b().s_b2, + addr.S_un.S_un_b().s_b3, + addr.S_un.S_un_b().s_b4, + ])) + }, + AF_INET6 => unsafe { + let addr = (*(sock_addr as *mut SOCKADDR_IN6)).sin6_addr; + Some(IpAddr::from(addr.u.Byte().clone())) + }, + _ => { + // ignore unknown address families; only support IPv4/IPv6 + None + } + }; + if let Some(adapter_ip) = adapter_ip { + if adapter_ip == *ip { + return Ok(Some(bytes)); + } + } + // Otherwise check the next IP on the adapter + #[cfg(target_pointer_width = "32")] + { + ip_ptr = unsafe { ip_ptr.read_unaligned().Next }; + } + + #[cfg(not(target_pointer_width = "32"))] + { + ip_ptr = unsafe { (*ip_ptr).Next }; + } + } + + // Otherwise go to the next adapter + #[cfg(target_pointer_width = "32")] + { + ptr = unsafe { ptr.read_unaligned().Next }; + } + + #[cfg(not(target_pointer_width = "32"))] + { + ptr = unsafe { (*ptr).Next }; + } + } + // All of the calls succeeded, just didn't find it... + Ok(None) +} /// Uses bindings to the `Iphlpapi.h` Windows header to fetch the interface /// devices list with /// [GetAdaptersAddresses][https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx]