inherface 0.3.0

Retrieve a system's Network Interfaces on Linux
Documentation
use crate::getifaddrs::getifaddrs;
use crate::{Error, NetworkInterface, Result};
use libc::{AF_INET, AF_INET6, AF_PACKET, c_char, if_nametoindex, strlen};
#[cfg(feature = "mac")]
use mac_address::MacAddress;
use std::collections::HashMap;
#[cfg(feature = "ipv4")]
use std::net::Ipv4Addr;
#[cfg(feature = "ipv6")]
use std::net::Ipv6Addr;
#[cfg(feature = "ipv4")]
use std::ops::{BitOr, Not};
use std::slice::from_raw_parts;

/// Maps the raw data from libc linked list nodes into rust data types
/// and joins the separate chunks of interface data into single `NetworkInterface`s.
pub fn get_interfaces() -> Result<HashMap<String, NetworkInterface>> {
    let mut network_interfaces = HashMap::new();

    for ifaddrs in getifaddrs()? {
        let ifa_addr = ifaddrs.ifa_addr;

        // `ifa_addr` may contain a null pointer according to documentation.
        if ifa_addr.is_null() {
            continue;
        }

        let family = unsafe { (*ifa_addr).sa_family as i32 };
        match family {
            AF_PACKET | AF_INET | AF_INET6 => {}
            _ => continue,
        };

        let raw_name = ifaddrs.ifa_name as *const libc::c_char;
        let name = get_name(raw_name)?;
        let entry = network_interfaces.entry(name).or_insert_with_key(|name| {
            let index = get_index_from_name(raw_name);
            NetworkInterface::new(name.clone(), index)
        });

        match family {
            #[cfg(feature = "mac")]
            AF_PACKET => {
                let mac = make_mac_addrs(&ifaddrs);
                entry.mac_addr = Some(mac);
            }
            #[cfg(feature = "ipv4")]
            AF_INET => {
                let socket_addr = ifa_addr as *mut libc::sockaddr_in;
                let internet_address = unsafe { (*socket_addr).sin_addr };
                let ip = ipv4_from_in_addr(&internet_address);
                let netmask = Netmask::new(&ifaddrs).map(|netmask| netmask.as_v4());
                let broadcast = get_broadcast(ip, netmask);

                entry.add_v4(ip, broadcast, netmask);
            }
            #[cfg(feature = "ipv6")]
            AF_INET6 => {
                let socket_addr = ifa_addr as *mut libc::sockaddr_in6;
                let internet_address = unsafe { (*socket_addr).sin6_addr };
                let ip = Ipv6Addr::from(internet_address.s6_addr);
                let netmask = Netmask::new(&ifaddrs).map(|netmask| netmask.as_v6().unwrap());
                let broadcast = make_ipv6_broadcast_addr(&ifaddrs);

                entry.add_v6(ip, broadcast, netmask);
            }
            _ => {}
        };
    }

    Ok(network_interfaces)
}

/// According to [wikipedia](https://en.wikipedia.org/wiki/Broadcast_address),
/// The directed broadcast address for any IPv4 host can be obtained by taking the bitwise NOT of the subnet mask and then performing a bitwise OR with the host's IP address.
///
/// I have found this method to be more reliable than using ifa_ifu, which will
/// sometimes return the network address instead. It also has the benefit of
/// requiring less unsafe code.
#[cfg(feature = "ipv4")]
fn get_broadcast(addr: Ipv4Addr, netmask: Option<Ipv4Addr>) -> Option<Ipv4Addr> {
    Some(addr.bitor(netmask?.not()))
}

/// Retrieves the network interface name
fn get_name(raw_name: *const c_char) -> Result<String> {
    let len = unsafe { strlen(raw_name) };
    let bytes_slice = unsafe { from_raw_parts(raw_name as *const u8, len) };

    match String::from_utf8(bytes_slice.to_vec()) {
        Ok(s) => Ok(s),
        Err(e) => Err(Error::ParseUtf8Error(e)),
    }
}

/// Returns the index number for the the interface name provided.
/// 0 indicates an error getting the index, 1+ was successful.
///
/// https://man7.org/linux/man-pages/man3/if_nametoindex.3.html
#[inline]
fn get_index_from_name(raw_name: *const c_char) -> u32 {
    unsafe { if_nametoindex(raw_name) }
}

