From 7fb63b007658d889faa423cfda6aaaf16d029d97 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 11 Jan 2024 02:05:58 -0800 Subject: [PATCH 1/3] Added 'get_mac_address_by_ip' which does what it says Also added ./example/lookup_by_ip.rs which correctly outputs the MacAddress on my windows machine. Linux implementation in the next commit. --- examples/lookup_by_ip.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/lookup_by_ip.rs diff --git a/examples/lookup_by_ip.rs b/examples/lookup_by_ip.rs new file mode 100644 index 0000000..82fd3e7 --- /dev/null +++ b/examples/lookup_by_ip.rs @@ -0,0 +1,29 @@ +use std::{net::IpAddr, str::FromStr}; + +use mac_address::get_mac_address_by_ip; + +// UDP 'connect' to a remote IP (Google's DNS) and +// then see which local IP address we were bound to. +// +// NOTE: this is a nice portable way to use the routing +// table and sends no actual packets +fn lookup_default_adapter_ip() -> std::io::Result { + let udp_sock = std::net::UdpSocket::bind(("0.0.0.0", 0))?; + udp_sock.connect((IpAddr::from_str("8.8.8.8").unwrap(), 53))?; + Ok(udp_sock.local_addr()?.ip()) +} + +fn main() -> std::io::Result<()> { + // find a useful IP local address to test against + let local_ip = lookup_default_adapter_ip()?; + // find the MacAddress of the Adapter with this IP + match get_mac_address_by_ip(&local_ip) { + Ok(Some(ma)) => { + println!("MAC addr = {}", ma); + println!("bytes = {:?}", ma.bytes()); + } + Ok(None) => println!("No MAC address found."), + Err(e) => println!("{:?}", e), + } + Ok(()) +} From 3833e418ae7d33bd7dc6a877ba606bf611167069 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 11 Jan 2024 02:05:58 -0800 Subject: [PATCH 2/3] Added 'get_mac_address_by_ip' which does what it says Also added ./example/lookup_by_ip.rs which correctly outputs the MacAddress on my windows machine. Linux implementation in the next commit. --- examples/lookup_by_ip.rs | 29 ++++++++++++++++ src/lib.rs | 9 +++++ src/windows.rs | 74 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 examples/lookup_by_ip.rs diff --git a/examples/lookup_by_ip.rs b/examples/lookup_by_ip.rs new file mode 100644 index 0000000..82fd3e7 --- /dev/null +++ b/examples/lookup_by_ip.rs @@ -0,0 +1,29 @@ +use std::{net::IpAddr, str::FromStr}; + +use mac_address::get_mac_address_by_ip; + +// UDP 'connect' to a remote IP (Google's DNS) and +// then see which local IP address we were bound to. +// +// NOTE: this is a nice portable way to use the routing +// table and sends no actual packets +fn lookup_default_adapter_ip() -> std::io::Result { + let udp_sock = std::net::UdpSocket::bind(("0.0.0.0", 0))?; + udp_sock.connect((IpAddr::from_str("8.8.8.8").unwrap(), 53))?; + Ok(udp_sock.local_addr()?.ip()) +} + +fn main() -> std::io::Result<()> { + // find a useful IP local address to test against + let local_ip = lookup_default_adapter_ip()?; + // find the MacAddress of the Adapter with this IP + match get_mac_address_by_ip(&local_ip) { + Ok(Some(ma)) => { + println!("MAC addr = {}", ma); + println!("bytes = {:?}", ma.bytes()); + } + Ok(None) => println!("No MAC address found."), + Err(e) => println!("{:?}", e), + } + Ok(()) +} 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] From 59473cdcf87b91a2ec903332224f51d2e0391b00 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 11 Jan 2024 02:55:29 -0800 Subject: [PATCH 3/3] Linux implementation of 'get_mac_address_by_ip' getaddrif() does not return the information indexed by adapter so we have to build our own index. Tested on my Linux vm - seems to work! --- src/linux.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/linux.rs b/src/linux.rs index 5417bbc..5b59bd2 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1,5 +1,10 @@ #![allow(dead_code)] +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, +}; + use crate::MacAddressError; use nix::ifaddrs::*; @@ -30,6 +35,54 @@ pub fn get_mac(name: Option<&str>) -> Result, MacAddressError> { Ok(None) } +/// Uses the `getifaddrs` call to retrieve a list of network interfaces on the +/// host device and returns the MAC address that matching the adapter with +/// the given IP +/// +/// Because nix returns all of the IP's and MAC's in a combined list, we need +/// to map the IP to an inteface name and track the MAC addresses by interface name +/// and see if there's a match +pub fn get_mac_address_by_ip(ip: &IpAddr) -> Result, MacAddressError> { + let ifiter = getifaddrs()?; + + let mut ip_on_inferface: Option = None; + let mut mac_to_interface: HashMap> = HashMap::new(); + for interface in ifiter { + if let Some(iface_address) = interface.address { + // is this a mac address? + if let Some(link) = iface_address.as_link_addr() { + mac_to_interface.insert(interface.interface_name.clone(), link.addr()); + // did we just find what we're looking for? + if let Some(intf_name) = &ip_on_inferface { + if *intf_name == interface.interface_name { + return Ok(link.addr()); + } + } + } + if let Some(adapter_ip) = if let Some(sin4) = iface_address.as_sockaddr_in() { + // v4 addr? + Some(IpAddr::from(Ipv4Addr::from(sin4.ip()))) + } else if let Some(sin6) = iface_address.as_sockaddr_in6() { + // v6 addr? + Some(IpAddr::from(Ipv6Addr::from(sin6.ip()))) + } else { + // something else, ignore + None + } { + // found an IP for this adapter - if it's the one we're looking for, save it + if adapter_ip == *ip { + ip_on_inferface = Some(interface.interface_name.clone()); + if let Some(mac) = mac_to_interface.get(&interface.interface_name) { + return Ok(mac.clone()); + } + } + } + } + } + + Ok(None) +} + pub fn get_ifname(mac: &[u8; 6]) -> Result, MacAddressError> { let ifiter = getifaddrs()?;