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

Add getifaddrs/ifaddrs wrapper #764

Closed
wants to merge 10 commits into from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added socket option variant that enables the timestamp socket
control message: `nix::sys::socket::sockopt::ReceiveTimestamp`
([#663](https://github.com/nix-rust/nix/pull/663))
- Added API for `getifaddrs` with associated wrapper type.
([#764](https://github.com/nix-rust/nix/pull/764))

### Changed
- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))
Expand Down
50 changes: 50 additions & 0 deletions src/net/ifaddrs/iff_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use libc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine these into use libc::{self, c_uint};

use libc::c_uint;

libc_bitflags! {
pub struct IffFlags: c_uint {
/// Interface is running.
IFF_UP as c_uint;
/// Valid broadcast address set.
IFF_BROADCAST as c_uint;
/// Internal debugging flag.
IFF_DEBUG as c_uint;
/// Interface is a loopback interface.
IFF_LOOPBACK as c_uint;
/// Interface is a point-to-point link.
IFF_POINTOPOINT as c_uint;
/// Resources allocated.
IFF_NOTRAILERS as c_uint;
/// No arp protocol, L2 destination address not set.
IFF_RUNNING as c_uint;
/// Interface is in promiscuous mode.
IFF_NOARP as c_uint;
/// Avoid use of trailers.
IFF_PROMISC as c_uint;
/// Receive all multicast packets.
IFF_ALLMULTI as c_uint;
/// Master of a load balancing bundle.
IFF_MASTER as c_uint;
/// Slave of a load balancing bundle.
IFF_SLAVE as c_uint;
/// Supports multicast
IFF_MULTICAST as c_uint;
/// Is able to select media type via ifmap.
IFF_PORTSEL as c_uint;
/// Auto media selection active.
IFF_AUTOMEDIA as c_uint;
/// The addresses are lost when the interface goes down.
IFF_DYNAMIC as c_uint;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this blank line.

// These flags are available on modern Linuxes
#[cfg(any(target_os = "linux", target_os = "android"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OSes here should be listed alphabetically.

/// Driver signals L1 up (since Linux 2.6.17)
IFF_LOWER_UP as c_uint;
#[cfg(any(target_os = "linux", target_os = "android"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

/// Driver signals dormant (since Linux 2.6.17)
IFF_DORMANT as c_uint;
#[cfg(any(target_os = "linux", target_os = "android"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

/// Echo sent packets (since Linux 2.6.25)
IFF_ECHO as c_uint;
}
}
273 changes: 273 additions & 0 deletions src/net/ifaddrs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
//! `ifaddrs` provides a safe interface for the system's network interface data.
//!
//! The `InterfaceAddrs` struct provides access to the system's network
//! interface data. You can either iterate over it or consume it and convert
//! it into an `InterfaceMap` (a `HashMap<String, Vec<InterfaceAddr>>`) for
//! more convenient access by interface name.
//!
//! # Examples
//!
//! You can access the basic information of the system in a few lines.
//! The following program prints all the known addresses.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get a colon at the end of this line.

//!
//! ```
//! use nix::net::ifaddrs::InterfaceAddrs;
//!
//! let addrs = InterfaceAddrs::getifaddrs()
//! .expect("Failed to enumerate network interfaces.");
//!
//! for addr in addrs {
//! println!("{}: {:?}", addr.name, addr.address);
//! }
//! ```
//!
//! The `IffFlags` struct provides access to info about the
//! state of an interface. This program prints the addresses of only
//! interfaces which are up.
//!
//! ```
//! use nix::net::ifaddrs::{InterfaceAddrs, iff_flags};
//!
//! let addrs = InterfaceAddrs::getifaddrs()
//! .expect("Failed to eunmerate network interfaces.");
//!
//! for addr in addrs {
//! if addr.flags.contains(iff_flags::IFF_UP) {
//! println!("{}: {:?}", addr.name, addr.address);
//! }
//! }
//! ```
//!
//! You can convert the `InterfaceAddrs` struct into a `HashMap` easily.
//! `InterfaceMap` is an alias for `HashMap<String, Vec<InterfaceAddr>>` for
//! easier reference.
//!
//! ```
//! use nix::net::ifaddrs::{InterfaceAddrs, InterfaceAddr, InterfaceMap};
//! use std::collections::HashMap;
//!
//! let interfaces: InterfaceMap =
//! InterfaceAddrs::getifaddrs()
//! .expect("Failed to enumerate network interfaces.")
//! .into(); // Convert to a hash map
//!
//! // Print all the addresses of the loopback interface
//! if let Some(addrs) = interfaces.get("lo") {
//! println!("Loopback addresses:");
//! for addr in addrs {
//! println!("\t{:?}", addr);
//! }
//! }
//!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line.

//! ```
//!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this one.


use libc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grouping here should be std, then third-party, then local library modules each separated by a blank line. Additionally imports should be alphabetized.

use std::ptr::null_mut;
use std::ffi::CStr;
use std::collections::HashMap;
use errno::{Errno, errno};
use Error;
use Result;

pub mod iff_flags;
use self::iff_flags::IffFlags;

mod sockaddr;
use self::sockaddr::{IfAddrValue, sockaddr_to_ifaddrvalue};

mod tests;

pub type InterfaceMap<'a> = HashMap<String, Vec<InterfaceAddr<'a>>>;

/// Represents a handle into the operating system's knowledge about network
/// interfaces present on the system. Allows the user to iterate over
/// interface configurations.
pub struct InterfaceAddrs<'a> {
inner: *mut libc::ifaddrs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even those these inner members are not public, it's useful for developers to get some docs on these. Can you provide doc comments for each element here?

current: Option<&'a libc::ifaddrs>,
do_free: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like do_free only exists to support your test code. You can eliminate it by using mem::forget in the test code to prevent drop from being run.

}

impl<'a> InterfaceAddrs<'a> {
/// Creates an `InterfaceAddrs` from a raw pointer, without calling into
/// the `libc`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the "the" here.

///
/// The destructor will not attempt to free memory on an InterfaceAddrs
/// created in this way.
///
/// # Unsafety
/// The caller is responsible for making sure the given pointer is not
/// in invalid memory.
///
/// # Errors
/// `Err(())` will be returned if `p` was void.
pub unsafe fn from_raw(p: *mut libc::ifaddrs) -> ::std::result::Result<Self, ()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function is only used by test code, it should not be pub

match p.as_ref() {
Some(r) => Ok(Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails to compile. You should change Self to InterfaceAddrs

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It compiles for me? Something must be screwy with my Rustup, I'll change it. Thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You fixed the wrong line. Just check the Travis build log to see the error I'm talking about. If it works for you, that's probably because you're using a newer version of Rust than Travis. We're using 1.13.0 in Travis.

inner: p,
current: Some(r),
do_free: false,
}),
None => Err(()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't an Invalid Argument error be provided here instead which would be a bit clearer?

}
}

/// Produce an `InterfaceAddrs` from the system's information.
pub fn getifaddrs() -> Result<Self> {
let mut p = null_mut();

unsafe {
libc::getifaddrs(&mut p);
}

// UNSAFETY: *mut -> &'static mut. This is known to be either in valid
// memory or null based on the guarantees of getifaddrs()
match unsafe { p.as_ref() } {
Some(r) => Ok(Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you eliminate do_free, this section can turn into a call to from_raw.

inner: p,
current: Some(r),
do_free: true,
}),

None => Err(Error::from(Errno::from_i32(errno()))),
}
}
}

