ipconfig 0.3.2

Get network adapters information and network configuration for windows.
Documentation
#![allow(clippy::cast_ptr_alignment)]

use std;
use std::convert::TryFrom;
use std::ffi::CStr;
use std::net::IpAddr;

use crate::error::*;
use socket2;
use widestring::WideCString;
use windows_sys::Win32::Foundation::ERROR_BUFFER_OVERFLOW;
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
use windows_sys::Win32::Networking::WinSock::AF_UNSPEC;

use crate::bindings::*;

/// Represent an operational status of the adapter
/// See IP_ADAPTER_ADDRESSES docs for more details
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OperStatus {
    IfOperStatusUp = 1,
    IfOperStatusDown = 2,
    IfOperStatusTesting = 3,
    IfOperStatusUnknown = 4,
    IfOperStatusDormant = 5,
    IfOperStatusNotPresent = 6,
    IfOperStatusLowerLayerDown = 7,
}

/// Represent an interface type
/// See IANA docs on iftype for more details
/// <https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib>
/// Note that we only support a subset of the IANA interface
/// types and in case the adapter has an unsupported type,
/// `IfType::Unsupported` is used. `IfType::Other`
/// is different from `IfType::Unsupported`, as the former
/// one is defined by the IANA itself.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IfType {
    Other = 1,
    EthernetCsmacd = 6,
    Iso88025Tokenring = 9,
    Ppp = 23,
    SoftwareLoopback = 24,
    Atm = 37,
    Ieee80211 = 71,
    Tunnel = 131,
    Ieee1394 = 144,
    Unsupported,
    /// This enum may grow additional variants, so this makes sure clients
    /// don't count on exhaustive matching. (Otherwise, adding a new variant
    /// could break existing code.)
    #[doc(hidden)]
    __Nonexhaustive,
}

/// Represent an adapter.
#[derive(Debug)]
pub struct Adapter {
    adapter_name: String,
    ip_addresses: Vec<IpAddr>,
    prefixes: Vec<(IpAddr, u32)>,
    gateways: Vec<IpAddr>,
    dns_servers: Vec<IpAddr>,
    description: String,
    friendly_name: String,
    physical_address: Option<Vec<u8>>,
    receive_link_speed: u64,
    transmit_link_speed: u64,
    oper_status: OperStatus,
    if_type: IfType,
    ipv6_if_index: u32,
    ipv4_metric: u32,
    ipv6_metric: u32,
}

impl Adapter {
    /// Get the adapter's name
    pub fn adapter_name(&self) -> &str {
        &self.adapter_name
    }
    /// Get the adapter's ip addresses (unicast ip addresses)
    pub fn ip_addresses(&self) -> &[IpAddr] {
        &self.ip_addresses
    }
    /// Get the adapter's prefixes. Returns a list of tuples (IpAddr, u32),
    /// where first element is a subnet address, e.g. 192.168.1.0
    /// and second element is prefix length, e.g. 24
    pub fn prefixes(&self) -> &[(IpAddr, u32)] {
        &self.prefixes
    }
    /// Get the adapter's gateways
    pub fn gateways(&self) -> &[IpAddr] {
        &self.gateways
    }
    /// Get the adapter's dns servers (the preferred dns server is first)
    pub fn dns_servers(&self) -> &[IpAddr] {
        &self.dns_servers
    }
    /// Get the adapter's description
    pub fn description(&self) -> &str {
        &self.description
    }
    /// Get the adapter's friendly name
    pub fn friendly_name(&self) -> &str {
        &self.friendly_name
    }
    /// Get the adapter's physical (MAC) address
    pub fn physical_address(&self) -> Option<&[u8]> {
        self.physical_address.as_ref().map(std::vec::Vec::as_slice)
    }

    /// Get the adapter Recieve Link Speed (bits per second)
    pub fn receive_link_speed(&self) -> u64 {
        self.receive_link_speed
    }

    /// Get the Trasnmit Link Speed (bits per second)
    pub fn transmit_link_speed(&self) -> u64 {
        self.transmit_link_speed
    }