/// Retrieves the broadcast address for the network interface provided of the
/// AF_INET6 family.
#[cfg(feature = "ipv6")]
fn make_ipv6_broadcast_addr(ifaddrs: &libc::ifaddrs) -> Option<Ipv6Addr> {
    let ifa_dstaddr = ifaddrs.ifa_ifu;

    if ifa_dstaddr.is_null() {
        return None;
    }

    let socket_addr = ifa_dstaddr as *mut libc::sockaddr_in6;
    let internet_address = unsafe { (*socket_addr).sin6_addr };
    let addr = Ipv6Addr::from(internet_address.s6_addr);

    Some(addr)
}

#[cfg(feature = "mac")]
fn make_mac_addrs(ifaddrs: &libc::ifaddrs) -> MacAddress {
    let ifa_addr = ifaddrs.ifa_addr;
    let socket_addr = ifa_addr as *mut libc::sockaddr_ll;
    let raw_array = unsafe { (*socket_addr).sll_addr };
    let addr_len = unsafe { (*socket_addr).sll_halen };
    let real_addr_len = std::cmp::min(addr_len as usize, raw_array.len());
    let mac_slice = unsafe { std::slice::from_raw_parts(raw_array.as_ptr(), real_addr_len) };
    let mac_array = mac_slice.try_into().unwrap();

    MacAddress::new(mac_array)
}

/// Creates a `Ipv4Addr` from a (Unix) `in_addr` taking in account
/// the CPU endianess to avoid having twisted IP addresses.
///
/// refer: https://github.com/rust-lang/rust/issues/48819
#[cfg(feature = "ipv4")]
pub fn ipv4_from_in_addr(internet_address: &libc::in_addr) -> Ipv4Addr {
    if cfg!(target_endian = "little") {
        Ipv4Addr::from(internet_address.s_addr.swap_bytes())
    } else {
        Ipv4Addr::from(internet_address.s_addr)
    }
}

pub struct Netmask {
    sockaddr: *mut libc::sockaddr,
}

impl Netmask {
    pub fn new(ifaddrs: &libc::ifaddrs) -> Option<Self> {
        let sockaddr = ifaddrs.ifa_netmask;

        // `ifa_netmask` may contain a null pointer according to documentation.
        if sockaddr.is_null() {
            return None;
        }

        Some(Self { sockaddr })
    }

    /// Retrieves the Netmask from a `ifaddrs` instance for a network interface
    /// from the AF_INET (IPv4) family.
    #[cfg(feature = "ipv4")]
    pub fn as_v4(&self) -> Ipv4Addr {
        let socket_addr = self.sockaddr as *mut libc::sockaddr_in;
        let internet_address = unsafe { (*socket_addr).sin_addr };
        ipv4_from_in_addr(&internet_address)
    }

    /// Retrieves the Netmask from a `ifaddrs` instance for a network interface
    /// from the AF_INET6 (IPv6) family.
    #[cfg(feature = "ipv6")]
    pub fn as_v6(&self) -> Option<Ipv6Addr> {
        let socket_addr = self.sockaddr as *mut libc::sockaddr_in6;
        let internet_address = unsafe { (*socket_addr).sin6_addr };

        //  Ignore local addresses
        if internet_address.s6_addr[0] == 0xfe && internet_address.s6_addr[1] == 0x80 {
            return None;
        }

        let addr = Ipv6Addr::from(internet_address.s6_addr);
        Some(addr)
    }
}

// ---

#[test]
#[cfg(feature = "ipv4")]
fn test_get_broadcast() {
    let broadcast = get_broadcast(
        Ipv4Addr::new(172, 16, 0, 0),
        Some(Ipv4Addr::new(255, 240, 0, 0)),
    )
    .unwrap();
    assert_eq!(broadcast, Ipv4Addr::new(172, 31, 255, 255));
}

#[test]
#[cfg(feature = "ipv4")]
fn test_get_broadcast_without_netmask() {
    let broadcast = get_broadcast(Ipv4Addr::new(172, 16, 0, 0), None);
    assert!(broadcast.is_none());
}