impl<'a> From<InterfaceAddrs<'a>> for HashMap<String, Vec<InterfaceAddr<'a>>> {
/// Collect an `InterfaceAddrs` into a `HashMap<String, InterfaceAddr>`.
fn from(ia: InterfaceAddrs<'a>) -> HashMap<String, Vec<InterfaceAddr<'a>>> {
let mut m = HashMap::new();
for i in ia {
if !m.contains_key(&i.name) {
m.insert(i.name.clone(), Vec::new());
}
// Unwrap here because contains is checked above
m.get_mut(&i.name).unwrap().push(i);
}

m
}
}

impl<'a> Drop for InterfaceAddrs<'a> {
fn drop(&mut self) {
if self.do_free {
// UNSAFETY: Calling libc FFI function which frees previously allocated
// memory.
unsafe {
// Ask the libc to drop free the memory it allocated when
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"to free the memory" I think is what this should read.

// the struct was created.
libc::freeifaddrs(self.inner as *mut libc::ifaddrs);
}
}
}
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line. Should only be 1 between things in a file.

/// Represents the configuration and state of a network interface.
/// Interfaces are uniquely identified by name, and each interface is likely
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a blank line above for the generated docs to look pretty (run rustdoc to see what I'm talking about).

/// to be referred to multiple times, e.g. one for IPv4 and one for IPv6.
#[derive(Debug, Clone)]
pub struct InterfaceAddr<'a> {
/// The name of the interface
pub name: String,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these blank lines from between the elements in this struct.

/// The address assigned to the interface for this protocol.
/// A value of `None` means the libc reported a type of address that
/// `std::net` doesn't understand.
pub address: Option<IfAddrValue<'a>>,

/// The netmasks assigned to the interface for this protocol.
/// A value of `None` means the libc reported a type of address that
/// `std::net` doesn't understand.
pub netmask: Option<IfAddrValue<'a>>,

/// The ifu assigned to the interface for this protocol.
/// A value of `{Broadcast, Destination}Addr(None)` means the libc reported
/// a type of address that `std::net` doesn't understand, while a value of
/// `Neither` means that the interface has neither a valid broadcast address
/// nor a point-to-point destination address.
pub ifu: InterfaceIfu<'a>,

/// Flags regarding the interface's behaviour and state
pub flags: IffFlags,
}

/// Represents the ifu of an interface: either its broadcast address or
/// point-to-point destination address.
#[derive(Debug, Clone)]
pub enum InterfaceIfu<'a> {
BroadcastAddr(Option<IfAddrValue<'a>>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All variants here need doc comments.

DestinationAddr(Option<IfAddrValue<'a>>),
Neither,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You say Neither here but None on line 153. They should be consistent.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that comment on 153 refers to a BroadcastAddr(None) or DestinationAddr(None). I'll make that more clear.

}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the extra blank line here.

impl<'a> Iterator for InterfaceAddrs<'a> {
type Item = InterfaceAddr<'a>;
fn next(&mut self) -> Option<InterfaceAddr<'a>> {
// If the current ifaddrs is None, there are no more ifaddrs to inspect
if self.current.is_none() {
return None;
}

// Workaround for the borrow checker being overzealous
// (without ptr_temp, self.current would technically
// "still be in use" when the loop ends, meaning we
// couldn't advance to the next struct)
let ptr_temp = self.current.clone();
let p = ptr_temp.as_ref().unwrap();

// Get a pointer to the interface's name
let name_ptr = p.ifa_name;
// Check that name_ptr isn't null.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check might be more semantically clear if you used assert! instead of an if here. @asomers any opinion on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, assert! would be better.

if name_ptr.is_null() {
panic!("getifaddrs() gave an ifaddrs struct with a null ifa_name");
}

// UNSAFETY: Constructing CStr from pointer. If this pointer is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the pointer being null is checked above with a panic if it's NULL at this point, it's a compiler error or something's super wrong with the CPU. I think this comment can be cleaned up a big to clarify that. Same for the one below.

// null it's a libc bug; it's checked above.
let name = unsafe { CStr::from_ptr(name_ptr) }
.to_string_lossy()
.into_owned();

// Interpret the flags field into a typed version of those flags
let flags = IffFlags::from_bits_truncate(p.ifa_flags);

// Get IfAddrValue representations of the address and netmask
// UNSAFETY: sockaddr_to_ifaddrvalue requires valid pointer.
let address = unsafe { sockaddr_to_ifaddrvalue(p.ifa_addr) };
// UNSAFETY: sockaddr_to_ifaddrvalue requires valid pointer.
let netmask = unsafe { sockaddr_to_ifaddrvalue(p.ifa_netmask) };

// Figure out which ifu type is needed and create it
let ifu = if flags.contains(iff_flags::IFF_POINTOPOINT) {
// Point to point destination address
// UNSAFETY: sockaddr_to_ifaddrvalue requires valid pointer.
let ifu_addr = unsafe { sockaddr_to_ifaddrvalue(p.ifa_ifu) };
InterfaceIfu::DestinationAddr(ifu_addr)
} else if flags.contains(iff_flags::IFF_BROADCAST) {
// Broadcast address
// UNSAFETY: sockaddr_to_ifaddrvalue requires valid pointer.
let ifu_addr = unsafe { sockaddr_to_ifaddrvalue(p.ifa_ifu) };
InterfaceIfu::BroadcastAddr(ifu_addr)
} else {
InterfaceIfu::Neither
};

// Move along the list to the next ifaddrs struct
// UNSAFETY: *mut -> Option<&'static mut>.
// This is known to be in valid memory or null.
self.current = unsafe { p.ifa_next.as_ref() };

Some(InterfaceAddr {
name: name,
address: address,
netmask: netmask,
ifu: ifu,
flags: flags,
})
}
}
65 changes: 65 additions & 0 deletions src/net/ifaddrs/sockaddr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::net::{Ipv4Addr, Ipv6Addr, IpAddr};
use std::mem::transmute;
use std::fmt;
use libc;

/// Represents the actual data of an address in use by an interface.
#[derive(Clone)]
pub enum IfAddrValue<'a> {
IpAddr(IpAddr),
Other(&'a libc::sockaddr),
}

impl<'a> fmt::Debug for IfAddrValue<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
IfAddrValue::IpAddr(ref addr) => write!(f, "IfAddrValue({:?})", addr),
IfAddrValue::Other(_) => write!(f, "IfAddrValue(<Unknown Address Type>)"),
}
}
}

