Skip to content

Commit

Permalink
socket api
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan561 committed Nov 23, 2023
1 parent a96a31e commit 8835561
Show file tree
Hide file tree
Showing 7 changed files with 5,445 additions and 2,921 deletions.
175 changes: 98 additions & 77 deletions src/ifaddrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,32 @@
//!
//! Uses the Linux and/or BSD specific function `getifaddrs` to query the list
//! of interfaces and their associated addresses.

use cfg_if::cfg_if;
#[cfg(apple_targets)]
use std::convert::TryFrom;
use std::ffi;
use std::iter::Iterator;
use std::marker::PhantomData;
use std::mem;
use std::option::Option;

use crate::net::if_::*;
use crate::sys::socket::{SockaddrLike, SockaddrStorage};
use crate::sys::socket::RawAddr;
use crate::{Errno, Result};

/// Describes a single address for an interface as returned by `getifaddrs`.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct InterfaceAddress {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InterfaceAddress<'a> {
/// Name of the network interface
pub interface_name: String,
/// Flags as from `SIOCGIFFLAGS` ioctl
pub flags: InterfaceFlags,
/// Network address of this interface
pub address: Option<SockaddrStorage>,
pub address: Option<RawAddr<'a>>,
/// Netmask of this interface
pub netmask: Option<SockaddrStorage>,
pub netmask: Option<RawAddr<'a>>,
/// Broadcast address of this interface, if applicable
pub broadcast: Option<SockaddrStorage>,
pub broadcast: Option<RawAddr<'a>>,
/// Point-to-point destination address
pub destination: Option<SockaddrStorage>,
pub destination: Option<RawAddr<'a>>,
}

cfg_if! {
Expand All @@ -44,54 +42,12 @@ cfg_if! {
}
}

/// Workaround a bug in XNU where netmasks will always have the wrong size in
/// the sa_len field due to the kernel ignoring trailing zeroes in the structure
/// when setting the field. See https://github.com/nix-rust/nix/issues/1709#issuecomment-1199304470
///
/// To fix this, we stack-allocate a new sockaddr_storage, zero it out, and
/// memcpy sa_len of the netmask to that new storage. Finally, we reset the
/// ss_len field to sizeof(sockaddr_storage). This is supposedly valid as all
/// members of the sockaddr_storage are "ok" with being zeroed out (there are
/// no pointers).
#[cfg(apple_targets)]
unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option<SockaddrStorage> {
let src_sock = info.ifa_netmask;
if src_sock.is_null() {
return None;
}

let mut dst_sock = mem::MaybeUninit::<libc::sockaddr_storage>::zeroed();

let dst_sock = unsafe {
// memcpy only sa_len bytes, assume the rest is zero
std::ptr::copy_nonoverlapping(
src_sock as *const u8,
dst_sock.as_mut_ptr().cast(),
(*src_sock).sa_len.into(),
);

// Initialize ss_len to sizeof(libc::sockaddr_storage).
(*dst_sock.as_mut_ptr()).ss_len =
u8::try_from(mem::size_of::<libc::sockaddr_storage>()).unwrap();
dst_sock.assume_init()
};

let dst_sock_ptr =
&dst_sock as *const libc::sockaddr_storage as *const libc::sockaddr;

unsafe { SockaddrStorage::from_raw(dst_sock_ptr, None) }
}

impl InterfaceAddress {
impl<'a> InterfaceAddress<'a> {
/// Create an `InterfaceAddress` from the libc struct.
fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress {
let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) };
let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) };
#[cfg(apple_targets)]
let netmask = unsafe { workaround_xnu_bug(info) };
#[cfg(not(apple_targets))]
let netmask =
unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) };
let address = unsafe { RawAddr::new(&*info.ifa_addr) };
let netmask = unsafe { RawAddr::new(&*info.ifa_netmask) };
let mut addr = InterfaceAddress {
interface_name: ifname.to_string_lossy().to_string(),
flags: InterfaceFlags::from_bits_truncate(info.ifa_flags as i32),
Expand All @@ -103,9 +59,9 @@ impl InterfaceAddress {

let ifu = get_ifu_from_sockaddr(info);
if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) {
addr.destination = unsafe { SockaddrStorage::from_raw(ifu, None) };
addr.destination = unsafe { RawAddr::new(&*ifu) };
} else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) {
addr.broadcast = unsafe { SockaddrStorage::from_raw(ifu, None) };
addr.broadcast = unsafe { RawAddr::new(&*ifu) };
}

