sysinfo 0.40.0

Library to get system information such as processes, CPUs, disks, components and networks
Documentation
// Take a look at the license at the top of the repository in the LICENSE file.

use std::collections::{HashMap, hash_map};
use std::ffi::{OsString, c_char};
use std::mem::{MaybeUninit, size_of};
use std::os::unix::ffi::OsStringExt;
use std::ptr::null_mut;

use libc::{
    CTL_NET, IFDATA_GENERAL, IFF_RUNNING, IFF_UP, IFMIB_IFDATA, IFNAMSIZ, NET_RT_IFLIST2,
    NETLINK_GENERIC, PF_LINK, PF_ROUTE, RTM_IFINFO2, if_data64, if_indextoname, if_msghdr,
    if_msghdr2, ifmibdata, strlen, sysctl,
};

use crate::{InterfaceOperationalState, MacAddr, NetworkData, NetworkDataInner};

#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::uninit_vec)]
#[allow(clippy::needless_late_init)]
pub(crate) fn update_networks(interfaces: &mut HashMap<OsString, NetworkData>) {
    let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0];
    let mib2 = &mut [
        CTL_NET,
        PF_LINK,
        NETLINK_GENERIC,
        IFMIB_IFDATA,
        0,
        IFDATA_GENERAL,
    ];

    let mut len = 0;
    unsafe {
        if sysctl(
            mib.as_mut_ptr(),
            mib.len() as _,
            null_mut(),
            &mut len,
            null_mut(),
            0,
        ) < 0
        {
            // TODO: might be nice to put an error in here...
            return;
        }
        let mut buf = Vec::with_capacity(len);
        buf.set_len(len);
        if sysctl(
            mib.as_mut_ptr(),
            mib.len() as _,
            buf.as_mut_ptr(),
            &mut len,
            null_mut(),
            0,
        ) < 0
        {
            // TODO: might be nice to put an error in here...
            return;
        }
        let buf = buf.as_ptr() as *const c_char;
        let lim = buf.add(len);
        let mut next = buf;

        while next < lim {
            let ifm = next as *const if_msghdr;
            next = next.offset((*ifm).ifm_msglen as isize);
            if (*ifm).ifm_type == RTM_IFINFO2 as u8 {
                // The interface (line description) name stored at ifname will be returned in
                // the default coded character set identifier (CCSID) currently in effect for
                // the job. If this is not a single byte CCSID, then storage greater than
                // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs.
                let mut name = vec![0u8; IFNAMSIZ + 6];

                let if2m: *const if_msghdr2 = ifm as *const if_msghdr2;
                let pname = if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _);
                if pname.is_null() {
                    continue;
                }
                name.set_len(strlen(pname));
                let name = OsString::from_vec(name);
                let mtu = (*if2m).ifm_data.ifi_mtu as u64;

                let baudrate = (*if2m).ifm_data.ifi_baudrate;
                let link_speed = if baudrate != 0 { Some(baudrate) } else { None };

                // FIXME: the documentation I could find was rather spars and unclear, are these the right flags?
                let operational_state = InterfaceOperationalState::from_flags((*if2m).ifm_flags);

                // Because data size is capped at 32 bits with the previous sysctl call for some
                // reasons, we need to make another sysctl call to get the actual values
                // we originally got into `ifm.ifm_data`...
                //
                // Issue: https://github.com/GuillaumeGomez/sysinfo/issues/1378
                let mut mib_data: MaybeUninit<ifmibdata> = MaybeUninit::uninit();

                mib2[4] = (*if2m).ifm_index as _;
                let ret = sysctl(
                    mib2.as_mut_ptr(),
                    mib2.len() as _,
                    mib_data.as_mut_ptr() as *mut _,
                    &mut size_of::<ifmibdata>(),
                    null_mut(),
                    0,
                );

                match interfaces.entry(name) {
                    hash_map::Entry::Occupied(mut e) => {
                        let interface = e.get_mut();
                        let interface = &mut interface.inner;

                        if ret < 0 {
                            sysinfo_debug!(
                                "Cannot get network interface data usage: sysctl failed: {ret}"
                            );
                        } else {
                            let data = mib_data.assume_init();
                            update_network_data(interface, &data.ifmd_data);
                        }
                        interface.mtu = mtu;
                        interface.operational_state = operational_state;
                        interface.updated = true;
                        interface.link_speed = link_speed;
                    }
                    hash_map::Entry::Vacant(e) => {
                        let current_in;
                        let current_out;
                        let packets_in;
                        let packets_out;
                        let errors_in;
                        let errors_out;

                        if ret < 0 {
                            sysinfo_debug!(
                                "Cannot get network interface data usage: sysctl failed: {ret}"
                            );

                            current_in = 0;
                            current_out = 0;
                            packets_in = 0;
                            packets_out = 0;
                            errors_in = 0;
                            errors_out = 0;
                        } else {
                            let data = mib_data.assume_init();
                            let data = data.ifmd_data;

                            current_in = data.ifi_ibytes;
                            current_out = data.ifi_obytes;
                            packets_in = data.ifi_ipackets;
                            packets_out = data.ifi_opackets;
                            errors_in = data.ifi_ierrors;
                            errors_out = data.ifi_oerrors;
                        }

                        e.insert(NetworkData {
                            inner: NetworkDataInner {
                                current_in,
                                old_in: current_in,
                                current_out,
                                old_out: current_out,
                                packets_in,
                                old_packets_in: packets_in,
                                packets_out,
                                old_packets_out: packets_out,
                                errors_in,
                                old_errors_in: errors_in,
                                errors_out,
                                old_errors_out: errors_out,
                                updated: true,
                                mac_addr: MacAddr::UNSPECIFIED,
                                ip_networks: vec![],
                                mtu,
                                link_speed,
                                operational_state,
                            },
                        });
                    }
                }
            }
        }
    }
}

#[inline]
fn update_field(old_field: &mut u64, new_field: &mut u64, value: u64) {
    *old_field = *new_field;
    *new_field = value;
}

fn update_network_data(inner: &mut NetworkDataInner, data: &if_data64) {
    update_field(&mut inner.old_out, &mut inner.current_out, data.ifi_obytes);
    update_field(&mut inner.old_in, &mut inner.current_in, data.ifi_ibytes);

    update_field(
        &mut inner.old_packets_out,
        &mut inner.packets_out,
        data.ifi_opackets,
    );
    update_field(
        &mut inner.old_packets_in,
        &mut inner.packets_in,
        data.ifi_ipackets,
    );

    update_field(
        &mut inner.old_errors_in,
        &mut inner.errors_in,
        data.ifi_ierrors,
    );
    update_field(
        &mut inner.old_errors_out,
        &mut inner.errors_out,
        data.ifi_oerrors,
    );
}

impl InterfaceOperationalState {
    fn from_flags(flags: core::ffi::c_int) -> Self {
        // based on the bsd version in src\unix\bsd\network_common.rs
        if (flags & IFF_RUNNING) != 0 {
            if (flags & IFF_UP) != 0 {
                InterfaceOperationalState::Up
            } else {
                InterfaceOperationalState::Dormant
            }
        } else {
            InterfaceOperationalState::Down
        }
    }
}