├── .gitignore ├── Cargo.toml ├── src ├── constants.c.in ├── flags.rs ├── helpers.c ├── error.rs ├── constants.rs ├── ffi.rs ├── ifaddrs.c └── lib.rs ├── README.md ├── Vagrantfile ├── .github └── workflows │ └── regression.yml └── examples ├── ifupdown.rs └── ifconfig-simple.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | # Vagrant stuff 5 | .vagrant 6 | 7 | # Generated from a template 8 | src/constants.c 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interfaces" 3 | description = "A Rust library for interacting with network interfaces" 4 | version = "0.0.9" 5 | authors = ["Naoya Hatta ", "Arvid E Picciani ", "Andrew Dunham "] 6 | homepage = "https://github.com/andrew-d/interfaces-rs" 7 | repository = "https://github.com/andrew-d/interfaces-rs" 8 | license = "MIT/Apache-2.0" 9 | build = "build.rs" 10 | 11 | [dependencies] 12 | bitflags = "2.2" 13 | lazy_static = "1.4.0" 14 | libc = "0.2.103" 15 | nix = "0.26.0" 16 | 17 | [build-dependencies] 18 | cc = "1" 19 | handlebars = "3.5.5" 20 | serde = "1.0" 21 | serde_derive = "1.0" 22 | -------------------------------------------------------------------------------- /src/constants.c.in: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct constant { 9 | const char* name; 10 | uint64_t value; 11 | } constant_t; 12 | 13 | constant_t* rust_get_constants() { 14 | static constant_t constants[] = { 15 | // Testable constants 16 | {{~ #each test_constants as |c|}} 17 | #ifdef {{c}} 18 | { "{{c}}", {{c}} }, 19 | #endif 20 | {{~ /each}} 21 | 22 | // "Always" constants 23 | {{~ #each always_constants as |c|}} 24 | { "{{c}}", {{c}} }, 25 | {{/each}} 26 | 27 | // End of list sentinel 28 | { NULL, 0 }, 29 | }; 30 | 31 | return constants; 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interfaces-rs 2 | 3 | [![Actions Status](https://github.com/andrew-d/interfaces-rs/workflows/Regression/badge.svg)](https://github.com/andrew-d/interfaces-rs/actions) 4 | [![Crate](https://img.shields.io/crates/v/interfaces.svg)](https://crates.io/crates/interfaces) 5 | [![Docs](https://docs.rs/interfaces/badge.svg)](https://docs.rs/interfaces) 6 | 7 | This project consists of functions to work with network interfaces in a 8 | cross-platform manner. 9 | 10 | This is based on `getifaddrs()` to get information of network interfaces. 11 | Therefore some platforms (e.g. Windows) which don't have it are not supported. 12 | 13 | # Example 14 | 15 | Add this to your `Cargo.toml`: 16 | 17 | ``` 18 | [dependencies] 19 | interfaces = "0.0.9" 20 | ``` 21 | 22 | Then, in your crate: 23 | 24 | ```rust 25 | extern crate interfaces; 26 | 27 | use interfaces::Interface; 28 | ``` 29 | 30 | # License 31 | 32 | MIT or Apache 2.0 33 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | # NOTE: Starting this VM might hang - you have to open the VM console and 6 | # Ctrl-C the "Starting vmware_guestd" line. 7 | # For now, this is commented out. 8 | ##config.vm.define "freebsd" do |bsd| 9 | ## bsd.vm.guest = :freebsd 10 | ## bsd.vm.synced_folder ".", "/vagrant", id: "vagrant-root", disabled: true 11 | ## bsd.vm.box = "freebsd/FreeBSD-10.2-RELEASE" 12 | ## bsd.ssh.shell = "sh" 13 | ## 14 | ## bsd.vm.provider :vmware_fusion do |v| 15 | ## v.vmx["memsize"] = "1024" 16 | ## v.vmx["numvcpus"] = "1" 17 | ## end 18 | ##end 19 | 20 | config.vm.define "linux" do |linux| 21 | linux.vm.box = "precise64_vmware" 22 | linux.vm.provision :shell, inline: <<-SHELL 23 | sudo apt-get update 24 | sudo apt-get install -yy curl git 25 | curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh -s -- --yes 26 | multirust default stable 27 | SHELL 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/workflows/regression.yml: -------------------------------------------------------------------------------- 1 | name: Regression 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macOS-latest] 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: test 22 | 23 | cross_build: 24 | 25 | strategy: 26 | matrix: 27 | target: [aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, x86_64-unknown-linux-musl] 28 | 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: stable 36 | target: ${{ matrix.target }} 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | use-cross: true 41 | command: test 42 | args: --target ${{ matrix.target }} 43 | -------------------------------------------------------------------------------- /examples/ifupdown.rs: -------------------------------------------------------------------------------- 1 | extern crate interfaces; 2 | 3 | use std::env; 4 | use std::process::exit; 5 | 6 | use interfaces::Interface; 7 | 8 | fn main() { 9 | let args = env::args().collect::>(); 10 | 11 | if args.len() != 3 { 12 | usage(); 13 | } 14 | let new_status = match &*args[1] { 15 | "up" => true, 16 | "down" => false, 17 | _ => usage(), 18 | }; 19 | 20 | let ifname = &args[2]; 21 | let mut i = match Interface::get_by_name(ifname) { 22 | Ok(Some(i)) => i, 23 | Ok(None) => { 24 | println!("Could not find an interface named: {}", ifname); 25 | return; 26 | } 27 | Err(e) => { 28 | println!("An error occured fetching interfaces: {:?}", e); 29 | return; 30 | } 31 | }; 32 | 33 | println!( 34 | "Interface {} was {}", 35 | i.name, 36 | if i.is_up() { "up" } else { "down" } 37 | ); 38 | match i.set_up(new_status) { 39 | Ok(_) => { 40 | println!("Successfully set interface status"); 41 | println!( 42 | "Interface is now {}", 43 | if new_status { "up" } else { "down" } 44 | ); 45 | } 46 | Err(e) => { 47 | println!("Could not set interface status: {:?}", e); 48 | } 49 | }; 50 | } 51 | 52 | fn usage() -> ! { 53 | println!("Usage: {} up/down ", env::args().next().unwrap()); 54 | exit(1); 55 | } 56 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | /// Represents a set of flags that describe the state of an interface. This corresponds to the 3 | /// flags that are returned from the `SIOCGIFFLAGS` syscall 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub struct InterfaceFlags: u32 { 6 | /// Interface is up. 7 | const IFF_UP = 0x1; 8 | 9 | /// Broadcast address valid. 10 | const IFF_BROADCAST = 0x2; 11 | 12 | /// Turn on debugging. 13 | const IFF_DEBUG = 0x4; 14 | 15 | /// Is a loopback net. 16 | const IFF_LOOPBACK = 0x8; 17 | 18 | /// Interface is point-to-point link. 19 | const IFF_POINTOPOINT = 0x10; 20 | 21 | /// Avoid use of trailers. 22 | const IFF_NOTRAILERS = 0x20; 23 | 24 | /// Resources allocated. 25 | const IFF_RUNNING = 0x40; 26 | 27 | /// No address resolution protocol. 28 | const IFF_NOARP = 0x80; 29 | 30 | /// Receive all packets. 31 | const IFF_PROMISC = 0x100; 32 | 33 | /// Receive all multicast packets. 34 | const IFF_ALLMULTI = 0x200; 35 | 36 | /// Master of a load balancer. 37 | const IFF_MASTER = 0x400; 38 | 39 | /// Slave of a load balancer. 40 | const IFF_SLAVE = 0x800; 41 | 42 | /// Supports multicast. 43 | const IFF_MULTICAST = 0x1000; 44 | 45 | /// Can set media type. 46 | const IFF_PORTSEL = 0x2000; 47 | 48 | /// Auto media select active. 49 | const IFF_AUTOMEDIA = 0x4000; 50 | 51 | /// Dialup device with changing addresses. 52 | const IFF_DYNAMIC = 0x8000; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/helpers.c: -------------------------------------------------------------------------------- 1 | // All modern Unices include this file, which can make things a bit nicer. Pull it in. 2 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 3 | #include 4 | #endif 5 | 6 | // Figuring out which OS it is is a giant PITA in C :-( 7 | #ifdef _WIN32 8 | #define OS_WINDOWS 1 9 | 10 | #ifdef _WIN64 11 | #define OS_WIN64 1 12 | #else 13 | #define OS_WIN32 1 14 | #endif 15 | #elif __APPLE__ 16 | #include "TargetConditionals.h" 17 | 18 | #if TARGET_IPHONE_SIMULATOR 19 | #define OS_IOS 1 20 | #define OS_IOS_SIMULATOR 1 21 | #elif TARGET_OS_IPHONE 22 | #define OS_IOS 1 23 | #elif TARGET_OS_MAC 24 | #define OS_MACOS 1 25 | #else 26 | #error "Unknown Apple platform" 27 | #endif 28 | #elif __linux__ 29 | #define OS_LINUX 1 30 | #elif defined(BSD) 31 | #define OS_BSD 1 32 | #endif 33 | 34 | // General platforms 35 | #if __unix__ 36 | #define OS_UNIX 1 37 | #endif 38 | 39 | #if defined(_POSIX_VERSION) 40 | #define OS_POSIX 1 41 | #endif 42 | 43 | #if !defined(OS_LINUX) && !defined(OS_MACOS) && !defined(OS_IOS) && !defined(OS_WINDOWS) && !defined(OS_BSD) 44 | #error "Unknown compiler" 45 | #endif 46 | 47 | 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | 57 | // Only need this function on OS X. 58 | #if defined(OS_MACOS) || defined(OS_BSD) 59 | #include 60 | 61 | 62 | uint8_t* rust_LLADDR(struct ifaddrs* ifap) { 63 | return (uint8_t *)LLADDR((struct sockaddr_dl *)(ifap)->ifa_addr); 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::error::Error; 3 | use std::fmt; 4 | 5 | use nix; 6 | 7 | /// InterfacesError is the error type that is returned by all functions in this crate. See the 8 | /// documentation on the individual variants for more information. 9 | #[derive(Debug)] 10 | pub enum InterfacesError { 11 | /// Errno indicates that something went wrong with an underlying syscall. The internal value 12 | /// is the `errno` that was returned. 13 | Errno(nix::errno::Errno), 14 | 15 | /// NotSupported indicates that something required for this operation is not currently 16 | /// supported on this platform or computer. The internal string may contain more detail. 17 | NotSupported(&'static str), 18 | } 19 | 20 | impl InterfacesError { 21 | /// Create a new instance of `InterfacesError` with the error set to the current value of the 22 | /// libc `errno` variable. 23 | pub fn last_os_error() -> InterfacesError { 24 | return InterfacesError::Errno(nix::errno::Errno::last()); 25 | } 26 | } 27 | 28 | impl From for InterfacesError { 29 | fn from(e: nix::errno::Errno) -> InterfacesError { 30 | InterfacesError::Errno(e) 31 | } 32 | } 33 | 34 | impl Error for InterfacesError { 35 | fn description(&self) -> &str { 36 | use InterfacesError::*; 37 | 38 | match *self { 39 | Errno(..) => "A syscall error occured", 40 | NotSupported(..) => "A required feature is not supported", 41 | } 42 | } 43 | } 44 | 45 | impl fmt::Display for InterfacesError { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | use InterfacesError::*; 48 | 49 | match *self { 50 | Errno(ref err) => write!(f, "Errno({})", err.desc()), 51 | NotSupported(msg) => write!(f, "NotSupported({})", msg), 52 | } 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use std::error::Error; 59 | use std::fmt; 60 | 61 | use super::*; 62 | 63 | #[test] 64 | fn test_error_has_traits() { 65 | let e = InterfacesError::last_os_error(); 66 | 67 | assert_is_error(&e); 68 | assert_is_display(&e); 69 | } 70 | 71 | fn assert_is_error(_: &T) {} 72 | fn assert_is_display(_: &T) {} 73 | } 74 | -------------------------------------------------------------------------------- /examples/ifconfig-simple.rs: -------------------------------------------------------------------------------- 1 | extern crate interfaces; 2 | 3 | use std::iter; 4 | use std::net; 5 | 6 | use interfaces::{ 7 | flags::{self, InterfaceFlags}, 8 | Interface, Kind, 9 | }; 10 | 11 | // Flag mappings that ifconfig uses, in order. 12 | const NAME_MAPPINGS: &'static [(flags::InterfaceFlags, &'static str)] = &[ 13 | (InterfaceFlags::IFF_UP, "UP"), 14 | (InterfaceFlags::IFF_LOOPBACK, "LOOPBACK"), 15 | (InterfaceFlags::IFF_BROADCAST, "BROADCAST"), 16 | // SMART? 17 | (InterfaceFlags::IFF_RUNNING, "RUNNING"), 18 | // SIMPLEX? 19 | (InterfaceFlags::IFF_MULTICAST, "MULTICAST"), 20 | ]; 21 | 22 | fn main() { 23 | let mut ifs = Interface::get_all().expect("could not get interfaces"); 24 | ifs.sort_by(|a, b| a.name.cmp(&b.name)); 25 | 26 | // Find the maximum alignment for our interface names. 27 | let max_align = ifs.iter().map(|i| i.name.len() + 2).max().unwrap(); 28 | 29 | let full_align = iter::repeat(' ').take(max_align).collect::(); 30 | 31 | for i in ifs.iter() { 32 | let name_align = iter::repeat(' ') 33 | .take(max_align - i.name.len() - 2) 34 | .collect::(); 35 | 36 | // Build the first line by printing the interface flags. 37 | let first_line = { 38 | let mut buf = String::new(); 39 | buf.push_str(&*format!("flags={} <", i.flags.bits())); 40 | 41 | let mut flag_strs = vec![]; 42 | for &(f, s) in NAME_MAPPINGS.iter() { 43 | if i.flags.contains(f) { 44 | flag_strs.push(s); 45 | } 46 | } 47 | 48 | buf.push_str(&*flag_strs.join(",")); 49 | buf.push_str(&*format!("> mtu {}", i.get_mtu().unwrap_or(0))); 50 | 51 | buf 52 | }; 53 | 54 | println!("{}: {}{}", i.name, name_align, first_line); 55 | 56 | if i.flags.contains(InterfaceFlags::IFF_LOOPBACK) { 57 | println!("{}loop (Local Loopback)", full_align); 58 | } else { 59 | if let Ok(addr) = i.hardware_addr() { 60 | println!("{}ether {}", full_align, addr); 61 | } 62 | } 63 | 64 | for addr in i.addresses.iter() { 65 | let raddr = match addr.addr { 66 | Some(a) => a, 67 | None => continue, 68 | }; 69 | 70 | let prefix = match addr.kind { 71 | Kind::Ipv4 => "inet", 72 | Kind::Ipv6 => "inet6", 73 | _ => continue, 74 | }; 75 | 76 | println!("{}{} {}", full_align, prefix, format_addr(&raddr)); 77 | } 78 | } 79 | } 80 | 81 | fn format_addr(addr: &net::SocketAddr) -> String { 82 | match addr { 83 | &net::SocketAddr::V4(ref a) => format!("{}", a.ip()), 84 | &net::SocketAddr::V6(ref a) => format!("{}", a.ip()), 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::convert::AsRef; 3 | use std::ffi::CStr; 4 | use std::mem; 5 | use std::os::raw::c_char; 6 | use std::ptr; 7 | 8 | #[cfg(any( 9 | target_os = "fuchsia", 10 | target_os = "haiku", 11 | target_os = "hermit", 12 | target_os = "android", 13 | target_os = "emscripten", 14 | target_os = "solaris", 15 | target_os = "illumos", 16 | target_os = "vxworks", 17 | target_os = "wasi", 18 | target_env = "wasi", 19 | ))] 20 | pub type ConstantType = libc::c_int; 21 | 22 | #[cfg(any( 23 | target_os = "macos", 24 | target_os = "ios", 25 | target_os = "freebsd", 26 | target_os = "dragonfly", 27 | target_os = "openbsd", 28 | target_os = "netbsd", 29 | target_os = "redox", 30 | target_env = "newlib", 31 | target_env = "uclibc", 32 | ))] 33 | pub type ConstantType = libc::c_ulong; 34 | 35 | #[cfg(all(target_os = "linux", target_env = "musl"))] 36 | pub type ConstantType = libc::c_int; 37 | 38 | #[cfg(all(target_os = "linux", target_env = "gnu"))] 39 | pub type ConstantType = libc::c_ulong; 40 | 41 | /// The constant as sent by the C side. 42 | #[repr(C)] 43 | struct Constant { 44 | name: *const c_char, 45 | value: u64, 46 | } 47 | 48 | extern "C" { 49 | /// Get a list of all constants compiled into the C code. 50 | fn rust_get_constants() -> *const Constant; 51 | } 52 | 53 | lazy_static! { 54 | static ref CONSTANTS: HashMap = { 55 | let mut cvals = vec![]; 56 | 57 | let mut constant = unsafe { rust_get_constants() }; 58 | 59 | // The C function will return a static array that contains the various constants we requested, 60 | // terminated by a single entry in the array that has a NULL pointer for a name. We loop 61 | // forever until we see this entry. 62 | loop { 63 | let cval = unsafe { ptr::read(constant) }; 64 | if cval.name.is_null() { 65 | break 66 | } 67 | 68 | // Save this entry 69 | cvals.push(cval); 70 | 71 | // Bump pointer. This "steps" through the array by the size of the underlying 72 | // structure. 73 | constant = ((constant as usize) + mem::size_of::()) as *const Constant; 74 | } 75 | 76 | // Convert from the C-provided type into a hashmap. 77 | let ret = cvals 78 | .into_iter() 79 | .map(|v| { 80 | // HashMap has a from_iter method that accepts (key, value) tuples. 81 | ( 82 | unsafe { CStr::from_ptr(v.name).to_string_lossy().into_owned() }, 83 | v.value as ConstantType 84 | ) 85 | }) 86 | .collect::>(); 87 | ret 88 | }; 89 | } 90 | 91 | pub fn get_constant>(name: S) -> Option { 92 | // Since `u64` is `Copy`, we can dereference the constant directly 93 | CONSTANTS.get(name.as_ref()).map(|v| *v) 94 | } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | 100 | #[test] 101 | fn test_existing() { 102 | assert!(get_constant("SIOCGIFFLAGS").is_some()) 103 | } 104 | 105 | #[test] 106 | fn test_not_existing() { 107 | assert!(get_constant("bad key").is_none()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::mem; 4 | use std::net; 5 | use std::ptr; 6 | 7 | use libc::{self, c_void, c_char, c_int, c_uint, c_ushort}; 8 | use nix::sys::socket; 9 | 10 | pub const IFNAMSIZ: usize = 16; 11 | 12 | #[repr(C)] 13 | pub struct ifreq_with_hwaddr { 14 | pub ifr_name: [u8; IFNAMSIZ], 15 | pub ifr_hwaddr: socket::sockaddr, 16 | } 17 | 18 | #[repr(C)] 19 | pub struct ifreq_with_flags { 20 | pub ifr_name: [u8; IFNAMSIZ], 21 | pub ifr_flags: c_ushort, 22 | } 23 | 24 | #[repr(C)] 25 | pub struct ifreq_with_mtu { 26 | pub ifr_name: [u8; IFNAMSIZ], 27 | pub ifr_mtu: c_int, 28 | } 29 | 30 | #[repr(C)] 31 | pub struct union_ifa_ifu { 32 | pub data: *mut c_void, 33 | } 34 | impl union_ifa_ifu { 35 | pub fn ifu_broadaddr(&mut self) -> *mut socket::sockaddr { 36 | self.data as *mut socket::sockaddr 37 | } 38 | pub fn ifu_dstaddr(&mut self) -> *mut socket::sockaddr { 39 | self.data as *mut socket::sockaddr 40 | } 41 | } 42 | 43 | #[repr(C)] 44 | pub struct ifaddrs { 45 | pub ifa_next: *mut ifaddrs, 46 | pub ifa_name: *mut c_char, 47 | pub ifa_flags: c_uint, 48 | pub ifa_addr: *mut socket::sockaddr, 49 | pub ifa_netmask: *mut socket::sockaddr, 50 | pub ifa_ifu: union_ifa_ifu, 51 | pub ifa_data: *mut c_void, 52 | } 53 | 54 | extern "C" { 55 | pub fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int; 56 | pub fn freeifaddrs(ifa: *mut ifaddrs) -> c_void; 57 | } 58 | 59 | fn make_int16(hi: u8, lo: u8) -> u16 { 60 | (lo as u16) | ((hi as u16) << 8) 61 | } 62 | 63 | pub fn convert_sockaddr(sa: *mut socket::sockaddr) -> Option { 64 | if sa == ptr::null_mut() { 65 | return None; 66 | } 67 | 68 | let (addr, port, flowinfo, scope_id) = match unsafe { *sa }.sa_family as i32 { 69 | libc::AF_INET => { 70 | let sa: *const socket::sockaddr_in = unsafe { mem::transmute(sa) }; 71 | let sa = &unsafe { *sa }; 72 | let (addr, port) = (sa.sin_addr.s_addr, sa.sin_port); 73 | (net::IpAddr::V4(net::Ipv4Addr::new(((addr & 0x000000FF) >> 0) as u8, 74 | ((addr & 0x0000FF00) >> 8) as u8, 75 | ((addr & 0x00FF0000) >> 16) as u8, 76 | ((addr & 0xFF000000) >> 24) as u8)), 77 | port, 0, 0) 78 | } 79 | libc::AF_INET6 => { 80 | let sa: *const socket::sockaddr_in6 = unsafe { mem::transmute(sa) }; 81 | let sa = &unsafe { *sa }; 82 | let (addr, port, flowinfo, scope_id) = (sa.sin6_addr.s6_addr, sa.sin6_port, sa.sin6_flowinfo, sa.sin6_scope_id); 83 | (net::IpAddr::V6(net::Ipv6Addr::new(make_int16(addr[0], addr[1]), 84 | make_int16(addr[2], addr[3]), 85 | make_int16(addr[4], addr[5]), 86 | make_int16(addr[6], addr[7]), 87 | make_int16(addr[8], addr[9]), 88 | make_int16(addr[10], addr[11]), 89 | make_int16(addr[12], addr[13]), 90 | make_int16(addr[14], addr[15]), 91 | )), 92 | port, flowinfo, scope_id) 93 | } 94 | _ => return None, 95 | }; 96 | 97 | let sa = match addr { 98 | net::IpAddr::V4(addr) => net::SocketAddr::V4(net::SocketAddrV4::new(addr, port)), 99 | net::IpAddr::V6(addr) => net::SocketAddr::V6(net::SocketAddrV6::new(addr, port, flowinfo, scope_id)), 100 | }; 101 | Some(sa) 102 | } 103 | 104 | // Helper functions from `helpers.c` 105 | extern "C" { 106 | #[cfg(target_os = "macos")] 107 | pub fn rust_LLADDR(p: *mut ifaddrs) -> *const u8; 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::make_int16; 113 | 114 | #[test] 115 | fn test_make_int16() { 116 | assert_eq!(make_int16(0xff, 0x00), 0xff00); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/ifaddrs.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, Kenneth MacKay 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include "ifaddrs.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | typedef struct NetlinkList 38 | { 39 | struct NetlinkList *m_next; 40 | struct nlmsghdr *m_data; 41 | unsigned int m_size; 42 | } NetlinkList; 43 | 44 | static int netlink_socket(void) 45 | { 46 | int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 47 | if(l_socket < 0) 48 | { 49 | return -1; 50 | } 51 | 52 | struct sockaddr_nl l_addr; 53 | memset(&l_addr, 0, sizeof(l_addr)); 54 | l_addr.nl_family = AF_NETLINK; 55 | if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0) 56 | { 57 | close(l_socket); 58 | return -1; 59 | } 60 | 61 | return l_socket; 62 | } 63 | 64 | static int netlink_send(int p_socket, int p_request) 65 | { 66 | char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; 67 | memset(l_buffer, 0, sizeof(l_buffer)); 68 | struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer; 69 | struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr); 70 | 71 | l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg)); 72 | l_hdr->nlmsg_type = p_request; 73 | l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; 74 | l_hdr->nlmsg_pid = 0; 75 | l_hdr->nlmsg_seq = p_socket; 76 | l_msg->rtgen_family = AF_UNSPEC; 77 | 78 | struct sockaddr_nl l_addr; 79 | memset(&l_addr, 0, sizeof(l_addr)); 80 | l_addr.nl_family = AF_NETLINK; 81 | return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr))); 82 | } 83 | 84 | static int netlink_recv(int p_socket, void *p_buffer, size_t p_len) 85 | { 86 | struct msghdr l_msg; 87 | struct iovec l_iov = { p_buffer, p_len }; 88 | struct sockaddr_nl l_addr; 89 | int l_result; 90 | 91 | for(;;) 92 | { 93 | l_msg.msg_name = (void *)&l_addr; 94 | l_msg.msg_namelen = sizeof(l_addr); 95 | l_msg.msg_iov = &l_iov; 96 | l_msg.msg_iovlen = 1; 97 | l_msg.msg_control = NULL; 98 | l_msg.msg_controllen = 0; 99 | l_msg.msg_flags = 0; 100 | int l_result = recvmsg(p_socket, &l_msg, 0); 101 | 102 | if(l_result < 0) 103 | { 104 | if(errno == EINTR) 105 | { 106 | continue; 107 | } 108 | return -2; 109 | } 110 | 111 | if(l_msg.msg_flags & MSG_TRUNC) 112 | { // buffer was too small 113 | return -1; 114 | } 115 | return l_result; 116 | } 117 | } 118 | 119 | static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done) 120 | { 121 | size_t l_size = 4096; 122 | void *l_buffer = NULL; 123 | 124 | for(;;) 125 | { 126 | free(l_buffer); 127 | l_buffer = malloc(l_size); 128 | 129 | int l_read = netlink_recv(p_socket, l_buffer, l_size); 130 | *p_size = l_read; 131 | if(l_read == -2) 132 | { 133 | free(l_buffer); 134 | return NULL; 135 | } 136 | if(l_read >= 0) 137 | { 138 | pid_t l_pid = getpid(); 139 | struct nlmsghdr *l_hdr; 140 | for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read)) 141 | { 142 | if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) 143 | { 144 | continue; 145 | } 146 | 147 | if(l_hdr->nlmsg_type == NLMSG_DONE) 148 | { 149 | *p_done = 1; 150 | break; 151 | } 152 | 153 | if(l_hdr->nlmsg_type == NLMSG_ERROR) 154 | { 155 | free(l_buffer); 156 | return NULL; 157 | } 158 | } 159 | return l_buffer; 160 | } 161 | 162 | l_size *= 2; 163 | } 164 | } 165 | 166 | static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size) 167 | { 168 | NetlinkList *l_item = malloc(sizeof(NetlinkList)); 169 | l_item->m_next = NULL; 170 | l_item->m_data = p_data; 171 | l_item->m_size = p_size; 172 | return l_item; 173 | } 174 | 175 | static void freeResultList(NetlinkList *p_list) 176 | { 177 | NetlinkList *l_cur; 178 | while(p_list) 179 | { 180 | l_cur = p_list; 181 | p_list = p_list->m_next; 182 | free(l_cur->m_data); 183 | free(l_cur); 184 | } 185 | } 186 | 187 | static NetlinkList *getResultList(int p_socket, int p_request) 188 | { 189 | if(netlink_send(p_socket, p_request) < 0) 190 | { 191 | return NULL; 192 | } 193 | 194 | NetlinkList *l_list = NULL; 195 | NetlinkList *l_end = NULL; 196 | int l_size; 197 | int l_done = 0; 198 | while(!l_done) 199 | { 200 | struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); 201 | if(!l_hdr) 202 | { // error 203 | freeResultList(l_list); 204 | return NULL; 205 | } 206 | 207 | NetlinkList *l_item = newListItem(l_hdr, l_size); 208 | if(!l_list) 209 | { 210 | l_list = l_item; 211 | } 212 | else 213 | { 214 | l_end->m_next = l_item; 215 | } 216 | l_end = l_item; 217 | } 218 | return l_list; 219 | } 220 | 221 | static size_t maxSize(size_t a, size_t b) 222 | { 223 | return (a > b ? a : b); 224 | } 225 | 226 | static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) 227 | { 228 | switch(p_family) 229 | { 230 | case AF_INET: 231 | return sizeof(struct sockaddr_in); 232 | case AF_INET6: 233 | return sizeof(struct sockaddr_in6); 234 | case AF_PACKET: 235 | return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); 236 | default: 237 | return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); 238 | } 239 | } 240 | 241 | static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size) 242 | { 243 | switch(p_family) 244 | { 245 | case AF_INET: 246 | memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); 247 | break; 248 | case AF_INET6: 249 | memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); 250 | break; 251 | case AF_PACKET: 252 | memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); 253 | ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; 254 | break; 255 | default: 256 | memcpy(p_dest->sa_data, p_data, p_size); 257 | break; 258 | } 259 | p_dest->sa_family = p_family; 260 | } 261 | 262 | static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry) 263 | { 264 | if(!*p_resultList) 265 | { 266 | *p_resultList = p_entry; 267 | } 268 | else 269 | { 270 | struct ifaddrs *l_cur = *p_resultList; 271 | while(l_cur->ifa_next) 272 | { 273 | l_cur = l_cur->ifa_next; 274 | } 275 | l_cur->ifa_next = p_entry; 276 | } 277 | } 278 | 279 | static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) 280 | { 281 | struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr); 282 | 283 | size_t l_nameSize = 0; 284 | size_t l_addrSize = 0; 285 | size_t l_dataSize = 0; 286 | 287 | size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); 288 | struct rtattr *l_rta; 289 | for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) 290 | { 291 | void *l_rtaData = RTA_DATA(l_rta); 292 | size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); 293 | switch(l_rta->rta_type) 294 | { 295 | case IFLA_ADDRESS: 296 | case IFLA_BROADCAST: 297 | l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); 298 | break; 299 | case IFLA_IFNAME: 300 | l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); 301 | break; 302 | case IFLA_STATS: 303 | l_dataSize += NLMSG_ALIGN(l_rtaSize); 304 | break; 305 | default: 306 | break; 307 | } 308 | } 309 | 310 | struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize); 311 | memset(l_entry, 0, sizeof(struct ifaddrs)); 312 | l_entry->ifa_name = ""; 313 | 314 | char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); 315 | char *l_addr = l_name + l_nameSize; 316 | char *l_data = l_addr + l_addrSize; 317 | 318 | l_entry->ifa_flags = l_info->ifi_flags; 319 | 320 | l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); 321 | for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) 322 | { 323 | void *l_rtaData = RTA_DATA(l_rta); 324 | size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); 325 | switch(l_rta->rta_type) 326 | { 327 | case IFLA_ADDRESS: 328 | case IFLA_BROADCAST: 329 | { 330 | size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); 331 | makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); 332 | ((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index; 333 | ((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type; 334 | if(l_rta->rta_type == IFLA_ADDRESS) 335 | { 336 | l_entry->ifa_addr = (struct sockaddr *)l_addr; 337 | } 338 | else 339 | { 340 | l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; 341 | } 342 | l_addr += NLMSG_ALIGN(l_addrLen); 343 | break; 344 | } 345 | case IFLA_IFNAME: 346 | strncpy(l_name, l_rtaData, l_rtaDataSize); 347 | l_name[l_rtaDataSize] = '\0'; 348 | l_entry->ifa_name = l_name; 349 | break; 350 | case IFLA_STATS: 351 | memcpy(l_data, l_rtaData, l_rtaDataSize); 352 | l_entry->ifa_data = l_data; 353 | break; 354 | default: 355 | break; 356 | } 357 | } 358 | 359 | addToEnd(p_resultList, l_entry); 360 | p_links[l_info->ifi_index - 1] = l_entry; 361 | } 362 | 363 | static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) 364 | { 365 | struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr); 366 | 367 | size_t l_nameSize = 0; 368 | size_t l_addrSize = 0; 369 | 370 | int l_addedNetmask = 0; 371 | 372 | size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); 373 | struct rtattr *l_rta; 374 | for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) 375 | { 376 | void *l_rtaData = RTA_DATA(l_rta); 377 | size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); 378 | if(l_info->ifa_family == AF_PACKET) 379 | { 380 | continue; 381 | } 382 | 383 | switch(l_rta->rta_type) 384 | { 385 | case IFA_ADDRESS: 386 | case IFA_LOCAL: 387 | if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) 388 | { // make room for netmask 389 | l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); 390 | l_addedNetmask = 1; 391 | } 392 | case IFA_BROADCAST: 393 | l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); 394 | break; 395 | case IFA_LABEL: 396 | l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); 397 | break; 398 | default: 399 | break; 400 | } 401 | } 402 | 403 | struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); 404 | memset(l_entry, 0, sizeof(struct ifaddrs)); 405 | l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name; 406 | 407 | char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); 408 | char *l_addr = l_name + l_nameSize; 409 | 410 | l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags; 411 | 412 | l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); 413 | for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) 414 | { 415 | void *l_rtaData = RTA_DATA(l_rta); 416 | size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); 417 | switch(l_rta->rta_type) 418 | { 419 | case IFA_ADDRESS: 420 | case IFA_BROADCAST: 421 | case IFA_LOCAL: 422 | { 423 | size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); 424 | makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); 425 | if(l_info->ifa_family == AF_INET6) 426 | { 427 | if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData)) 428 | { 429 | ((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index; 430 | } 431 | } 432 | 433 | if(l_rta->rta_type == IFA_ADDRESS) 434 | { // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address 435 | if(l_entry->ifa_addr) 436 | { 437 | l_entry->ifa_dstaddr = (struct sockaddr *)l_addr; 438 | } 439 | else 440 | { 441 | l_entry->ifa_addr = (struct sockaddr *)l_addr; 442 | } 443 | } 444 | else if(l_rta->rta_type == IFA_LOCAL) 445 | { 446 | if(l_entry->ifa_addr) 447 | { 448 | l_entry->ifa_dstaddr = l_entry->ifa_addr; 449 | } 450 | l_entry->ifa_addr = (struct sockaddr *)l_addr; 451 | } 452 | else 453 | { 454 | l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; 455 | } 456 | l_addr += NLMSG_ALIGN(l_addrLen); 457 | break; 458 | } 459 | case IFA_LABEL: 460 | strncpy(l_name, l_rtaData, l_rtaDataSize); 461 | l_name[l_rtaDataSize] = '\0'; 462 | l_entry->ifa_name = l_name; 463 | break; 464 | default: 465 | break; 466 | } 467 | } 468 | 469 | if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) 470 | { 471 | unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); 472 | unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); 473 | char l_mask[16] = {0}; 474 | unsigned i; 475 | for(i=0; i<(l_prefix/8); ++i) 476 | { 477 | l_mask[i] = 0xff; 478 | } 479 | l_mask[i] = 0xff << (8 - (l_prefix % 8)); 480 | 481 | makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8); 482 | l_entry->ifa_netmask = (struct sockaddr *)l_addr; 483 | } 484 | 485 | addToEnd(p_resultList, l_entry); 486 | } 487 | 488 | static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList) 489 | { 490 | pid_t l_pid = getpid(); 491 | for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) 492 | { 493 | unsigned int l_nlsize = p_netlinkList->m_size; 494 | struct nlmsghdr *l_hdr; 495 | for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) 496 | { 497 | if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) 498 | { 499 | continue; 500 | } 501 | 502 | if(l_hdr->nlmsg_type == NLMSG_DONE) 503 | { 504 | break; 505 | } 506 | 507 | if(l_hdr->nlmsg_type == RTM_NEWLINK) 508 | { 509 | interpretLink(l_hdr, p_links, p_resultList); 510 | } 511 | else if(l_hdr->nlmsg_type == RTM_NEWADDR) 512 | { 513 | interpretAddr(l_hdr, p_links, p_resultList); 514 | } 515 | } 516 | } 517 | } 518 | 519 | static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList) 520 | { 521 | unsigned l_links = 0; 522 | pid_t l_pid = getpid(); 523 | for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) 524 | { 525 | unsigned int l_nlsize = p_netlinkList->m_size; 526 | struct nlmsghdr *l_hdr; 527 | for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) 528 | { 529 | if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) 530 | { 531 | continue; 532 | } 533 | 534 | if(l_hdr->nlmsg_type == NLMSG_DONE) 535 | { 536 | break; 537 | } 538 | 539 | if(l_hdr->nlmsg_type == RTM_NEWLINK) 540 | { 541 | ++l_links; 542 | } 543 | } 544 | } 545 | 546 | return l_links; 547 | } 548 | 549 | int __attribute__((weak)) getifaddrs(struct ifaddrs **ifap) 550 | { 551 | if(!ifap) 552 | { 553 | return -1; 554 | } 555 | *ifap = NULL; 556 | 557 | int l_socket = netlink_socket(); 558 | if(l_socket < 0) 559 | { 560 | return -1; 561 | } 562 | 563 | NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK); 564 | if(!l_linkResults) 565 | { 566 | close(l_socket); 567 | return -1; 568 | } 569 | 570 | NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR); 571 | if(!l_addrResults) 572 | { 573 | close(l_socket); 574 | freeResultList(l_linkResults); 575 | return -1; 576 | } 577 | 578 | unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults); 579 | struct ifaddrs *l_links[l_numLinks]; 580 | memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *)); 581 | 582 | interpret(l_socket, l_linkResults, l_links, ifap); 583 | interpret(l_socket, l_addrResults, l_links, ifap); 584 | 585 | freeResultList(l_linkResults); 586 | freeResultList(l_addrResults); 587 | close(l_socket); 588 | return 0; 589 | } 590 | 591 | void __attribute__((weak)) freeifaddrs(struct ifaddrs *ifa) 592 | { 593 | struct ifaddrs *l_cur; 594 | while(ifa) 595 | { 596 | l_cur = ifa; 597 | ifa = ifa->ifa_next; 598 | free(l_cur); 599 | } 600 | } 601 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A library for interoperating with the network interfaces of a system. 4 | //! 5 | //! TODO: add more documentation on how to use. 6 | 7 | #[macro_use] 8 | extern crate bitflags; 9 | #[macro_use] 10 | extern crate lazy_static; 11 | extern crate libc; 12 | extern crate nix; 13 | 14 | use std::collections::HashMap; 15 | use std::ffi::CStr; 16 | use std::fmt; 17 | use std::mem; 18 | use std::net; 19 | use std::ptr; 20 | 21 | use libc::c_int; 22 | use libc::{close, ioctl, socket}; 23 | use libc::{AF_INET, SOCK_DGRAM}; 24 | 25 | #[cfg(target_os = "linux")] 26 | use nix::sys::socket; 27 | 28 | pub use error::InterfacesError; 29 | pub use flags::InterfaceFlags; 30 | 31 | mod constants; 32 | mod error; 33 | mod ffi; 34 | 35 | /// Submodule containing various flags. 36 | pub mod flags; 37 | 38 | /// A specialized Result type for this crate. 39 | pub type Result = ::std::result::Result; 40 | 41 | /// `Kind` represents the interface family (equivalent to the `sa_family` field in the `sockaddr` 42 | /// structure). 43 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 44 | pub enum Kind { 45 | /// This interface is IPv4. 46 | Ipv4, 47 | 48 | /// This interface is IPv6. 49 | Ipv6, 50 | 51 | /// This interface is a link interface (`AF_LINK`). 52 | Link, 53 | 54 | /// This interface has an unknown interface type. The interior `i32` contains the numerical 55 | /// value that is unknown. 56 | Unknown(i32), 57 | 58 | /// Linux only: this interface is a packet interface (`AF_PACKET`). 59 | Packet, 60 | } 61 | 62 | impl fmt::Display for Kind { 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 64 | match *self { 65 | Kind::Ipv4 => write!(f, "IPv4"), 66 | Kind::Ipv6 => write!(f, "IPv6"), 67 | Kind::Link => write!(f, "Link"), 68 | Kind::Unknown(v) => write!(f, "Unknown({})", v), 69 | Kind::Packet => write!(f, "Packet"), 70 | } 71 | } 72 | } 73 | 74 | /// The next hop for an interface. See the individual variants for more information. 75 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 76 | pub enum NextHop { 77 | /// The broadcast address associated with the interface's address. 78 | Broadcast(net::SocketAddr), 79 | 80 | /// The destination address of a point-to-point interface. 81 | Destination(net::SocketAddr), 82 | } 83 | 84 | impl fmt::Display for NextHop { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | match *self { 87 | NextHop::Broadcast(ref addr) => write!(f, "Broadcast({})", addr), 88 | NextHop::Destination(ref addr) => write!(f, "Destination({})", addr), 89 | } 90 | } 91 | } 92 | 93 | /// This structure represents a single address for a given interface. 94 | #[derive(Debug, Clone, Copy)] 95 | pub struct Address { 96 | /// The kind of address this is (e.g. IPv4). 97 | pub kind: Kind, 98 | 99 | /// The underlying socket address, if it applies. 100 | pub addr: Option, 101 | 102 | /// The netmask of this interface address, if it applies. 103 | pub mask: Option, 104 | 105 | /// The broadcast address or destination address, if it applies. 106 | pub hop: Option, 107 | } 108 | 109 | /// HardwareAddr represents a hardware address (commonly known as a MAC address) of a given 110 | /// interface. 111 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 112 | pub struct HardwareAddr([u8; 6]); 113 | 114 | impl HardwareAddr { 115 | /// Returns a new, empty `HardwareAddr` structure. This is equivalent to the MAC address 116 | /// `00:00:00:00:00:00`. 117 | pub fn zero() -> HardwareAddr { 118 | HardwareAddr([0; 6]) 119 | } 120 | 121 | /// Formats this hardware address in the standard MAC address format - 6 octets in hexadecimal 122 | /// format, each seperated by a colon. 123 | /// 124 | /// ``` 125 | /// # use interfaces::HardwareAddr; 126 | /// let s = HardwareAddr::zero().as_string(); 127 | /// assert_eq!(s, "00:00:00:00:00:00"); 128 | /// ``` 129 | pub fn as_string(&self) -> String { 130 | let &HardwareAddr(ref arr) = self; 131 | 132 | format!( 133 | "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", 134 | arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], 135 | ) 136 | } 137 | 138 | /// Formats this hardware address as a sequence of hexadecimal numbers without the seperating 139 | /// colons. 140 | /// 141 | /// ``` 142 | /// # use interfaces::HardwareAddr; 143 | /// let s = HardwareAddr::zero().as_bare_string(); 144 | /// assert_eq!(s, "000000000000"); 145 | /// ``` 146 | pub fn as_bare_string(&self) -> String { 147 | let &HardwareAddr(ref arr) = self; 148 | 149 | format!( 150 | "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", 151 | arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], 152 | ) 153 | } 154 | 155 | /// Returns the raw bytes representing this hardware address. 156 | /// 157 | /// ``` 158 | /// # use interfaces::HardwareAddr; 159 | /// let s = HardwareAddr::zero(); 160 | /// assert_eq!(s.as_bytes(), &[0, 0, 0, 0, 0, 0]); 161 | /// ``` 162 | pub fn as_bytes(&self) -> &[u8] { 163 | let &HardwareAddr(ref arr) = self; 164 | arr 165 | } 166 | } 167 | 168 | impl fmt::Display for HardwareAddr { 169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 170 | write!(f, "{}", self.as_string()) 171 | } 172 | } 173 | 174 | /// An iterator to walk through all `ifaddrs`. 175 | struct IfAddrIterator { 176 | orig: *mut ffi::ifaddrs, 177 | ifap: *mut ffi::ifaddrs, 178 | } 179 | 180 | impl IfAddrIterator { 181 | fn new() -> Result { 182 | // Get all interface addresses 183 | let mut ifap: *mut ffi::ifaddrs = unsafe { mem::zeroed() }; 184 | if unsafe { ffi::getifaddrs(&mut ifap as *mut _) } != 0 { 185 | return Err(InterfacesError::last_os_error()); 186 | } 187 | 188 | Ok(IfAddrIterator { 189 | orig: ifap, 190 | ifap: ifap, 191 | }) 192 | } 193 | } 194 | 195 | impl Iterator for IfAddrIterator { 196 | type Item = *mut ffi::ifaddrs; 197 | 198 | fn next(&mut self) -> Option<*mut ffi::ifaddrs> { 199 | if self.ifap.is_null() { 200 | return None; 201 | } 202 | 203 | let ret = self.ifap; 204 | self.ifap = unsafe { (*self.ifap).ifa_next }; 205 | Some(ret) 206 | } 207 | } 208 | 209 | impl Drop for IfAddrIterator { 210 | fn drop(&mut self) { 211 | // Zero the iterator pointer. 212 | self.ifap = ptr::null_mut(); 213 | 214 | // Zero the original pointer in the structure and then free it. 215 | let ptr = mem::replace(&mut self.orig, ptr::null_mut()); 216 | unsafe { ffi::freeifaddrs(ptr) }; 217 | } 218 | } 219 | 220 | /// The `Interface` structure represents a single interface on the system. It also contains 221 | /// methods to control the interface. 222 | #[derive(Debug)] 223 | pub struct Interface { 224 | /// The name of this interface. 225 | pub name: String, 226 | 227 | /// All addresses for this interface. 228 | pub addresses: Vec
, 229 | 230 | /// Interface flags. 231 | /// 232 | /// NOTE: The underlying API returns this value for each address of an interface, not each 233 | /// interface itself. We assume that they are all equal and take the first set of flags (from 234 | /// the first address). 235 | pub flags: InterfaceFlags, 236 | 237 | // Information socket 238 | sock: c_int, 239 | } 240 | 241 | impl Interface { 242 | /// Retrieve a list of all interfaces on this system. 243 | pub fn get_all() -> Result> { 244 | // Map each interface address to a single interface name. 245 | let mut ifs = HashMap::new(); 246 | for cur in IfAddrIterator::new()? { 247 | // Only support interfaces with valid names. 248 | let ifname = match convert_ifaddr_name(cur) { 249 | Some(n) => n, 250 | None => continue, 251 | }; 252 | 253 | let iface = if ifs.contains_key(&ifname) { 254 | ifs.get_mut(&ifname).unwrap() 255 | } else { 256 | let new_if = match Interface::new_from_ptr(cur) { 257 | Ok(i) => i, 258 | Err(_) => continue, 259 | }; 260 | ifs.insert(ifname.clone(), new_if); 261 | ifs.get_mut(&ifname).unwrap() 262 | }; 263 | 264 | // If we can, convert this current address. 265 | if let Some(addr) = convert_ifaddr_address(cur) { 266 | iface.addresses.push(addr); 267 | } 268 | } 269 | 270 | let ret = ifs.into_iter().map(|(_, v)| v).collect::>(); 271 | Ok(ret) 272 | } 273 | 274 | /// Returns an `Interface` instance representing the interface with the given name. Will 275 | /// return `Ok(Some(Interface))` on success, `Ok(None)` if there is no such interface, and 276 | /// `Err(..)` on failure. 277 | /// 278 | /// ``` 279 | /// # use interfaces::{Interface, Result}; 280 | /// # fn foo() -> Result> { 281 | /// let iface = Interface::get_by_name("lo")?; 282 | /// if let Some(ref lo) = iface { 283 | /// assert!(lo.is_loopback()); 284 | /// } else { 285 | /// println!("Could not find loopback interface"); 286 | /// } 287 | /// # Ok(iface) 288 | /// # } 289 | /// ``` 290 | pub fn get_by_name(name: &str) -> Result> { 291 | let mut ret = None; 292 | 293 | for cur in IfAddrIterator::new()? { 294 | // Only support interfaces with valid names. 295 | let ifname = match convert_ifaddr_name(cur) { 296 | Some(n) => n, 297 | None => continue, 298 | }; 299 | 300 | if ifname != name { 301 | continue; 302 | } 303 | 304 | // Get or create the Interface 305 | let mut i = match ret.take() { 306 | Some(i) => i, 307 | None => Interface::new_from_ptr(cur)?, 308 | }; 309 | 310 | // If we can, convert this current address. 311 | if let Some(addr) = convert_ifaddr_address(cur) { 312 | i.addresses.push(addr); 313 | } 314 | 315 | ret = Some(i); 316 | } 317 | 318 | Ok(ret) 319 | } 320 | 321 | /// Create a new Interface from a given `ffi::ifaddrs`. 322 | fn new_from_ptr(ifa: *mut ffi::ifaddrs) -> Result { 323 | let ifa = unsafe { &mut *ifa }; 324 | 325 | // NOTE: can unwrap() here since we only call this function if the prior call to 326 | // convert_ifaddr_name succeeded. It's a bit sad that we have to duplicate work, but not a 327 | // huge deal. 328 | let name = convert_ifaddr_name(ifa).unwrap(); 329 | 330 | // Try to create a socket that we use to get info about this interface. 331 | let sock = unsafe { socket(AF_INET, SOCK_DGRAM, 0) }; 332 | if sock < 0 { 333 | return Err(InterfacesError::last_os_error()); 334 | } 335 | 336 | let flags = InterfaceFlags::from_bits_truncate(ifa.ifa_flags); 337 | Ok(Interface { 338 | name: name, 339 | addresses: vec![], 340 | flags: flags, 341 | sock: sock, 342 | }) 343 | } 344 | 345 | /// Returns whether this interface is up. 346 | pub fn is_up(&self) -> bool { 347 | self.flags.contains(InterfaceFlags::IFF_UP) 348 | } 349 | 350 | /// Returns whether this interface is ready for transfer. 351 | pub fn is_running(&self) -> bool { 352 | self.flags.contains(InterfaceFlags::IFF_RUNNING) 353 | } 354 | 355 | /// Returns whether this interface is a loopback address. 356 | pub fn is_loopback(&self) -> bool { 357 | self.flags.contains(InterfaceFlags::IFF_LOOPBACK) 358 | } 359 | 360 | /// Retrieves the hardware address of this interface. 361 | pub fn hardware_addr(&self) -> Result { 362 | self.hardware_addr_impl() 363 | } 364 | 365 | #[cfg(target_os = "linux")] 366 | #[allow(non_snake_case)] 367 | fn hardware_addr_impl(&self) -> Result { 368 | // We need this IOCTL in order to get the hardware address. 369 | let SIOCGIFHWADDR = match constants::get_constant("SIOCGIFHWADDR") { 370 | Some(c) => c, 371 | None => return Err(InterfacesError::NotSupported("SIOCGIFHWADDR")), 372 | }; 373 | 374 | let mut req = ffi::ifreq_with_hwaddr { 375 | ifr_name: [0; ffi::IFNAMSIZ], 376 | ifr_hwaddr: socket::sockaddr { 377 | sa_family: 0, 378 | sa_data: [0; 14], 379 | }, 380 | }; 381 | 382 | copy_slice(&mut req.ifr_name, self.name.as_bytes()); 383 | 384 | let res = unsafe { ioctl(self.sock, SIOCGIFHWADDR, &mut req) }; 385 | if res < 0 { 386 | return Err(InterfacesError::last_os_error()); 387 | } 388 | 389 | let mut addr = [0; 6]; 390 | for i in 0..6 { 391 | addr[i] = req.ifr_hwaddr.sa_data[i]; 392 | } 393 | 394 | // Hardware addresses are `i8`s on some Linux versions for some reason? 395 | let addr = unsafe { mem::transmute::<[_; 6], [u8; 6]>(addr) }; 396 | Ok(HardwareAddr(addr)) 397 | } 398 | 399 | #[cfg(target_os = "macos")] 400 | #[allow(non_snake_case)] 401 | fn hardware_addr_impl(&self) -> Result { 402 | // We need certain constants - get them now. 403 | let AF_LINK = match constants::get_constant("AF_LINK") { 404 | Some(c) => c as i32, 405 | None => return Err(InterfacesError::NotSupported("AF_LINK")), 406 | }; 407 | 408 | // Walk all interfaces looking for one that is the right type and name. We: 409 | // - Get the name from this interface 410 | // - Filter only where the name == ours 411 | // - Get only AF_LINK interfaces 412 | let mut it = IfAddrIterator::new()? 413 | .filter_map(|cur| { 414 | if let Some(name) = convert_ifaddr_name(cur) { 415 | Some((name, cur)) 416 | } else { 417 | None 418 | } 419 | }) 420 | .filter(|&(ref name, _)| name == &self.name) 421 | .filter(|&(_, ifa)| { 422 | let ifa = unsafe { &mut *ifa }; 423 | let family = unsafe { *ifa.ifa_addr }.sa_family as i32; 424 | family == AF_LINK 425 | }); 426 | 427 | let link_if = match it.next() { 428 | Some((_, ifa)) => ifa, 429 | None => return Err(InterfacesError::NotSupported("No AF_LINK")), 430 | }; 431 | 432 | let mut addr = [0; 6]; 433 | let mut pr = unsafe { ffi::rust_LLADDR(link_if) }; 434 | 435 | for i in 0..6 { 436 | addr[i] = unsafe { *pr }; 437 | pr = ((pr as usize) + 1) as *const u8; 438 | } 439 | 440 | drop(it); 441 | Ok(HardwareAddr(addr)) 442 | } 443 | 444 | #[cfg(not(any(target_os = "linux", target_os = "macos")))] 445 | fn hardware_addr_impl(&self) -> Result { 446 | Err(InterfacesError::NotSupported("Unknown OS")) 447 | } 448 | 449 | /// Sets the interface as up or down. This will change the status of the given interface in 450 | /// the system, and update the flags of this `Interface` instance. 451 | #[allow(non_snake_case)] 452 | pub fn set_up(&mut self, up: bool) -> Result<()> { 453 | // We need these IOCTLs in order to get/set the interface flags. 454 | let SIOCGIFFLAGS = match constants::get_constant("SIOCGIFFLAGS") { 455 | Some(c) => c, 456 | None => return Err(InterfacesError::NotSupported("SIOCGIFFLAGS")), 457 | }; 458 | let SIOCSIFFLAGS = match constants::get_constant("SIOCSIFFLAGS") { 459 | Some(c) => c, 460 | None => return Err(InterfacesError::NotSupported("SIOCSIFFLAGS")), 461 | }; 462 | 463 | let mut req = ffi::ifreq_with_flags { 464 | ifr_name: [0; ffi::IFNAMSIZ], 465 | ifr_flags: 0, 466 | }; 467 | 468 | copy_slice(&mut req.ifr_name, self.name.as_bytes()); 469 | 470 | // Get the existing flags. 471 | let res = unsafe { ioctl(self.sock, SIOCGIFFLAGS, &mut req) }; 472 | if res < 0 { 473 | let err = InterfacesError::last_os_error(); 474 | return Err(err); 475 | } 476 | 477 | // Depending on our up/down, clear IFF_UP. 478 | // NOTE: we don't want to convert this to/from an InterfaceFlags variable, since that will 479 | // strip out any unknown bits (which we don't want). So, we just use good old bitwise 480 | // operators to set/clear the flags. 481 | let flag_val = InterfaceFlags::IFF_UP.bits() as u16; 482 | req.ifr_flags = if up { 483 | req.ifr_flags | flag_val 484 | } else { 485 | req.ifr_flags & (!flag_val) 486 | }; 487 | 488 | // Set the flags back. 489 | let res = unsafe { ioctl(self.sock, SIOCSIFFLAGS, &mut req) }; 490 | if res < 0 { 491 | return Err(InterfacesError::last_os_error()); 492 | } 493 | 494 | // Update our flags to represent the new state. 495 | self.flags = InterfaceFlags::from_bits_truncate(req.ifr_flags as u32); 496 | 497 | Ok(()) 498 | } 499 | 500 | /// Retrieve the MTU of this interface. 501 | #[allow(non_snake_case)] 502 | pub fn get_mtu(&self) -> Result { 503 | let SIOCGIFMTU = match constants::get_constant("SIOCGIFMTU") { 504 | Some(c) => c, 505 | None => return Err(InterfacesError::NotSupported("SIOCGIFMTU")), 506 | }; 507 | 508 | let mut req = ffi::ifreq_with_mtu { 509 | ifr_name: [0; ffi::IFNAMSIZ], 510 | ifr_mtu: 0, 511 | }; 512 | 513 | copy_slice(&mut req.ifr_name, self.name.as_bytes()); 514 | 515 | let res = unsafe { ioctl(self.sock, SIOCGIFMTU, &mut req) }; 516 | if res < 0 { 517 | return Err(InterfacesError::last_os_error()); 518 | } 519 | 520 | Ok(req.ifr_mtu as u32) 521 | } 522 | } 523 | 524 | fn convert_ifaddr_name(ifa: *mut ffi::ifaddrs) -> Option { 525 | let ifa = unsafe { &mut *ifa }; 526 | match unsafe { CStr::from_ptr(ifa.ifa_name).to_str() } { 527 | Ok(s) => Some(s.to_string()), 528 | Err(_) => None, 529 | } 530 | } 531 | 532 | // This is a bit scary, but the various address families are different from platform to platform, 533 | // and also from OS version to OS version. Essentially, we have a couple of families that we know 534 | // about (IPv4, IPv6, etc.), and a couple that we determined at build time by compiling some C code 535 | // that tried to include the value of the AF_* constant. For each of these, we try getting the 536 | // corresponding constant, and then verify if it matches. 537 | fn convert_ifaddr_family(family: i32) -> Kind { 538 | // Helper macro! 539 | macro_rules! check_family { 540 | ($cc:tt -> $ty:ident) => { 541 | if let Some(val) = constants::get_constant(stringify!($cc)) { 542 | if family == val as i32 { 543 | return Kind::$ty; 544 | } 545 | } 546 | }; 547 | } 548 | 549 | check_family!(AF_PACKET -> Packet); 550 | check_family!(AF_LINK -> Link); 551 | 552 | match family { 553 | libc::AF_INET => Kind::Ipv4, 554 | libc::AF_INET6 => Kind::Ipv6, 555 | val => Kind::Unknown(val), 556 | } 557 | } 558 | 559 | fn convert_ifaddr_address(ifa: *mut ffi::ifaddrs) -> Option
{ 560 | let ifa = unsafe { &mut *ifa }; 561 | 562 | let kind = if ifa.ifa_addr != ptr::null_mut() { 563 | let fam = unsafe { *ifa.ifa_addr }.sa_family as i32; 564 | convert_ifaddr_family(fam) 565 | } else { 566 | return None; 567 | }; 568 | 569 | let addr = ffi::convert_sockaddr(ifa.ifa_addr); 570 | 571 | let mask = ffi::convert_sockaddr(ifa.ifa_netmask); 572 | 573 | let flags = InterfaceFlags::from_bits_truncate(ifa.ifa_flags); 574 | let hop = if flags.contains(InterfaceFlags::IFF_BROADCAST) { 575 | match ffi::convert_sockaddr(ifa.ifa_ifu.ifu_broadaddr()) { 576 | Some(x) => Some(NextHop::Broadcast(x)), 577 | None => None, 578 | } 579 | } else { 580 | match ffi::convert_sockaddr(ifa.ifa_ifu.ifu_dstaddr()) { 581 | Some(x) => Some(NextHop::Destination(x)), 582 | None => None, 583 | } 584 | }; 585 | 586 | Some(Address { 587 | kind: kind, 588 | addr: addr, 589 | mask: mask, 590 | hop: hop, 591 | }) 592 | } 593 | 594 | impl PartialEq for Interface { 595 | fn eq(&self, other: &Interface) -> bool { 596 | self.name == other.name 597 | } 598 | } 599 | 600 | impl Eq for Interface {} 601 | 602 | impl Drop for Interface { 603 | fn drop(&mut self) { 604 | let sock = mem::replace(&mut self.sock, 0); 605 | unsafe { close(sock) }; 606 | } 607 | } 608 | 609 | // Helper function 610 | fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize { 611 | let mut c = 0; 612 | 613 | for (d, s) in dst.iter_mut().zip(src.iter()) { 614 | *d = *s; 615 | c += 1; 616 | } 617 | 618 | c 619 | } 620 | 621 | #[cfg(test)] 622 | mod tests { 623 | use super::*; 624 | use std::hash::Hash; 625 | 626 | #[test] 627 | fn test_interface_is_comparable() { 628 | let ifs = Interface::get_all().unwrap(); 629 | 630 | assert!(ifs[0] == ifs[0]); 631 | } 632 | 633 | #[test] 634 | fn test_hardwareaddr_deriving() { 635 | let one = HardwareAddr::zero(); 636 | let two = HardwareAddr::zero(); 637 | 638 | assert!(one == two); 639 | assert_is_clone(&one); 640 | assert_is_copy(&one); 641 | assert_is_hash(&one); 642 | } 643 | 644 | #[test] 645 | fn test_hardwareaddr_format() { 646 | let h = HardwareAddr::zero(); 647 | 648 | assert_eq!(h.as_string(), "00:00:00:00:00:00"); 649 | assert_eq!(h.as_bare_string(), "000000000000"); 650 | } 651 | 652 | fn assert_is_clone(_: &T) {} 653 | fn assert_is_copy(_: &T) {} 654 | fn assert_is_hash(_: &T) {} 655 | } 656 | --------------------------------------------------------------------------------