    /// Check if the adapter is up (OperStatus is IfOperStatusUp)
    pub fn oper_status(&self) -> OperStatus {
        self.oper_status
    }

    /// Get the interface type
    pub fn if_type(&self) -> IfType {
        self.if_type
    }

    /// Get the IPv6 interface index.
    ///
    /// The return value can be used as an IPv6 scope id for link-local
    /// addresses.
    pub fn ipv6_if_index(&self) -> u32 {
        self.ipv6_if_index
    }

    /// Returns the metric used to compute route preference for IPv4
    pub fn ipv4_metric(&self) -> u32 {
        self.ipv4_metric
    }
    /// Returns the metric used to compute route preference for IPv6
    pub fn ipv6_metric(&self) -> u32 {
        self.ipv6_metric
    }
}

/// Get all the network adapters on this machine.
pub fn get_adapters() -> Result<Vec<Adapter>> {
    unsafe {
        // Preallocate 16K per Microsoft recommendation, see Remarks section
        // https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
        let mut buf_len: ULONG = 16384;
        let mut adapters_addresses_buffer = Vec::new();

        let mut result = ERROR_BUFFER_OVERFLOW;
        while result == ERROR_BUFFER_OVERFLOW {
            adapters_addresses_buffer.resize(buf_len as usize, 0);

            result = GetAdaptersAddresses(
                AF_UNSPEC as u32,
                0x0080 | 0x0010, //GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_INCLUDE_PREFIX,
                std::ptr::null_mut(),
                adapters_addresses_buffer.as_mut_ptr() as PIP_ADAPTER_ADDRESSES,
                &mut buf_len as *mut ULONG,
            );
        }

        if result != ERROR_SUCCESS {
            return Err(Error {
                kind: ErrorKind::Os(result),
            });
        }

        let mut adapters = vec![];
        let mut adapter_addresses_ptr =
            adapters_addresses_buffer.as_mut_ptr() as PIP_ADAPTER_ADDRESSES;

        while !adapter_addresses_ptr.is_null() {
            adapters.push(get_adapter(adapter_addresses_ptr)?);
            adapter_addresses_ptr = adapter_addresses_ptr.read_unaligned().Next;
        }

        Ok(adapters)
    }
}

unsafe fn get_adapter(adapter_addresses_ptr: PIP_ADAPTER_ADDRESSES) -> Result<Adapter> {
    let adapter_addresses = adapter_addresses_ptr.read_unaligned();
    let adapter_name = CStr::from_ptr(adapter_addresses.AdapterName)
        .to_str()?
        .to_owned();
    let dns_servers = get_dns_servers(adapter_addresses.FirstDnsServerAddress)?;
    let gateways = get_gateways(adapter_addresses.FirstGatewayAddress)?;
    let prefixes = get_prefixes(adapter_addresses.FirstPrefix)?;
    let unicast_addresses = get_unicast_addresses(adapter_addresses.FirstUnicastAddress)?;
    let receive_link_speed: u64 = adapter_addresses.ReceiveLinkSpeed;
    let transmit_link_speed: u64 = adapter_addresses.TransmitLinkSpeed;
    let ipv4_metric = adapter_addresses.Ipv4Metric;
    let ipv6_metric = adapter_addresses.Ipv6Metric;
    let oper_status = match adapter_addresses.OperStatus {
        1 => OperStatus::IfOperStatusUp,
        2 => OperStatus::IfOperStatusDown,
        3 => OperStatus::IfOperStatusTesting,
        4 => OperStatus::IfOperStatusUnknown,
        5 => OperStatus::IfOperStatusDormant,
        6 => OperStatus::IfOperStatusNotPresent,
        7 => OperStatus::IfOperStatusLowerLayerDown,
        v => {
            panic!("unexpected OperStatus value: {}", v);
        }
    };
    let if_type = match adapter_addresses.IfType {
        1 => IfType::Other,
        6 => IfType::EthernetCsmacd,
        9 => IfType::Iso88025Tokenring,
        23 => IfType::Ppp,
        24 => IfType::SoftwareLoopback,
        37 => IfType::Atm,
        71 => IfType::Ieee80211,
        131 => IfType::Tunnel,
        144 => IfType::Ieee1394,
        _ => IfType::Unsupported,
    };
    let ipv6_if_index = adapter_addresses.Ipv6IfIndex;

    let description = WideCString::from_ptr_str(adapter_addresses.Description).to_string()?;
    let friendly_name = WideCString::from_ptr_str(adapter_addresses.FriendlyName).to_string()?;
    let physical_address = if adapter_addresses.PhysicalAddressLength == 0 {
        None
    } else {
        Some(
            adapter_addresses.PhysicalAddress[..adapter_addresses.PhysicalAddressLength as usize]
                .to_vec(),
        )
    };
    Ok(Adapter {
        adapter_name,
        ip_addresses: unicast_addresses,
        prefixes,
        gateways,
        dns_servers,
        description,
        friendly_name,
        physical_address,
        receive_link_speed,
        transmit_link_speed,
        oper_status,
        if_type,
        ipv6_if_index,
        ipv4_metric,
        ipv6_metric,
    })
}