addr
Expand All @@ -114,23 +70,43 @@ impl InterfaceAddress {

/// Holds the results of `getifaddrs`.
///
/// Use the function `getifaddrs` to create this Iterator. Note that the
/// actual list of interfaces can be iterated once and will be freed as
/// soon as the Iterator goes out of scope.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct InterfaceAddressIterator {
/// Use the function `getifaddrs` to create this struct and [`Self::iter`]
/// to create the iterator. Note that the actual list of interfaces can be
/// iterated once and will be freed as soon as the Iterator goes out of scope.
#[derive(Debug)]
pub struct InterfaceAddresses {
base: *mut libc::ifaddrs,
next: *mut libc::ifaddrs,
}

impl Drop for InterfaceAddressIterator {
impl InterfaceAddresses {
/// Create an iterator over the list of interfaces.
pub fn iter(&self) -> InterfaceAddressIterator<'_> {
InterfaceAddressIterator {
next: self.base,
_a: PhantomData,
}
}
}

impl Drop for InterfaceAddresses {
fn drop(&mut self) {
unsafe { libc::freeifaddrs(self.base) };
}
}

impl Iterator for InterfaceAddressIterator {
type Item = InterfaceAddress;
/// Holds the results of `getifaddrs`.
///
/// Use the function `getifaddrs` to create this Iterator. Note that the
/// actual list of interfaces can be iterated once and will be freed as
/// soon as the Iterator goes out of scope.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct InterfaceAddressIterator<'a> {
next: *mut libc::ifaddrs,
_a: PhantomData<&'a ()>,
}

impl<'a> Iterator for InterfaceAddressIterator<'a> {
type Item = InterfaceAddress<'a>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
match unsafe { self.next.as_ref() } {
Some(ifaddr) => {
Expand All @@ -154,7 +130,7 @@ impl Iterator for InterfaceAddressIterator {
/// # Example
/// ```
/// let addrs = nix::ifaddrs::getifaddrs().unwrap();
/// for ifaddr in addrs {
/// for ifaddr in addrs.iter() {
/// match ifaddr.address {
/// Some(address) => {
/// println!("interface {} address {}",
Expand All @@ -167,21 +143,23 @@ impl Iterator for InterfaceAddressIterator {
/// }
/// }
/// ```
pub fn getifaddrs() -> Result<InterfaceAddressIterator> {
pub fn getifaddrs() -> Result<InterfaceAddresses> {
let mut addrs = mem::MaybeUninit::<*mut libc::ifaddrs>::uninit();
unsafe {
Errno::result(libc::getifaddrs(addrs.as_mut_ptr())).map(|_| {
InterfaceAddressIterator {
InterfaceAddresses {
base: addrs.assume_init(),
next: addrs.assume_init(),
}
})
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use super::*;
use crate::sys::socket::{AddressFamily, Ipv4Address};

// Only checks if `getifaddrs` can be invoked without panicking.
#[test]
Expand All @@ -194,22 +172,65 @@ mod tests {
#[test]
fn test_getifaddrs_netmask_correct() {
let addrs = getifaddrs().unwrap();
for iface in addrs {
for iface in addrs.iter() {
let sock = if let Some(sock) = iface.netmask {
sock
} else {
continue;
};
if sock.family() == Some(crate::sys::socket::AddressFamily::Inet) {
let _ = sock.as_sockaddr_in().unwrap();
if sock.family() == AddressFamily::INET {
let _ = sock.to_ipv4().unwrap();
return;
} else if sock.family()
== Some(crate::sys::socket::AddressFamily::Inet6)
{
let _ = sock.as_sockaddr_in6().unwrap();
} else if sock.family() == AddressFamily::INET6 {
let _ = sock.to_ipv6().unwrap();
return;
}
}
panic!("No address?");
}

#[test]
fn test_get_ifaddrs_netmasks_eq() {
let mut netmasks = HashMap::new();

let ifs = getifaddrs().unwrap();

for ifa in ifs.iter() {
let Some(netmask) =
ifa.netmask.filter(|n| n.family() == AddressFamily::INET)
else {
continue;
};

let ipv4 = *netmask.to_ipv4().unwrap();

let [a, b, c, d] = ipv4.ip().to_be_bytes();

let x = Ipv4Address::new(a, b, c, d, ipv4.port());

assert_eq!(ipv4, x);

netmasks.insert(
ifa.interface_name.clone(),
*netmask.to_ipv4().unwrap(),
);
}

drop(ifs);

let ifs = getifaddrs().unwrap();

for ifa in ifs.iter() {
let Some(netmask) =
ifa.netmask.filter(|n| n.family() == AddressFamily::INET)
else {
continue;
};

assert_eq!(
netmasks.get(&ifa.interface_name).unwrap(),
netmask.to_ipv4().unwrap(),
);
}
}
}
2 changes: 1 addition & 1 deletion src/sys/ptrace/bsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ unsafe fn ptrace_other(
addr,
data,
))
.map(|_| 0)
.map(|_| 0)
}
}

Expand Down
Loading

0 comments on commit 8835561

Please sign in to comment.