diff --git a/CHANGELOG.md b/CHANGELOG.md index d8be1905cc..eae0809019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/src/net/ifaddrs/iff_flags.rs b/src/net/ifaddrs/iff_flags.rs new file mode 100644 index 0000000000..90622cc98b --- /dev/null +++ b/src/net/ifaddrs/iff_flags.rs @@ -0,0 +1,50 @@ +use libc; +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; + + // These flags are available on modern Linuxes + #[cfg(any(target_os = "linux", target_os = "android"))] + /// Driver signals L1 up (since Linux 2.6.17) + IFF_LOWER_UP as c_uint; + #[cfg(any(target_os = "linux", target_os = "android"))] + /// Driver signals dormant (since Linux 2.6.17) + IFF_DORMANT as c_uint; + #[cfg(any(target_os = "linux", target_os = "android"))] + /// Echo sent packets (since Linux 2.6.25) + IFF_ECHO as c_uint; + } +} diff --git a/src/net/ifaddrs/mod.rs b/src/net/ifaddrs/mod.rs new file mode 100644 index 0000000000..01613934ce --- /dev/null +++ b/src/net/ifaddrs/mod.rs @@ -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>`) 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. +//! +//! ``` +//! 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>` 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); +//! } +//! } +//! +//! ``` +//! + +use libc; +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>>; + +/// 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, + current: Option<&'a libc::ifaddrs>, + do_free: bool, +} + +impl<'a> InterfaceAddrs<'a> { + /// Creates an `InterfaceAddrs` from a raw pointer, without calling into + /// the `libc`. + /// + /// 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, ()> { + match p.as_ref() { + Some(r) => Ok(Self { + inner: p, + current: Some(r), + do_free: false, + }), + None => Err(()), + } + } + + /// Produce an `InterfaceAddrs` from the system's information. + pub fn getifaddrs() -> Result { + 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 { + inner: p, + current: Some(r), + do_free: true, + }), + + None => Err(Error::from(Errno::from_i32(errno()))), + } + } +} + +impl<'a> From> for HashMap>> { + /// Collect an `InterfaceAddrs` into a `HashMap`. + fn from(ia: InterfaceAddrs<'a>) -> HashMap>> { + 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 + // the struct was created. + libc::freeifaddrs(self.inner as *mut libc::ifaddrs); + } + } + } +} + + +/// Represents the configuration and state of a network interface. +/// Interfaces are uniquely identified by name, and each interface is likely +/// 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, + + /// 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>, + + /// 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>, + + /// 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>), + DestinationAddr(Option>), + Neither, +} + + +impl<'a> Iterator for InterfaceAddrs<'a> { + type Item = InterfaceAddr<'a>; + fn next(&mut self) -> Option> { + // 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. + 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 + // 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, + }) + } +} diff --git a/src/net/ifaddrs/sockaddr.rs b/src/net/ifaddrs/sockaddr.rs new file mode 100644 index 0000000000..48bf18d711 --- /dev/null +++ b/src/net/ifaddrs/sockaddr.rs @@ -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()"), + } + } +} + +impl<'a> From 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`. +/// +/// It returns `None` if the libc reports a type of address other than +/// 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> { + if let Some(sa) = sockaddr_input.as_ref() { + // Only IPv4 and IPv6 are supported. + 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 + // 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 + } +} diff --git a/src/net/ifaddrs/tests.rs b/src/net/ifaddrs/tests.rs new file mode 100644 index 0000000000..6ac4b6d2e8 --- /dev/null +++ b/src/net/ifaddrs/tests.rs @@ -0,0 +1,76 @@ +#![cfg(test)] + +use std::ptr::null_mut; +use std::mem::transmute; + +use std::ffi::CString; + +use libc; +use libc::{ifaddrs, sockaddr, sockaddr_in}; +use libc::in_addr; + +use super::iff_flags::*; +use super::InterfaceAddrs; +use super::InterfaceMap; + +// Utility function to turn some bytes into a u32 in the right order because +// endianness is a pain +fn convert_v4_addr(b0: u8, b1: u8, b2: u8, b3: u8) -> in_addr { + unsafe { in_addr { s_addr: transmute([b0, b1, b2, b3]) } } +} + +// Utility function to turn some bytes into a sockaddr with the v4 addr type +fn sockaddr_for_addr(b0: u8, b1: u8, b2: u8, b3: u8) -> sockaddr { + let sin = sockaddr_in { + sin_family: libc::AF_INET as u16, + sin_port: 0, + sin_addr: convert_v4_addr(b0, b1, b2, b3), + sin_zero: [0; 8], + }; + + unsafe { transmute(sin) } +} + +#[test] +fn test_ifaddrs() { + + // Create an "external" interface + let mut ext_address = sockaddr_for_addr(192, 168, 0, 1); + let mut ext_netmask = sockaddr_for_addr(255, 255, 255, 0); + let mut ext_brdcast = sockaddr_for_addr(192, 168, 0, 255); + + let mut test_ext_ipv4 = ifaddrs { + ifa_next: null_mut(), + ifa_name: CString::new("test_ext").unwrap().into_raw(), + ifa_flags: (IFF_BROADCAST | IFF_UP | IFF_RUNNING).bits(), + ifa_addr: &mut ext_address, + ifa_netmask: &mut ext_netmask, + ifa_ifu: &mut ext_brdcast, + ifa_data: null_mut(), + }; + + + // Create a "loopback" interface, and link it to the "external" one + let mut lo_address = sockaddr_for_addr(127, 0, 0, 1); + let mut lo_netmask = sockaddr_for_addr(255, 255, 255, 0); + let mut lo_brdcast = sockaddr_for_addr(127, 0, 0, 255); + + let mut test_lo_ipv4 = ifaddrs { + ifa_next: &mut test_ext_ipv4, + ifa_name: CString::new("test_lo").unwrap().into_raw(), + ifa_flags: (IFF_BROADCAST | IFF_LOOPBACK | IFF_UP | IFF_RUNNING).bits(), + ifa_addr: &mut lo_address, + ifa_netmask: &mut lo_netmask, + ifa_ifu: &mut lo_brdcast, + ifa_data: null_mut(), + }; + + let created = unsafe { InterfaceAddrs::from_raw(&mut test_lo_ipv4) }.unwrap(); + + let hm: InterfaceMap = created.into(); + + assert_eq!(hm.len(), 2, "Expected 2 interfaces, found {}.", hm.len()); + + assert!(hm.contains_key("test_lo")); + assert!(hm.contains_key("test_ext")); +} diff --git a/src/net/mod.rs b/src/net/mod.rs index eca5cfbb7a..e5fe4ebcd9 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,3 +1,5 @@ // To avoid clashing with the keyword "if", we use "if_" as the module name. // The original header is called "net/if.h". pub mod if_; + +pub mod ifaddrs;