From d1d4ba2ab8c5fe8ab6594e93f148169d4669c07f Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 3 Jan 2023 23:57:50 -0400 Subject: [PATCH] feat: dedicated macos impl --- src/bsd.rs | 3 +- src/lib.rs | 44 ++++++++++++++++--- src/linux.rs | 1 + src/macos.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 src/macos.rs diff --git a/src/bsd.rs b/src/bsd.rs index a3fc934..b633a25 100644 --- a/src/bsd.rs +++ b/src/bsd.rs @@ -1,7 +1,8 @@ -use libc::{getifaddrs, ifaddrs, sockaddr_in, sockaddr_in6, strlen, AF_INET, AF_INET6, IFF_RUNNING}; use std::alloc::{alloc, dealloc, Layout}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use libc::{getifaddrs, ifaddrs, sockaddr_in, sockaddr_in6, strlen, AF_INET, AF_INET6, IFF_RUNNING}; + use crate::Error; /// `ifaddrs` struct raw pointer alias diff --git a/src/lib.rs b/src/lib.rs index 784086a..7a28e3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,15 +74,14 @@ pub mod linux; pub use crate::linux::*; #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", ))] pub mod bsd; + #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", @@ -90,6 +89,12 @@ pub mod bsd; ))] pub use crate::bsd::*; +#[cfg(target_os = "macos")] +pub mod macos; + +#[cfg(target_os = "macos")] +pub use crate::macos::*; + #[cfg(target_family = "windows")] pub mod windows; #[cfg(target_family = "windows")] @@ -114,7 +119,6 @@ pub fn local_ip() -> Result { } #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", @@ -135,6 +139,20 @@ pub fn local_ip() -> Result { .ok_or_else(|| Error::PlatformNotSupported(env::consts::OS.to_string())) } + #[cfg(target_os = "macos")] + { + let ifas = crate::macos::list_afinet_netifas()?; + + if let Some((_, ip_addr)) = ifas + .into_iter() + .find(|(name, ipaddr)| name == "en0" && matches!(ipaddr, IpAddr::V4(_))) + { + Ok(ip_addr) + } else { + Err(Error::PlatformNotSupported(env::consts::OS.to_string())) + } + } + #[cfg(target_os = "windows")] { use windows_sys::Win32::Networking::WinSock::AF_INET; @@ -191,7 +209,6 @@ mod tests { #[test] #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", @@ -204,6 +221,15 @@ mod tests { println!("BSD 'local_ip': {:?}", my_local_ip); } + #[test] + #[cfg(target_os = "macos")] + fn find_local_ip() { + let my_local_ip = local_ip().unwrap(); + + assert!(matches!(my_local_ip, IpAddr::V4(_))); + println!("macOS 'local_ip': {:?}", my_local_ip); + } + #[test] #[cfg(target_os = "windows")] fn find_local_ip() { @@ -224,7 +250,6 @@ mod tests { #[test] #[cfg(any( - target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", @@ -237,6 +262,15 @@ mod tests { assert!(!network_interfaces.unwrap().is_empty()); } + #[test] + #[cfg(target_os = "macos")] + fn find_network_interfaces() { + let network_interfaces = list_afinet_netifas(); + + assert!(network_interfaces.is_ok()); + assert!(network_interfaces.unwrap().len() >= 1); + } + #[test] #[cfg(target_os = "windows")] fn find_network_interfaces() { diff --git a/src/linux.rs b/src/linux.rs index 492837d..6e8d971 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1,4 +1,5 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use neli::attr::Attribute; use neli::consts::nl::{NlmF, NlmFFlags}; use neli::consts::socket::NlFamily; diff --git a/src/macos.rs b/src/macos.rs new file mode 100644 index 0000000..a59110b --- /dev/null +++ b/src/macos.rs @@ -0,0 +1,118 @@ +use std::alloc::{alloc, dealloc, Layout}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use libc::{getifaddrs, ifaddrs, sockaddr_in, sockaddr_in6, strlen, AF_INET, AF_INET6}; + +use crate::Error; + +/// `ifaddrs` struct raw pointer alias +type IfAddrsPtr = *mut *mut ifaddrs; + +/// Perform a search over the system's network interfaces using `getifaddrs`, +/// retrieved network interfaces belonging to both socket address families +/// `AF_INET` and `AF_INET6` are retrieved along with the interface address name. +/// +/// # Example +/// +/// ``` +/// use std::net::IpAddr; +/// use local_ip_address::list_afinet_netifas; +/// +/// let ifas = list_afinet_netifas().unwrap(); +/// +/// if let Some((_, ipaddr)) = ifas +/// .iter() +/// .find(|(name, ipaddr)| *name == "en0" && matches!(ipaddr, IpAddr::V4(_))) { +/// // This is your local IP address: 192.168.1.111 +/// println!("This is your local IP address: {:?}", ipaddr); +/// } +/// ``` +pub fn list_afinet_netifas() -> Result, Error> { + unsafe { + let layout = Layout::new::(); + let ptr = alloc(layout); + let myaddr = ptr as IfAddrsPtr; + let getifaddrs_result = getifaddrs(myaddr); + + if getifaddrs_result != 0 { + // an error ocurred on getifaddrs + return Err(Error::StrategyError(format!( + "GetIfAddrs returned error: {}", + getifaddrs_result + ))); + } + + let mut interfaces: Vec<(String, IpAddr)> = Vec::new(); + let ifa = myaddr; + + // An instance of `ifaddrs` is build on top of a linked list where + // `ifaddrs.ifa_next` represent the next node in the list. + // + // To find the relevant interface address walk over the nodes of the + // linked list looking for interface address which belong to the socket + // address families AF_INET (IPv4) and AF_INET6 (IPv6) + while !(**ifa).ifa_next.is_null() { + let ifa_addr = (**ifa).ifa_addr; + + match (*ifa_addr).sa_family as i32 { + // AF_INET IPv4 protocol implementation + AF_INET => { + let interface_address = ifa_addr; + let socket_addr_v4: *mut sockaddr_in = interface_address as *mut sockaddr_in; + let in_addr = (*socket_addr_v4).sin_addr; + let mut ip_addr = Ipv4Addr::from(in_addr.s_addr); + + if cfg!(target_endian = "little") { + // due to a difference on how bytes are arranged on a + // single word of memory by the CPU, swap bytes based + // on CPU endianess to avoid having twisted IP addresses + // + // refer: https://github.com/rust-lang/rust/issues/48819 + ip_addr = Ipv4Addr::from(in_addr.s_addr.swap_bytes()); + } + + let name = get_ifa_name(ifa)?; + + interfaces.push((name, IpAddr::V4(ip_addr))); + + *ifa = (**ifa).ifa_next; + continue; + } + // AF_INET6 IPv6 protocol implementation + AF_INET6 => { + let interface_address = ifa_addr; + let socket_addr_v6: *mut sockaddr_in6 = interface_address as *mut sockaddr_in6; + let in6_addr = (*socket_addr_v6).sin6_addr; + let ip_addr = Ipv6Addr::from(in6_addr.s6_addr); + let name = get_ifa_name(ifa)?; + + interfaces.push((name, IpAddr::V6(ip_addr))); + + *ifa = (**ifa).ifa_next; + continue; + } + _ => { + *ifa = (**ifa).ifa_next; + continue; + } + } + } + + dealloc(ptr, layout); + Ok(interfaces) + } +} + +/// Retrieves the name of a interface address +unsafe fn get_ifa_name(ifa: *mut *mut ifaddrs) -> Result { + let str = (*(*ifa)).ifa_name as *mut u8; + let len = strlen(str as *const i8); + let slice = std::slice::from_raw_parts(str, len); + match String::from_utf8(slice.to_vec()) { + Ok(s) => Ok(s), + Err(e) => Err(Error::StrategyError(format!( + "Failed to retrieve interface name. The name is not a valid UTF-8 string. {}", + e + ))), + } +}