1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! The crate provides a way to get the link information of the interface, including
//! the port type, supported modes.
//! The crate is based on the ioctl command, so it can only be used on Linux.
//!
//! Examples
//! ----------------
//! List all the interfaces' ethtool related information.
//! ```
//! use ethernet_info::get_ethernet_info;
//! let interfaces_eth_info = get_ethernet_info(None);
//! for interface_info in interfaces_eth_info {
//!     println!("interface: {}", interface_info.name());
//!     println!("Port: {}", interface_info.port());
//!     println!("Supported Ports: {:?}", interface_info.ports());
//!     println!("Supported: {:?}", interface_info.supported());
//! }
//! ```
//!
//! Get the ethtool related information of the specified interface.
//! ```
//! use ethernet_info::get_ethernet_info;
//! let interfaces_eth_info = get_ethernet_info(Some("enp1s0"));
//! for interface_info in interfaces_eth_info {
//!     println!("interface: {}", interface_info.name());
//!     println!("Port: {}", interface_info.port());
//!     println!("Supported Ports: {:?}", interface_info.ports());
//!     println!("Supported: {:?}", interface_info.supported());
//! }
//! ```
//!
//! Get the ethtool related of the specified interface by EthernetInfo.
//! ```
//! use ethernet_info::EthernetInfo;
//! if let Ok(interface_info) = EthernetInfo::try_from("enp1s0") {
//!     println!("interface: {}", interface_info.name());
//!     println!("Port: {}", interface_info.port());
//!     println!("Supported Ports: {:?}", interface_info.ports());
//!     println!("Supported: {:?}", interface_info.supported());
//! }
//! ```
#![allow(non_upper_case_globals)]

#[macro_use]
extern crate nix;

mod errors;
mod ethtool_const;
mod internal;
mod settings_parser;

use crate::ethtool_const::*;
use crate::settings_parser::SettingsParser;
use internal::{CmdContext, EthtoolCommnad};

pub use errors::EthtoolError;

/// The port information includes the port type, supported modes.
#[derive(Debug, Clone)]
pub struct EthernetInfo {
    name: String,
    port: EthtoolPort,
    ports: Vec<EthtoolPortBits>,
    supported: Vec<EthtoolLinkModeBits>,
    advertised: Vec<EthtoolLinkModeBits>,
}

impl EthernetInfo {
    /// Create a EthernetInfo from the SettingsParser
    pub fn from_settings_parser(name: &str, settings_parser: SettingsParser) -> Self {
        let supported = settings_parser.supported_link_modes();
        let advertised = settings_parser.advertised_link_modes();
        EthernetInfo {
            name: name.to_string(),
            port: settings_parser.port(),
            ports: settings_parser.supported_ports(),
            advertised,
            supported,
        }
    }

    /// Get the name of the interface
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the port type of the interface
    pub fn port(&self) -> EthtoolPort {
        self.port
    }

    pub fn ports(&self) -> &Vec<EthtoolPortBits> {
        &self.ports
    }

    /// Get the supported modes of the interface
    pub fn supported(&self) -> &Vec<EthtoolLinkModeBits> {
        &self.supported
    }

    pub fn advertised(&self) -> &Vec<EthtoolLinkModeBits> {
        &self.advertised
    }
}

impl TryFrom<&str> for EthernetInfo {
    type Error = EthtoolError;

    /// Create a EthernetInfo from the interface name
    fn try_from(name: &str) -> Result<Self, Self::Error> {
        let ctx = CmdContext::new(name)?;
        let ethernet_info = do_ioctl_get_ethernet_info(ctx)?;
        Ok(ethernet_info)
    }
}

/// Use ioctl to get the port information of the interface
///
/// The main steps are defined in do_ioctl_ethtool_glinksettings() in ethtool.c
fn do_ioctl_get_ethernet_info(mut ctx: CmdContext) -> Result<EthernetInfo, EthtoolError> {
    let mut ecmd = EthtoolCommnad::new(ETHTOOL_GLINKSETTINGS)?;
    /* Handshake with kernel to determine number of words for link
     * mode bitmaps. When requested number of bitmap words is not
     * the one expected by kernel, the latter returns the integer
     * opposite of what it is expecting. We request length 0 below
     * (aka. invalid bitmap length) to get this info.
     */
    ctx = ctx.send_ioctl(ecmd)?;
    ecmd = ctx.get_ethtool_link_settings();
    if ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS {
        return Err(EthtoolError::new(
            "Failed to determine number of words for link mode bitmaps",
        ));
    }

    /* got the real ecmd.req.link_mode_masks_nwords,
     * now send the real request
     */
    ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
    ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
    ctx = ctx.send_ioctl(ecmd)?;
    ecmd = ctx.get_ethtool_link_settings();

    /* check the link_mode_masks_nwords again */
    if ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS {
        return Err(EthtoolError::new(
            "Failed to check the link_mode_masks_nwords.",
        ));
    }

    ctx.close_socket();

    Ok(ecmd.into_ethernet_info(ctx.ifname()))
}

/// Get the ethtool related information of the interface
/// If devname is None, get all the interfaces' ethtool related information.
/// If devname is Some(&str), get the specified interface's ethtool related information.
pub fn get_ethernet_info(devname: Option<&str>) -> Vec<EthernetInfo> {
    let mut ethernet_info_vec = Vec::new();
    if let Some(devname) = devname {
        let ethernet_info = EthernetInfo::try_from(devname);
        if let Ok(ethernet_info) = ethernet_info {
            ethernet_info_vec.push(ethernet_info);
        }
    } else if let Ok(interfaces) = nix::net::if_::if_nameindex() {
        for iface in interfaces.iter() {
            if let Ok(ethernet_info) =
                EthernetInfo::try_from(iface.name().to_string_lossy().as_ref())
            {
                ethernet_info_vec.push(ethernet_info);
            }
        }
    }
    ethernet_info_vec
}