use crate::IpType;
use crate::{Error, Result};
#[cfg(windows)]
use log::error;
use log::{debug, trace};
use std::ffi::CStr;
use std::mem;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ptr;
#[cfg(unix)]
use std::str::FromStr;
#[cfg(windows)]
use winapi::{
shared::{
minwindef::DWORD,
ntdef::{ULONG, VOID},
winerror, ws2def,
ws2def::{SOCKADDR, SOCKADDR_IN},
ws2ipdef::SOCKADDR_IN6,
},
um::{
heapapi::{GetProcessHeap, HeapAlloc, HeapFree},
iphlpapi::GetAdaptersAddresses,
iptypes::{
GAA_FLAG_INCLUDE_PREFIX, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST,
IP_ADAPTER_ADDRESSES, IP_ADAPTER_ANYCAST_ADDRESS, IP_ADAPTER_UNICAST_ADDRESS,
},
},
};
#[cfg(windows)]
const INITIAL_ALLOC_SIZE: ULONG = 15000;
#[cfg(windows)]
const MAX_TRIES: usize = 5;
macro_rules! fail_os_err {
() => {
Err(Error::IoError(std::io::Error::last_os_error()))
};
}
#[cfg(unix)]
fn get_addr_for_ifa_unix(addr: libc::ifaddrs, ip_type: Option<IpType>) -> Result<IpAddr> {
let sockaddr = addr.ifa_addr;
assert!(!sockaddr.is_null());
let family = libc::c_int::from(unsafe { *sockaddr }.sa_family);
if let Some(ip_type) = ip_type {
if (ip_type == IpType::Ipv4 && family != libc::AF_INET)
|| (ip_type == IpType::Ipv6 && family != libc::AF_INET6)
{
trace!("Short-circuiting NoAddress: addr family does not match requested type");
return Err(Error::NoAddress);
}
} else if family != libc::AF_INET && family != libc::AF_INET6 {
trace!("Short-circuiting NoAddress: family type {family:?} not address");
return Err(Error::NoAddress);
}
let socklen: libc::socklen_t = match family {
libc::AF_INET => size_of::<libc::sockaddr_in>(),
libc::AF_INET6 => size_of::<libc::sockaddr_in6>(),
_ => unreachable!(),
}
.try_into()
.unwrap_or_else(|_| unreachable!());
{
const MAXHOST: usize = libc::NI_MAXHOST as usize;
let mut host: [libc::c_char; MAXHOST] = [0; MAXHOST];
if unsafe {
libc::getnameinfo(
sockaddr,
socklen,
host.as_mut_ptr(),
libc::NI_MAXHOST,
ptr::null_mut(),
0,
libc::NI_NUMERICHOST,
)
} == 0
{
let address = unsafe { CStr::from_ptr(host.as_ptr()).to_bytes() };
let address = unsafe { std::str::from_utf8_unchecked(address) };
Ok(match family {
libc::AF_INET => IpAddr::V4(Ipv4Addr::from_str(address)?),
libc::AF_INET6 => IpAddr::V6(Ipv6Addr::from_str(address)?),
_ => unreachable!(),
})
} else {
fail_os_err!()
}
}
}
#[cfg(unix)]
pub fn get_iface_addrs(ip_type: Option<IpType>, iface_name: Option<&str>) -> Result<Vec<IpAddr>> {
let mut result: Vec<IpAddr> = Vec::new();
let mut save_addrs: *mut libc::ifaddrs = unsafe { mem::zeroed() };
if unsafe { libc::getifaddrs(&mut save_addrs) } != 0 {
return fail_os_err!();
}
let mut addrs = save_addrs;
while !addrs.is_null() {
let addr = unsafe { *addrs };
let ifa_name = unsafe { CStr::from_ptr(addr.ifa_name).to_bytes() };
let ifa_name = unsafe { std::str::from_utf8_unchecked(ifa_name) };
trace!("Got interface {ifa_name:?}");
let address = iface_name.map_or_else(
|| get_addr_for_ifa_unix(addr, ip_type),
|expected_ifa_name| {
if ifa_name == expected_ifa_name {
get_addr_for_ifa_unix(addr, ip_type)
} else {
Err(Error::NoAddress)
}
},
);
if let Ok(address) = address {
trace!(
"Found good addresses of type {ip_type:?} for interface {iface_name:?}: {address:?}"
);
result.push(address);
}
addrs = addr.ifa_next;
}
unsafe { libc::freeifaddrs(save_addrs) };
if result.is_empty() {
debug!("No address becase none of the interfaces has a matching one");
Err(Error::NoAddress)
} else {
Ok(result)
}
}
#[cfg(windows)]
unsafe fn sockaddr_to_ipaddr(raw_addr: *mut SOCKADDR) -> IpAddr {
if i32::from(unsafe { *raw_addr }.sa_family) == ws2def::AF_INET {
#[allow(clippy::cast_ptr_alignment)]
let saddr_in = raw_addr.cast::<SOCKADDR_IN>();
let saddr_in_addr = unsafe { (*saddr_in).sin_addr.S_un.S_addr() };
IpAddr::V4(Ipv4Addr::from(*saddr_in_addr))
} else {
#[allow(clippy::cast_ptr_alignment)]
let saddr_in = raw_addr.cast::<SOCKADDR_IN6>();
let saddr_in_addr = unsafe { (*saddr_in).sin6_addr.u.Byte() };
IpAddr::V6(Ipv6Addr::from(*saddr_in_addr))
}
}
#[cfg(windows)]
unsafe fn extract_addresses(adapter: *mut IP_ADAPTER_ADDRESSES) -> Vec<IpAddr> {
let mut addresses: Vec<IpAddr> = Vec::new();
let mut cur_unicast: *mut IP_ADAPTER_UNICAST_ADDRESS = unsafe { *adapter }.FirstUnicastAddress;
while !cur_unicast.is_null() {
let raw_addr = unsafe { *cur_unicast }.Address.lpSockaddr;
assert!(!raw_addr.is_null());
let ipaddr = unsafe { sockaddr_to_ipaddr(raw_addr) };
debug!(
"Found good unicast address on adapter {:?}: {:?}",
unsafe { *adapter }.FriendlyName,
ipaddr
);
addresses.push(ipaddr);
cur_unicast = unsafe { *cur_unicast }.Next;
}
let mut cur_anycast: *mut IP_ADAPTER_ANYCAST_ADDRESS = unsafe { *adapter }.FirstAnycastAddress;
while !cur_anycast.is_null() {
let raw_addr = unsafe { *cur_anycast }.Address.lpSockaddr;
assert!(!raw_addr.is_null());
let ipaddr = unsafe { sockaddr_to_ipaddr(raw_addr) };
debug!(
"Found good anycast address on adapter {:?}: {:?}",
unsafe { *adapter }.FriendlyName,
ipaddr
);
addresses.push(ipaddr);
cur_anycast = unsafe { *cur_anycast }.Next;
}
addresses
}
#[cfg(windows)]
pub fn get_iface_addrs(ip_type: Option<IpType>, iface_name: Option<&str>) -> Result<Vec<IpAddr>> {
let family: u32 = match ip_type {
Some(IpType::Ipv4) => ws2def::AF_INET,
Some(IpType::Ipv6) => ws2def::AF_INET6,
None => ws2def::AF_UNSPEC,
}
.try_into()
.unwrap_or_else(|_| unreachable!());
let flags: ULONG = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST;
let mut allocated_size: ULONG = INITIAL_ALLOC_SIZE;
let mut adapter_addresses: *mut IP_ADAPTER_ADDRESSES = unsafe { mem::zeroed() };
let mut return_value: DWORD = 0;
for trial in 0..MAX_TRIES {
adapter_addresses = unsafe { HeapAlloc(GetProcessHeap(), 0, allocated_size as usize) }
.cast::<IP_ADAPTER_ADDRESSES>();
if adapter_addresses.is_null() {
error!("Raw heap allocation failed");
return fail_os_err!();
}
return_value = unsafe {
GetAdaptersAddresses(
family,
flags,
ptr::null_mut(),
adapter_addresses,
&mut allocated_size,
)
};
debug!(
"GetAdaptersAddresses returned {:?} on the {}th trial",
return_value, trial
);
if return_value == winerror::ERROR_BUFFER_OVERFLOW {
unsafe { HeapFree(GetProcessHeap(), 0, adapter_addresses.cast::<VOID>()) };
} else {
break;
}
}
let result = if return_value == winerror::NO_ERROR {
let mut addresses: Vec<IpAddr> = Vec::new();
let mut curr_adapter = adapter_addresses;
while !curr_adapter.is_null() {
let adapter_name = unsafe { *curr_adapter }.FriendlyName as *const libc::c_char;
let adapter_name = unsafe { CStr::from_ptr(adapter_name).to_bytes() };
let adapter_name = unsafe { std::str::from_utf8_unchecked(adapter_name) };
trace!("Examining adpater {:?}", adapter_name);
if let Some(expected_adapter_name) = iface_name {
if adapter_name == expected_adapter_name {
let mut addrs = unsafe { extract_addresses(curr_adapter) };
addresses.append(&mut addrs);
}
} else {
let mut addrs = unsafe { extract_addresses(curr_adapter) };
addresses.append(&mut addrs);
}
curr_adapter = unsafe { *curr_adapter }.Next;
}
if addresses.is_empty() {
debug!("No address becase none of the adapters has a matching one");
Err(Error::NoAddress)
} else {
Ok(addresses)
}
} else {
Err(Error::IoError(std::io::Error::from_raw_os_error(
return_value.try_into().unwrap_or_else(|_| unreachable!()),
)))
};
unsafe {
HeapFree(GetProcessHeap(), 0, adapter_addresses.cast::<VOID>());
}
result
}
#[cfg(test)]
mod test {
use super::get_iface_addrs;
use crate::Error;
use crate::IpType;
#[test]
fn test_get_iface_addrs_ipv4() {
match get_iface_addrs(Some(IpType::Ipv4), None) {
Ok(addresses) => {
assert!(!addresses.is_empty(), "Addresses should not be empty");
for address in &addresses {
assert!(address.is_ipv4(), "Address not IPv4: {address:?}");
}
}
Err(error) => {
assert!(
matches!(error, Error::NoAddress),
"get_iface_addrs failed because of reasons other than NoAddress: {error:?}"
);
}
}
}
#[test]
fn test_get_iface_addrs_ipv6() {
match get_iface_addrs(Some(IpType::Ipv6), None) {
Ok(addresses) => {
assert!(!addresses.is_empty(), "Addresses should not be empty");
for address in &addresses {
assert!(address.is_ipv6(), "Address not IPv6: {address:?}");
}
}
Err(error) => {
assert!(
matches!(error, Error::NoAddress),
"get_iface_addrs failed because of reasons other than NoAddress: {error:?}"
);
}
}
}
#[test]
fn test_get_iface_addrs_any() {
match get_iface_addrs(None, None) {
Ok(addresses) => {
assert!(!addresses.is_empty(), "Addresses should not be empty");
}
Err(error) => {
assert!(
matches!(error, Error::NoAddress),
"get_iface_addrs failed because of reasons other than NoAddress: {error:?}"
);
assert!(
matches!(
get_iface_addrs(Some(IpType::Ipv6), None),
Err(Error::NoAddress)
) || matches!(
get_iface_addrs(Some(IpType::Ipv4), None),
Err(Error::NoAddress)
),
"Individual get_iface_addrs succeeded but generic one didn't"
);
}
}
}
}