impl<'a> From<IpAddr> for IfAddrValue<'a> {
fn from(ip: IpAddr) -> IfAddrValue<'a> {
IfAddrValue::IpAddr(ip)
}
}

impl<'a> From<&'a libc::sockaddr> for IfAddrValue<'a> {
fn from(addr: &'a libc::sockaddr) -> IfAddrValue<'a> {
IfAddrValue::Other(addr)
}
}

/// Converts a `libc::sockaddr` into an `Option<IfAddrValue>`.
///
/// It returns `None` if the libc reports a type of address other than
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been chewing on this, and I've concluded that we're doing it wrong. There are two problems:

  1. Nix consumers can't extend this function to handle weird stuff like AF_IPX.
  2. Handling link addresses is too platform specific. That functionality probably doesn't belong in Nix at all.

Both of these problems share a common solution. For unknown address families, sockaddr_to_ifaddrvalue should return Other(&libc::sockaddr) instead of None. That way Nix consumers can do whatever they need to handle AF_LINK or anything else.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that solution a lot, actually. I'll go ahead and implement that.

/// IPv4, or IPv6, or if the given `sockaddr_input` was null.
///
/// # Unsafety
///
/// The caller is responsible for guaranteeing that the provided reference
/// refers to valid memory.
pub unsafe fn sockaddr_to_ifaddrvalue<'a>(
sockaddr_input: *mut libc::sockaddr,
) -> Option<IfAddrValue<'a>> {
if let Some(sa) = sockaddr_input.as_ref() {
// Only IPv4 and IPv6 are supported.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have AF_LINK too.

match sa.sa_family as i32 {
libc::AF_INET => {
let data_v4: &libc::sockaddr_in = transmute(sa);
// Transmuting a u32 into a [u8; 4] because
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be wrapped to the same number of columns as the code. We should be wrapping everything to 99 characters (which is the Rust standard).

// the address is in network byte order.
let s_addr_v4: [u8; 4] = transmute(data_v4.sin_addr.s_addr);
Some(IpAddr::V4(Ipv4Addr::from(s_addr_v4)).into())
}
libc::AF_INET6 => {
let data_v6: &libc::sockaddr_in6 = transmute(sa);
Some(IpAddr::V6(Ipv6Addr::from(data_v6.sin6_addr.s6_addr)).into())
}
_ => Some(sa.into()),
}
} else {
None
}
}
Loading