Skip to content

Commit

Permalink
Dynamically obtain local IP for BSD (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakewilliami authored Nov 28, 2022
1 parent eaaec59 commit 9c3de8f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
44 changes: 37 additions & 7 deletions src/bsd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use libc::{getifaddrs, ifaddrs, sockaddr_in, sockaddr_in6, strlen, AF_INET, AF_INET6};
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};

Expand Down Expand Up @@ -27,6 +27,24 @@ type IfAddrsPtr = *mut *mut ifaddrs;
/// }
/// ```
pub fn list_afinet_netifas() -> Result<Vec<(String, IpAddr)>, Error> {
match list_afinet_netifas_info() {
Ok(interfaces) => Ok(interfaces
.iter()
.map(|i| (i.iname.clone(), i.addr))
.collect()),
Err(e) => Err(e),
}
}

pub struct AfInetInfo {
pub addr: IpAddr,
pub iname: String,
pub is_loopback: bool,
}

// Internal methos to list AF_INET info in a struct. This metho is used by
// list_afiinet_netifas and local_ip,
pub fn list_afinet_netifas_info() -> Result<Vec<AfInetInfo>, Error> {
unsafe {
let layout = Layout::new::<IfAddrsPtr>();
let ptr = alloc(layout);
Expand All @@ -41,7 +59,7 @@ pub fn list_afinet_netifas() -> Result<Vec<(String, IpAddr)>, Error> {
)));
}

let mut interfaces: Vec<(String, IpAddr)> = Vec::new();
let mut interfaces: Vec<AfInetInfo> = Vec::new();
let ifa = myaddr;

// An instance of `ifaddrs` is build on top of a linked list where
Expand Down Expand Up @@ -70,19 +88,24 @@ pub fn list_afinet_netifas() -> Result<Vec<(String, IpAddr)>, Error> {
ip_addr = Ipv4Addr::from(in_addr.s_addr.swap_bytes());
}

let name = get_ifa_name(ifa)?;

interfaces.push((name, IpAddr::V4(ip_addr)));
interfaces.push(AfInetInfo {
addr: IpAddr::V4(ip_addr),
iname: get_ifa_name(ifa)?,
is_loopback: is_loopback_addr(ifa),
});
}
// 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)));
interfaces.push(AfInetInfo {
addr: IpAddr::V6(ip_addr),
iname: get_ifa_name(ifa)?,
is_loopback: is_loopback_addr(ifa),
});
}
_ => {}
}
Expand Down Expand Up @@ -112,3 +135,10 @@ unsafe fn get_ifa_name(ifa: *mut *mut ifaddrs) -> Result<String, Error> {
))),
}
}

/// Determines if an interface address is a loopback address
unsafe fn is_loopback_addr(ifa: *mut *mut ifaddrs) -> bool {
let iname = get_ifa_name(ifa).unwrap_or_default();
let iflags = (*(*ifa)).ifa_flags;
iname.starts_with("lo") || ((iflags as i32) & IFF_RUNNING) == 0
}
27 changes: 12 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,18 @@ pub fn local_ip() -> Result<IpAddr, Error> {
target_os = "dragonfly",
))]
{
let ifas = crate::bsd::list_afinet_netifas()?;

if let Some((_, ipaddr)) = find_ifa(ifas, vec!["en0", "epair0b"]) {
return Ok(ipaddr);
}

Err(Error::PlatformNotSupported(env::consts::OS.to_string()))
let ifas = crate::bsd::list_afinet_netifas_info()?;

ifas.into_iter()
.filter_map(|interface| {
if interface.is_loopback {
Some(interface.addr)
} else {
None
}
})
.find(|ip_addr| matches!(ip_addr, IpAddr::V4(_)))
.ok_or_else(|| Error::PlatformNotSupported(env::consts::OS.to_string()))
}

#[cfg(target_os = "windows")]
Expand Down Expand Up @@ -157,14 +162,6 @@ pub fn local_ip() -> Result<IpAddr, Error> {
}
}

/// Finds the network interface with any of the provided names in the vector of network
/// interfaces provided
pub fn find_ifa(ifas: Vec<(String, IpAddr)>, ifa_names: Vec<&str>) -> Option<(String, IpAddr)> {
ifas.into_iter().find(|(name, ipaddr)| {
ifa_names.contains(&name.as_str()) && matches!(ipaddr, IpAddr::V4(_))
})
}

// A catch-all function to error if not implemented for OS
#[cfg(not(any(
target_os = "linux",
Expand Down

0 comments on commit 9c3de8f

Please sign in to comment.