Skip to main content

nic_port_info/
lib.rs

1//! The crate provides a way to get the link information of the NIC, including
2//! the port type, supported modes.
3//! The crate is based on the ioctl command, so it can only be used on Linux.
4//!
5//! # Examples
6//! List all the NICs' port information.
7//! ```
8//! use ethtool::get_nic_port_info;
9//! let nics_port = get_nic_port_info(None);
10//! for nic_port in nics_port {
11//!    println!("NIC: {}", nic_port.name());
12//!    println!("Port: {}", nic_port.port());
13//!    println!("Supported: {:?}", nic_port.supported());
14//! }
15//! ```
16//!
17//! Get the port information of the specified NIC.
18//! ```
19//! use ethtool::get_nic_port_info;
20//! let nics_port = get_nic_port_info(Some("eth0"));
21//! for nic_port in nics_port {
22//!   println!("NIC: {}", nic_port.name());
23//!   println!("Port: {}", nic_port.port());
24//!   println!("Supported: {:?}", nic_port.supported());
25//! }
26//! ```
27//!
28//! Get the port information of the specified NIC by PortInfo.
29//! ```
30//! use ethtool::PortInfo;
31//! let nic_port = PortInfo::from_name("eth0").unwrap();
32//! println!("NIC: {}", nic_port.name());
33//! println!("Port: {}", nic_port.port());
34//! println!("Supported: {:?}", nic_port.supported());
35//! ```
36#![allow(non_upper_case_globals)]
37
38#[macro_use]
39extern crate nix;
40
41mod errors;
42mod ethtool_const;
43mod internal;
44mod settings_parser;
45
46use crate::ethtool_const::*;
47use crate::settings_parser::SettingsParser;
48use internal::{CmdContext, EthtoolCommnad};
49
50pub use errors::EthtoolError;
51
52/// The port information includes the port type, supported modes.
53#[derive(Default, Debug, Clone)]
54pub struct PortInfo {
55    name: String,
56    port: String,
57    supported: Vec<String>,
58}
59
60impl PortInfo {
61    /// Create a PortInfo from the SettingsParser
62    pub fn from_settings_parser(name: &str, settings_parser: SettingsParser) -> Self {
63        let supported = settings_parser.supported_link_modes();
64        PortInfo {
65            name: name.to_string(),
66            port: settings_parser.port(),
67            supported,
68        }
69    }
70
71    /// Get the name of the NIC
72    pub fn name(&self) -> &str {
73        &self.name
74    }
75
76    /// Get the port type of the NIC
77    pub fn port(&self) -> &str {
78        &self.port
79    }
80
81    /// Get the supported modes of the NIC
82    pub fn supported(&self) -> &Vec<String> {
83        &self.supported
84    }
85
86    /// Create a PortInfo from the NIC name
87    pub fn from_name(name: &str) -> Result<Self, EthtoolError> {
88        let ctx = CmdContext::new(name)?;
89        let port = do_ioctl_get_nic_port(ctx)?;
90        Ok(port)
91    }
92}
93
94
95/// Use ioctl to get the port information of the NIC
96///
97/// The main steps are defined in do_ioctl_ethtool_glinksettings() in ethtool.c
98fn do_ioctl_get_nic_port(mut ctx: CmdContext) -> Result<PortInfo, EthtoolError> {
99    let mut ecmd = EthtoolCommnad::new(ETHTOOL_GLINKSETTINGS)?;
100    /* Handshake with kernel to determine number of words for link
101     * mode bitmaps. When requested number of bitmap words is not
102     * the one expected by kernel, the latter returns the integer
103     * opposite of what it is expecting. We request length 0 below
104     * (aka. invalid bitmap length) to get this info.
105     */
106    ctx = ctx.send_ioctl(ecmd)?;
107    ecmd = ctx.get_ethtool_link_settings();
108    if ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS {
109        return Err(EthtoolError::new(
110            "Failed to determine number of words for link mode bitmaps",
111        ));
112    }
113
114    /* got the real ecmd.req.link_mode_masks_nwords,
115     * now send the real request
116     */
117    ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
118    ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
119    ctx = ctx.send_ioctl(ecmd)?;
120    ecmd = ctx.get_ethtool_link_settings();
121
122    /* check the link_mode_masks_nwords again */
123    if ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS {
124        return Err(EthtoolError::new(
125            "Failed to check the link_mode_masks_nwords.",
126        ));
127    }
128
129    ctx.close_socket();
130
131    Ok(ecmd.into_port(ctx.ifname()))
132}
133
134
135/// Get the port information of the NIC
136/// If devname is None, get all the NICs' port information.
137/// If devname is Some(&str), get the specified NIC's port information.
138pub fn get_nic_port_info(devname: Option<&str>) -> Vec<PortInfo> {
139    let mut nics_port = Vec::new();
140    if let Some(devname) = devname {
141        let port_info = PortInfo::from_name(devname);
142        if let Ok(port_info) = port_info {
143            nics_port.push(port_info);
144        }
145    } else {
146        let mut add_ports = Vec::new();
147        if let Ok(ifaddrs) = nix::ifaddrs::getifaddrs() {
148            ifaddrs.for_each(|ifaddr| {
149                if let Ok(nic_port) = PortInfo::from_name(&ifaddr.interface_name) {
150                    if !add_ports.contains(&nic_port.name) {
151                        add_ports.push(nic_port.name.clone());
152                        nics_port.push(nic_port);
153                    }
154                }
155            });
156        }
157    }
158    nics_port
159}