diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2017-12-04 05:13:51 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2017-12-04 05:13:51 +0000 |
commit | 3871586a9bcff84eea6dd05f229fbe6ddb3f2bdd (patch) | |
tree | b818216ed82cf901884e80ea0eaf627ac9a423af | |
parent | 86ebf7b0eac4cd0d092b816060042c55ca8871c5 (diff) | |
parent | 985ea01aa84811378eede881dad6f2f9020aa5d1 (diff) | |
download | nix-3871586a9bcff84eea6dd05f229fbe6ddb3f2bdd.zip |
Merge #667
667: Add support for getifaddrs. r=asomers a=mwanner
Here's a first attempt at a rustian interface for `getifaddrs`, please review.
Changes for the changelog:
- Added `nix::ifaddrs::{getifaddrs, InterfaceAddressIterator,
InterfaceAddress and InterfaceFlags}`
Closes #650.
Closes #764.
-rw-r--r-- | src/ifaddrs.rs | 153 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/net/if_.rs | 241 | ||||
-rw-r--r-- | src/sys/socket/addr.rs | 48 |
4 files changed, 451 insertions, 0 deletions
diff --git a/src/ifaddrs.rs b/src/ifaddrs.rs new file mode 100644 index 00000000..2b3024b5 --- /dev/null +++ b/src/ifaddrs.rs @@ -0,0 +1,153 @@ +//! Query network interface addresses +//! +//! Uses the Linux and/or BSD specific function `getifaddrs` to query the list +//! of interfaces and their associated addresses. + +use std::ffi; +use std::fmt; +use std::iter::Iterator; +use std::mem; +use std::option::Option; + +use libc; + +use {Result, Errno}; +use sys::socket::SockAddr; +use net::if_::*; + +/// Describes a single address for an interface as returned by `getifaddrs`. +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct InterfaceAddress { + /// 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<SockAddr>, + /// Netmask of this interface + pub netmask: Option<SockAddr>, + /// Broadcast address of this interface, if applicable + pub broadcast: Option<SockAddr>, + /// Point-to-point destination address + pub destination: Option<SockAddr>, +} + +impl fmt::Debug for InterfaceAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "InterfaceAddress ({:?})", self.interface_name) + } +} + +cfg_if! { + if #[cfg(any(target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] { + fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { + info.ifa_ifu + } + } else { + fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { + info.ifa_dstaddr + } + } +} + +impl InterfaceAddress { + /// 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 { SockAddr::from_libc_sockaddr(info.ifa_addr) }; + let netmask = unsafe { SockAddr::from_libc_sockaddr(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), + address: address, + netmask: netmask, + broadcast: None, + destination: None, + }; + + let ifu = get_ifu_from_sockaddr(info); + if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) { + addr.destination = unsafe { SockAddr::from_libc_sockaddr(ifu) }; + } else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) { + addr.broadcast = unsafe { SockAddr::from_libc_sockaddr(ifu) }; + } + + addr + } +} + +/// 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 { + base: *mut libc::ifaddrs, + next: *mut libc::ifaddrs, +} + +impl Drop for InterfaceAddressIterator { + fn drop(&mut self) { + unsafe { libc::freeifaddrs(self.base) }; + } +} + +impl Iterator for InterfaceAddressIterator { + type Item = InterfaceAddress; + fn next(&mut self) -> Option<<Self as Iterator>::Item> { + match unsafe { self.next.as_ref() } { + Some(ifaddr) => { + self.next = ifaddr.ifa_next; + Some(InterfaceAddress::from_libc_ifaddrs(ifaddr)) + } + None => None, + } + } +} + +/// Get interface addresses using libc's `getifaddrs` +/// +/// Note that the underlying implementation differs between OSes. Only the +/// most common address families are supported by the nix crate (due to +/// lack of time and complexity of testing). The address family is encoded +/// in the specific variant of `SockAddr` returned for the fields `address`, +/// `netmask`, `broadcast`, and `destination`. For any entry not supported, +/// the returned list will contain a `None` entry. +/// +/// # Example +/// ``` +/// let addrs = nix::ifaddrs::getifaddrs().unwrap(); +/// for ifaddr in addrs { +/// match ifaddr.address { +/// Some(address) => { +/// println!("interface {} address {}", +/// ifaddr.interface_name, address); +/// }, +/// None => { +/// println!("interface {} with unsupported address family", +/// ifaddr.interface_name); +/// } +/// } +/// } +/// ``` +pub fn getifaddrs() -> Result<InterfaceAddressIterator> { + let mut addrs: *mut libc::ifaddrs = unsafe { mem::uninitialized() }; + Errno::result(unsafe { libc::getifaddrs(&mut addrs) }).map(|_| { + InterfaceAddressIterator { + base: addrs, + next: addrs, + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Only checks if `getifaddrs` can be invoked without panicking. + #[test] + fn test_getifaddrs() { + let _ = getifaddrs(); + } +} @@ -43,6 +43,15 @@ pub mod poll; pub mod net; +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] +pub mod ifaddrs; + #[cfg(any(target_os = "linux", target_os = "android"))] pub mod sched; diff --git a/src/net/if_.rs b/src/net/if_.rs index 7edfc998..19b1ee73 100644 --- a/src/net/if_.rs +++ b/src/net/if_.rs @@ -17,3 +17,244 @@ pub fn if_nametoindex<P: ?Sized + NixPath>(name: &P) -> Result<c_uint> { Ok(if_index) } } + +libc_bitflags!( + /// Standard interface flags, used by `getifaddrs` + pub struct InterfaceFlags: libc::c_int { + /// Interface is running. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_UP; + /// Valid broadcast address set. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_BROADCAST; + /// Internal debugging flag. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_DEBUG; + /// Interface is a loopback interface. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_LOOPBACK; + /// Interface is a point-to-point link. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_POINTOPOINT; + /// Avoid use of trailers. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + IFF_NOTRAILERS; + /// Interface manages own routes. + #[cfg(any(target_os = "dragonfly"))] + IFF_SMART; + /// Resources allocated. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris"))] + IFF_RUNNING; + /// No arp protocol, L2 destination address not set. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_NOARP; + /// Interface is in promiscuous mode. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_PROMISC; + /// Receive all multicast packets. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_ALLMULTI; + /// Master of a load balancing bundle. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + IFF_MASTER; + /// transmission in progress, tx hardware queue is full + #[cfg(any(target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + IFF_OACTIVE; + /// Protocol code on board. + #[cfg(target_os = "solaris")] + IFF_INTELLIGENT; + /// Slave of a load balancing bundle. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + IFF_SLAVE; + /// Can't hear own transmissions. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "osx"))] + IFF_SIMPLEX; + /// Supports multicast. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + IFF_MULTICAST; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + IFF_LINK0; + /// Multicast using broadcast. + #[cfg(any(target_os = "solaris"))] + IFF_MULTI_BCAST; + /// Is able to select media type via ifmap. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + IFF_PORTSEL; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + IFF_LINK1; + /// Non-unique address. + #[cfg(any(target_os = "solaris"))] + IFF_UNNUMBERED; + /// Auto media selection active. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + IFF_AUTOMEDIA; + /// Per link layer defined bit. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "ios"))] + IFF_LINK2; + /// Use alternate physical connection. + #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "ios"))] + IFF_ALTPHYS; + /// DHCP controlls interface. + #[cfg(any(target_os = "solaris"))] + IFF_DHCPRUNNING; + /// The addresses are lost when the interface goes down. (see + /// [`netdevice(7)`](http://man7.org/linux/man-pages/man7/netdevice.7.html)) + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + IFF_DYNAMIC; + /// Do not advertise. + #[cfg(any(target_os = "solaris"))] + IFF_PRIVATE; + /// Driver signals L1 up. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + IFF_LOWER_UP; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + IFF_POLLING_COMPAT; + /// Unconfigurable using ioctl(2). + #[cfg(any(target_os = "freebsd"))] + IFF_CANTCONFIG; + /// Do not transmit packets. + #[cfg(any(target_os = "solaris"))] + IFF_NOXMIT; + /// Driver signals dormant. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + IFF_DORMANT; + /// User-requested promisc mode. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + IFF_PPROMISC; + /// Just on-link subnet. + #[cfg(any(target_os = "solaris"))] + IFF_NOLOCAL; + /// Echo sent packets. Volatile. + #[cfg(any(target_os = "fuchsia", target_os = "linux"))] + IFF_ECHO; + /// User-requested monitor mode. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + IFF_MONITOR; + /// Address is deprecated. + #[cfg(any(target_os = "solaris"))] + IFF_DEPRECATED; + /// Static ARP. + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] + IFF_STATICARP; + /// Address from stateless addrconf. + #[cfg(any(target_os = "solaris"))] + IFF_ADDRCONF; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + IFF_NPOLLING; + /// Router on interface. + #[cfg(any(target_os = "solaris"))] + IFF_ROUTER; + /// Interface is in polling mode. + #[cfg(any(target_os = "dragonfly"))] + IFF_IDIRECT; + /// Interface is winding down + #[cfg(any(target_os = "freebsd"))] + IFF_DYING; + /// No NUD on interface. + #[cfg(any(target_os = "solaris"))] + IFF_NONUD; + /// Interface is being renamed + #[cfg(any(target_os = "freebsd"))] + IFF_RENAMING; + /// Anycast address. + #[cfg(any(target_os = "solaris"))] + IFF_ANYCAST; + /// Don't exchange routing info. + #[cfg(any(target_os = "solaris"))] + IFF_NORTEXCH; + /// IPv4 interface. + #[cfg(any(target_os = "solaris"))] + IFF_IPV4; + /// IPv6 interface. + #[cfg(any(target_os = "solaris"))] + IFF_IPV6; + /// in.mpathd test address + #[cfg(any(target_os = "solaris"))] + IFF_NOFAILOVER; + /// Interface has failed + #[cfg(any(target_os = "solaris"))] + IFF_FAILED; + /// Interface is a hot-spare + #[cfg(any(target_os = "solaris"))] + IFF_STANDBY; + /// Functioning but not used + #[cfg(any(target_os = "solaris"))] + IFF_INACTIVE; + /// Interface is offline + #[cfg(any(target_os = "solaris"))] + IFF_OFFLINE; + #[cfg(any(target_os = "solaris"))] + IFF_COS_ENABLED; + /// Prefer as source addr. + #[cfg(any(target_os = "solaris"))] + IFF_PREFERRED; + /// RFC3041 + #[cfg(any(target_os = "solaris"))] + IFF_TEMPORARY; + /// MTU set with SIOCSLIFMTU + #[cfg(any(target_os = "solaris"))] + IFF_FIXEDMTU; + /// Cannot send / receive packets + #[cfg(any(target_os = "solaris"))] + IFF_VIRTUAL; + /// Local address in use + #[cfg(any(target_os = "solaris"))] + IFF_DUPLICATE; + /// IPMP IP interface + #[cfg(any(target_os = "solaris"))] + IFF_IPMP; + } +); diff --git a/src/sys/socket/addr.rs b/src/sys/socket/addr.rs index 29832b37..a1f86518 100644 --- a/src/sys/socket/addr.rs +++ b/src/sys/socket/addr.rs @@ -201,6 +201,26 @@ pub enum AddressFamily { Natm = libc::AF_NATM, } +impl AddressFamily { + /// Create a new `AddressFamily` from an integer value retrieved from `libc`, usually from + /// the `sa_family` field of a `sockaddr`. + /// + /// Currently only supports these address families: Unix, Inet (v4 & v6), Netlink + /// and System. Returns None for unsupported or unknown address families. + pub fn from_i32(family: i32) -> Option<AddressFamily> { + match family { + libc::AF_UNIX => Some(AddressFamily::Unix), + libc::AF_INET => Some(AddressFamily::Inet), + libc::AF_INET6 => Some(AddressFamily::Inet6), + #[cfg(any(target_os = "android", target_os = "linux"))] + libc::AF_NETLINK => Some(AddressFamily::Netlink), + #[cfg(any(target_os = "macos", target_os = "macos"))] + libc::AF_SYSTEM => Some(AddressFamily::System), + _ => None + } + } +} + #[derive(Copy)] pub enum InetAddr { V4(libc::sockaddr_in), @@ -707,6 +727,34 @@ impl SockAddr { format!("{}", self) } + /// Creates a `SockAddr` struct from libc's sockaddr. + /// + /// Supports only the following address families: Unix, Inet (v4 & v6), Netlink and System. + /// Returns None for unsupported families. + pub unsafe fn from_libc_sockaddr(addr: *const libc::sockaddr) -> Option<SockAddr> { + if addr.is_null() { + None + } else { + match AddressFamily::from_i32((*addr).sa_family as i32) { + Some(AddressFamily::Unix) => None, + Some(AddressFamily::Inet) => Some(SockAddr::Inet( + InetAddr::V4(*(addr as *const libc::sockaddr_in)))), + Some(AddressFamily::Inet6) => Some(SockAddr::Inet( + InetAddr::V6(*(addr as *const libc::sockaddr_in6)))), + #[cfg(any(target_os = "android", target_os = "linux"))] + Some(AddressFamily::Netlink) => Some(SockAddr::Netlink( + NetlinkAddr(*(addr as *const libc::sockaddr_nl)))), + #[cfg(any(target_os = "ios", target_os = "macos"))] + Some(AddressFamily::System) => Some(SockAddr::SysControl( + SysControlAddr(*(addr as *const sys_control::sockaddr_ctl)))), + // Other address families are currently not supported and simply yield a None + // entry instead of a proper conversion to a `SockAddr`. + Some(_) => None, + None => None, + } + } + } + pub unsafe fn as_ffi_pair(&self) -> (&libc::sockaddr, libc::socklen_t) { match *self { SockAddr::Inet(InetAddr::V4(ref addr)) => (mem::transmute(addr), mem::size_of::<libc::sockaddr_in>() as libc::socklen_t), |