#![deny(missing_docs)]
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate lazy_static;
extern crate libc;
extern crate nix;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt;
use std::mem;
use std::net;
use std::ptr;
use libc::c_int;
use libc::{AF_INET, SOCK_DGRAM};
use libc::{close, ioctl, socket};
#[cfg(target_os = "linux")]
use nix::sys::socket;
pub use error::InterfacesError;
pub use flags::InterfaceFlags;
mod constants;
mod error;
mod ffi;
pub mod flags;
pub type Result<T> = ::std::result::Result<T, InterfacesError>;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Kind {
Ipv4,
Ipv6,
Link,
Unknown(i32),
Packet,
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Kind::Ipv4 => write!(f, "IPv4"),
Kind::Ipv6 => write!(f, "IPv6"),
Kind::Link => write!(f, "Link"),
Kind::Unknown(v) => write!(f, "Unknown({})", v),
Kind::Packet => write!(f, "Packet"),
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum NextHop {
Broadcast(net::SocketAddr),
Destination(net::SocketAddr),
}
impl fmt::Display for NextHop {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
NextHop::Broadcast(ref addr) => write!(f, "Broadcast({})", addr),
NextHop::Destination(ref addr) => write!(f, "Destination({})", addr),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Address {
pub kind: Kind,
pub addr: Option<net::SocketAddr>,
pub mask: Option<net::SocketAddr>,
pub hop: Option<NextHop>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct HardwareAddr([u8; 6]);
impl HardwareAddr {
pub fn zero() -> HardwareAddr {
HardwareAddr([0; 6])
}
pub fn as_string(&self) -> String {
let &HardwareAddr(ref arr) = self;
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
arr[0], arr[1], arr[2], arr[3], arr[4], arr[5],
)
}
pub fn as_bare_string(&self) -> String {
let &HardwareAddr(ref arr) = self;
format!(
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
arr[0], arr[1], arr[2], arr[3], arr[4], arr[5],
)
}
pub fn as_bytes(&self) -> &[u8] {
let &HardwareAddr(ref arr) = self;
arr
}
}
impl fmt::Display for HardwareAddr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_string())
}
}
struct IfAddrIterator {
orig: *mut ffi::ifaddrs,
ifap: *mut ffi::ifaddrs,
}
impl IfAddrIterator {
fn new() -> Result<IfAddrIterator> {
let mut ifap: *mut ffi::ifaddrs = unsafe { mem::zeroed() };
if unsafe { ffi::getifaddrs(&mut ifap as *mut _) } != 0 {
return Err(InterfacesError::last_os_error());
}
Ok(IfAddrIterator {
orig: ifap,
ifap: ifap,
})
}
}
impl Iterator for IfAddrIterator {
type Item = *mut ffi::ifaddrs;
fn next(&mut self) -> Option<*mut ffi::ifaddrs> {
if self.ifap.is_null() {
return None;
}
let ret = self.ifap;
self.ifap = unsafe { (*self.ifap).ifa_next };
Some(ret)
}
}
impl Drop for IfAddrIterator {
fn drop(&mut self) {
self.ifap = ptr::null_mut();
let ptr = mem::replace(&mut self.orig, ptr::null_mut());
unsafe { ffi::freeifaddrs(ptr) };
}
}
#[derive(Debug)]
pub struct Interface {
pub name: String,
pub addresses: Vec<Address>,
pub flags: InterfaceFlags,
sock: c_int,
}
impl Interface {
pub fn get_all() -> Result<Vec<Interface>> {
let mut ifs = HashMap::new();
for cur in try!(IfAddrIterator::new()) {
let ifname = match convert_ifaddr_name(cur) {
Some(n) => n,
None => continue,
};
let iface = if ifs.contains_key(&ifname) {
ifs.get_mut(&ifname).unwrap()
} else {
let new_if = match Interface::new_from_ptr(cur) {
Ok(i) => i,
Err(_) => continue,
};
ifs.insert(ifname.clone(), new_if);
ifs.get_mut(&ifname).unwrap()
};
if let Some(addr) = convert_ifaddr_address(cur) {
iface.addresses.push(addr);
}
}
let ret = ifs.into_iter().map(|(_, v)| v).collect::<Vec<_>>();
Ok(ret)
}
pub fn get_by_name(name: &str) -> Result<Option<Interface>> {
let mut ret = None;
for cur in try!(IfAddrIterator::new()) {
let ifname = match convert_ifaddr_name(cur) {
Some(n) => n,
None => continue,
};
if ifname != name {
continue;
}
let mut i = match ret.take() {
Some(i) => i,
None => try!(Interface::new_from_ptr(cur)),
};
if let Some(addr) = convert_ifaddr_address(cur) {
i.addresses.push(addr);
}
ret = Some(i);
}
Ok(ret)
}
fn new_from_ptr(ifa: *mut ffi::ifaddrs) -> Result<Interface> {
let ifa = unsafe { &mut *ifa };
let name = convert_ifaddr_name(ifa).unwrap();
let sock = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
if sock < 0 {
return Err(InterfacesError::last_os_error());
}
let flags = InterfaceFlags::from_bits_truncate(ifa.ifa_flags);
Ok(Interface {
name: name,
addresses: vec![],
flags: flags,
sock: sock,
})
}
pub fn is_up(&self) -> bool {
self.flags.contains(InterfaceFlags::IFF_UP)
}
pub fn is_loopback(&self) -> bool {
self.flags.contains(InterfaceFlags::IFF_LOOPBACK)
}
pub fn hardware_addr(&self) -> Result<HardwareAddr> {
self.hardware_addr_impl()
}
#[cfg(target_os = "linux")]
#[allow(non_snake_case)]
fn hardware_addr_impl(&self) -> Result<HardwareAddr> {
let SIOCGIFHWADDR = match constants::get_constant("SIOCGIFHWADDR") {
Some(c) => c,
None => return Err(InterfacesError::NotSupported("SIOCGIFHWADDR")),
};
let mut req = ffi::ifreq_with_hwaddr {
ifr_name: [0; ffi::IFNAMSIZ],
ifr_hwaddr: socket::sockaddr {
sa_family: 0,
sa_data: [0; 14],
},
};
copy_slice(&mut req.ifr_name, self.name.as_bytes());
let res = unsafe { ioctl(self.sock, SIOCGIFHWADDR, &mut req) };
if res < 0 {
return Err(InterfacesError::last_os_error());
}
let mut addr = [0; 6];
for i in 0..6 {
addr[i] = req.ifr_hwaddr.sa_data[i];
}
let addr = unsafe { mem::transmute::<[_; 6], [u8; 6]>(addr) };
Ok(HardwareAddr(addr))
}
#[cfg(target_os = "macos")]
#[allow(non_snake_case)]
fn hardware_addr_impl(&self) -> Result<HardwareAddr> {
let AF_LINK = match constants::get_constant("AF_LINK") {
Some(c) => c as i32,
None => return Err(InterfacesError::NotSupported("AF_LINK")),
};
let mut it = try!(IfAddrIterator::new())
.filter_map(|cur| {
if let Some(name) = convert_ifaddr_name(cur) {
Some((name, cur))
} else {
None
}
})
.filter(|&(ref name, _)| name == &self.name)
.filter(|&(_, ifa)| {
let ifa = unsafe { &mut *ifa };
let family = unsafe { *ifa.ifa_addr }.sa_family as i32;
family == AF_LINK
});
let link_if = match it.next() {
Some((_, ifa)) => ifa,
None => return Err(InterfacesError::NotSupported("No AF_LINK")),
};
let mut addr = [0; 6];
let mut pr = unsafe { ffi::rust_LLADDR(link_if) };
for i in 0..6 {
addr[i] = unsafe { *pr };
pr = ((pr as usize) + 1) as *const u8;
}
drop(it);
Ok(HardwareAddr(addr))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn hardware_addr_impl(&self) -> Result<HardwareAddr> {
Err(InterfacesError::NotSupported("Unknown OS"))
}
#[allow(non_snake_case)]
pub fn set_up(&mut self, up: bool) -> Result<()> {
let SIOCGIFFLAGS = match constants::get_constant("SIOCGIFFLAGS") {
Some(c) => c,
None => return Err(InterfacesError::NotSupported("SIOCGIFFLAGS")),
};
let SIOCSIFFLAGS = match constants::get_constant("SIOCSIFFLAGS") {
Some(c) => c,
None => return Err(InterfacesError::NotSupported("SIOCSIFFLAGS")),
};
let mut req = ffi::ifreq_with_flags {
ifr_name: [0; ffi::IFNAMSIZ],
ifr_flags: 0,
};
copy_slice(&mut req.ifr_name, self.name.as_bytes());
let res = unsafe { ioctl(self.sock, SIOCGIFFLAGS, &mut req) };
if res < 0 {
let err = InterfacesError::last_os_error();
return Err(err);
}
let flag_val = InterfaceFlags::IFF_UP.bits() as u16;
req.ifr_flags = if up {
req.ifr_flags | flag_val
} else {
req.ifr_flags & (!flag_val)
};
let res = unsafe { ioctl(self.sock, SIOCSIFFLAGS, &mut req) };
if res < 0 {
return Err(InterfacesError::last_os_error());
}
self.flags = InterfaceFlags::from_bits_truncate(req.ifr_flags as u32);
Ok(())
}
#[allow(non_snake_case)]
pub fn get_mtu(&self) -> Result<u32> {
let SIOCGIFMTU = match constants::get_constant("SIOCGIFMTU") {
Some(c) => c,
None => return Err(InterfacesError::NotSupported("SIOCGIFMTU")),
};
let mut req = ffi::ifreq_with_mtu {
ifr_name: [0; ffi::IFNAMSIZ],
ifr_mtu: 0,
};
copy_slice(&mut req.ifr_name, self.name.as_bytes());
let res = unsafe { ioctl(self.sock, SIOCGIFMTU, &mut req) };
if res < 0 {
return Err(InterfacesError::last_os_error());
}
Ok(req.ifr_mtu as u32)
}
}
fn convert_ifaddr_name(ifa: *mut ffi::ifaddrs) -> Option<String> {
let ifa = unsafe { &mut *ifa };
match unsafe { CStr::from_ptr(ifa.ifa_name).to_str() } {
Ok(s) => Some(s.to_string()),
Err(_) => None,
}
}
fn convert_ifaddr_family(family: i32) -> Kind {
macro_rules! check_family {
($cc:tt -> $ty:ident) => {
if let Some(val) = constants::get_constant(stringify!($cc)) {
if family == val as i32 {
return Kind::$ty;
}
}
};
}
check_family!(AF_PACKET -> Packet);
check_family!(AF_LINK -> Link);
match family {
libc::AF_INET => Kind::Ipv4,
libc::AF_INET6 => Kind::Ipv6,
val => Kind::Unknown(val),
}
}
fn convert_ifaddr_address(ifa: *mut ffi::ifaddrs) -> Option<Address> {
let ifa = unsafe { &mut *ifa };
let kind = if ifa.ifa_addr != ptr::null_mut() {
let fam = unsafe { *ifa.ifa_addr }.sa_family as i32;
convert_ifaddr_family(fam)
} else {
return None;
};
let addr = ffi::convert_sockaddr(ifa.ifa_addr);
let mask = ffi::convert_sockaddr(ifa.ifa_netmask);
let flags = InterfaceFlags::from_bits_truncate(ifa.ifa_flags);
let hop = if flags.contains(InterfaceFlags::IFF_BROADCAST) {
match ffi::convert_sockaddr(ifa.ifa_ifu.ifu_broadaddr()) {
Some(x) => Some(NextHop::Broadcast(x)),
None => None,
}
} else {
match ffi::convert_sockaddr(ifa.ifa_ifu.ifu_dstaddr()) {
Some(x) => Some(NextHop::Destination(x)),
None => None,
}
};
Some(Address {
kind: kind,
addr: addr,
mask: mask,
hop: hop,
})
}
impl PartialEq for Interface {
fn eq(&self, other: &Interface) -> bool {
self.name == other.name
}
}
impl Eq for Interface {}
impl Drop for Interface {
fn drop(&mut self) {
let sock = mem::replace(&mut self.sock, 0);
unsafe { close(sock) };
}
}
fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
let mut c = 0;
for (d, s) in dst.iter_mut().zip(src.iter()) {
*d = *s;
c += 1;
}
c
}
#[cfg(test)]
mod tests {
use super::*;
use std::hash::Hash;
#[test]
fn test_interface_is_comparable() {
let ifs = Interface::get_all().unwrap();
assert!(ifs[0] == ifs[0]);
}
#[test]
fn test_hardwareaddr_deriving() {
let one = HardwareAddr::zero();
let two = HardwareAddr::zero();
assert!(one == two);
assert_is_clone(&one);
assert_is_copy(&one);
assert_is_hash(&one);
}
#[test]
fn test_hardwareaddr_format() {
let h = HardwareAddr::zero();
assert_eq!(h.as_string(), "00:00:00:00:00:00");
assert_eq!(h.as_bare_string(), "000000000000");
}
fn assert_is_clone<T: Clone>(_: &T) {}
fn assert_is_copy<T: Copy>(_: &T) {}
fn assert_is_hash<T: Hash>(_: &T) {}
}