Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dedicated macos impl #98

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/bsd.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
44 changes: 39 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,27 @@ 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",
target_os = "dragonfly",
))]
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")]
Expand All @@ -114,7 +119,6 @@ pub fn local_ip() -> Result<IpAddr, Error> {
}

#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
Expand All @@ -135,6 +139,20 @@ pub fn local_ip() -> Result<IpAddr, Error> {
.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;
Expand Down Expand Up @@ -191,7 +209,6 @@ mod tests {

#[test]
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
Expand All @@ -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() {
Expand All @@ -224,7 +250,6 @@ mod tests {

#[test]
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
Expand All @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
118 changes: 118 additions & 0 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<(String, IpAddr)>, Error> {
unsafe {
let layout = Layout::new::<IfAddrsPtr>();
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<String, Error> {
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
))),
}
}