unsafe fn socket_address_to_ipaddr(socket_address: &SOCKET_ADDRESS) -> IpAddr {
    let (_, sockaddr) = socket2::SockAddr::try_init(|storage, length| {
        let sockaddr_length = usize::try_from(socket_address.iSockaddrLength).unwrap();
        assert!(sockaddr_length <= std::mem::size_of_val(&storage.read_unaligned()));
        let dst: *mut u8 = storage.cast();
        let src: *const u8 = socket_address.lpSockaddr.cast();
        dst.copy_from_nonoverlapping(src, sockaddr_length);
        std::ptr::write_unaligned(length, socket_address.iSockaddrLength);
        Ok(())
    })
    .unwrap();

    sockaddr.as_socket().map(|s| s.ip()).unwrap()
}

unsafe fn get_dns_servers(
    mut dns_server_ptr: PIP_ADAPTER_DNS_SERVER_ADDRESS_XP,
) -> Result<Vec<IpAddr>> {
    let mut dns_servers = vec![];

    while !dns_server_ptr.is_null() {
        let dns_server = dns_server_ptr.read_unaligned();
        let ipaddr = socket_address_to_ipaddr(&dns_server.Address);
        dns_servers.push(ipaddr);

        dns_server_ptr = dns_server.Next;
    }

    Ok(dns_servers)
}

unsafe fn get_gateways(mut gateway_ptr: PIP_ADAPTER_GATEWAY_ADDRESS_LH) -> Result<Vec<IpAddr>> {
    let mut gateways = vec![];

    while !gateway_ptr.is_null() {
        let gateway = gateway_ptr.read_unaligned();
        let ipaddr = socket_address_to_ipaddr(&gateway.Address);
        gateways.push(ipaddr);

        gateway_ptr = gateway.Next;
    }

    Ok(gateways)
}

unsafe fn get_unicast_addresses(
    mut unicast_addresses_ptr: PIP_ADAPTER_UNICAST_ADDRESS_LH,
) -> Result<Vec<IpAddr>> {
    let mut unicast_addresses = vec![];

    while !unicast_addresses_ptr.is_null() {
        let unicast_address = unicast_addresses_ptr.read_unaligned();
        let ipaddr = socket_address_to_ipaddr(&unicast_address.Address);
        unicast_addresses.push(ipaddr);

        unicast_addresses_ptr = unicast_address.Next;
    }

    Ok(unicast_addresses)
}

unsafe fn get_prefixes(mut prefixes_ptr: PIP_ADAPTER_PREFIX_XP) -> Result<Vec<(IpAddr, u32)>> {
    let mut prefixes = vec![];

    while !prefixes_ptr.is_null() {
        let prefix = prefixes_ptr.read_unaligned();
        let ipaddr = socket_address_to_ipaddr(&prefix.Address);
        prefixes.push((ipaddr, prefix.PrefixLength));

        prefixes_ptr = prefix.Next;
    }

    Ok(prefixes)
}