sysinfo/unix/linux/
network.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::collections::{hash_map, HashMap};
4use std::fs::File;
5use std::io::Read;
6use std::path::Path;
7
8use crate::network::refresh_networks_addresses;
9use crate::{IpNetwork, MacAddr, NetworkData};
10
11macro_rules! old_and_new {
12    ($ty_:expr, $name:ident, $old:ident) => {{
13        $ty_.$old = $ty_.$name;
14        $ty_.$name = $name;
15    }};
16    ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{
17        let _tmp = $path;
18        $ty_.$old = $ty_.$name;
19        $ty_.$name = _tmp;
20    }};
21}
22
23#[allow(clippy::ptr_arg)]
24fn read<P: AsRef<Path>>(parent: P, path: &str, data: &mut Vec<u8>) -> u64 {
25    if let Ok(mut f) = File::open(parent.as_ref().join(path)) {
26        if let Ok(size) = f.read(data) {
27            let mut i = 0;
28            let mut ret = 0;
29
30            while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' {
31                ret *= 10;
32                ret += (data[i] - b'0') as u64;
33                i += 1;
34            }
35            return ret;
36        }
37    }
38    0
39}
40
41fn refresh_networks_list_from_sysfs(
42    interfaces: &mut HashMap<String, NetworkData>,
43    remove_not_listed_interfaces: bool,
44    sysfs_net: &Path,
45) {
46    if let Ok(dir) = std::fs::read_dir(sysfs_net) {
47        let mut data = vec![0; 30];
48
49        for stats in interfaces.values_mut() {
50            stats.inner.updated = false;
51        }
52
53        for entry in dir.flatten() {
54            let parent = &entry.path().join("statistics");
55            let entry_path = &entry.path();
56            let entry = match entry.file_name().into_string() {
57                Ok(entry) => entry,
58                Err(_) => continue,
59            };
60            let rx_bytes = read(parent, "rx_bytes", &mut data);
61            let tx_bytes = read(parent, "tx_bytes", &mut data);
62            let rx_packets = read(parent, "rx_packets", &mut data);
63            let tx_packets = read(parent, "tx_packets", &mut data);
64            let rx_errors = read(parent, "rx_errors", &mut data);
65            let tx_errors = read(parent, "tx_errors", &mut data);
66            // let rx_compressed = read(parent, "rx_compressed", &mut data);
67            // let tx_compressed = read(parent, "tx_compressed", &mut data);
68            let mtu = read(entry_path, "mtu", &mut data);
69
70            match interfaces.entry(entry) {
71                hash_map::Entry::Occupied(mut e) => {
72                    let interface = e.get_mut();
73                    let interface = &mut interface.inner;
74
75                    old_and_new!(interface, rx_bytes, old_rx_bytes);
76                    old_and_new!(interface, tx_bytes, old_tx_bytes);
77                    old_and_new!(interface, rx_packets, old_rx_packets);
78                    old_and_new!(interface, tx_packets, old_tx_packets);
79                    old_and_new!(interface, rx_errors, old_rx_errors);
80                    old_and_new!(interface, tx_errors, old_tx_errors);
81                    // old_and_new!(e, rx_compressed, old_rx_compressed);
82                    // old_and_new!(e, tx_compressed, old_tx_compressed);
83                    if interface.mtu != mtu {
84                        interface.mtu = mtu;
85                    }
86                    interface.updated = true;
87                }
88                hash_map::Entry::Vacant(e) => {
89                    e.insert(NetworkData {
90                        inner: NetworkDataInner {
91                            rx_bytes,
92                            old_rx_bytes: rx_bytes,
93                            tx_bytes,
94                            old_tx_bytes: tx_bytes,
95                            rx_packets,
96                            old_rx_packets: rx_packets,
97                            tx_packets,
98                            old_tx_packets: tx_packets,
99                            rx_errors,
100                            old_rx_errors: rx_errors,
101                            tx_errors,
102                            old_tx_errors: tx_errors,
103                            mac_addr: MacAddr::UNSPECIFIED,
104                            ip_networks: vec![],
105                            // rx_compressed,
106                            // old_rx_compressed: rx_compressed,
107                            // tx_compressed,
108                            // old_tx_compressed: tx_compressed,
109                            mtu,
110                            updated: true,
111                        },
112                    });
113                }
114            };
115        }
116    }
117    // We do this here because `refresh_networks_list_remove_interface` test is checking that
118    // this is working as expected.
119    if remove_not_listed_interfaces {
120        // Remove interfaces which are gone.
121        interfaces.retain(|_, i| {
122            if !i.inner.updated {
123                return false;
124            }
125            i.inner.updated = false;
126            true
127        });
128    }
129}
130
131pub(crate) struct NetworksInner {
132    pub(crate) interfaces: HashMap<String, NetworkData>,
133}
134
135impl NetworksInner {
136    pub(crate) fn new() -> Self {
137        Self {
138            interfaces: HashMap::new(),
139        }
140    }
141
142    pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
143        &self.interfaces
144    }
145
146    pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
147        refresh_networks_list_from_sysfs(
148            &mut self.interfaces,
149            remove_not_listed_interfaces,
150            Path::new("/sys/class/net/"),
151        );
152        refresh_networks_addresses(&mut self.interfaces);
153    }
154}
155
156pub(crate) struct NetworkDataInner {
157    /// Total number of bytes received over interface.
158    rx_bytes: u64,
159    old_rx_bytes: u64,
160    /// Total number of bytes transmitted over interface.
161    tx_bytes: u64,
162    old_tx_bytes: u64,
163    /// Total number of packets received.
164    rx_packets: u64,
165    old_rx_packets: u64,
166    /// Total number of packets transmitted.
167    tx_packets: u64,
168    old_tx_packets: u64,
169    /// Shows the total number of packets received with error. This includes
170    /// too-long-frames errors, ring-buffer overflow errors, CRC errors,
171    /// frame alignment errors, fifo overruns, and missed packets.
172    rx_errors: u64,
173    old_rx_errors: u64,
174    /// similar to `rx_errors`
175    tx_errors: u64,
176    old_tx_errors: u64,
177    /// MAC address
178    pub(crate) mac_addr: MacAddr,
179    pub(crate) ip_networks: Vec<IpNetwork>,
180    /// Interface Maximum Transfer Unit (MTU)
181    mtu: u64,
182    // /// Indicates the number of compressed packets received by this
183    // /// network device. This value might only be relevant for interfaces
184    // /// that support packet compression (e.g: PPP).
185    // rx_compressed: usize,
186    // old_rx_compressed: usize,
187    // /// Indicates the number of transmitted compressed packets. Note
188    // /// this might only be relevant for devices that support
189    // /// compression (e.g: PPP).
190    // tx_compressed: usize,
191    // old_tx_compressed: usize,
192    /// Whether or not the above data has been updated during refresh
193    updated: bool,
194}
195
196impl NetworkDataInner {
197    pub(crate) fn received(&self) -> u64 {
198        self.rx_bytes.saturating_sub(self.old_rx_bytes)
199    }
200
201    pub(crate) fn total_received(&self) -> u64 {
202        self.rx_bytes
203    }
204
205    pub(crate) fn transmitted(&self) -> u64 {
206        self.tx_bytes.saturating_sub(self.old_tx_bytes)
207    }
208
209    pub(crate) fn total_transmitted(&self) -> u64 {
210        self.tx_bytes
211    }
212
213    pub(crate) fn packets_received(&self) -> u64 {
214        self.rx_packets.saturating_sub(self.old_rx_packets)
215    }
216
217    pub(crate) fn total_packets_received(&self) -> u64 {
218        self.rx_packets
219    }
220
221    pub(crate) fn packets_transmitted(&self) -> u64 {
222        self.tx_packets.saturating_sub(self.old_tx_packets)
223    }
224
225    pub(crate) fn total_packets_transmitted(&self) -> u64 {
226        self.tx_packets
227    }
228
229    pub(crate) fn errors_on_received(&self) -> u64 {
230        self.rx_errors.saturating_sub(self.old_rx_errors)
231    }
232
233    pub(crate) fn total_errors_on_received(&self) -> u64 {
234        self.rx_errors
235    }
236
237    pub(crate) fn errors_on_transmitted(&self) -> u64 {
238        self.tx_errors.saturating_sub(self.old_tx_errors)
239    }
240
241    pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
242        self.tx_errors
243    }
244
245    pub(crate) fn mac_address(&self) -> MacAddr {
246        self.mac_addr
247    }
248
249    pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
250        &self.ip_networks
251    }
252
253    pub(crate) fn mtu(&self) -> u64 {
254        self.mtu
255    }
256}
257
258#[cfg(test)]
259mod test {
260    use super::refresh_networks_list_from_sysfs;
261    use std::collections::HashMap;
262    use std::fs;
263
264    #[test]
265    fn refresh_networks_list_add_interface() {
266        let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory");
267
268        fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory");
269
270        let mut interfaces = HashMap::new();
271
272        refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
273        assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf1"]);
274
275        fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory");
276
277        refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
278        let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect();
279        itf_names.sort();
280        assert_eq!(itf_names, ["itf1", "itf2"]);
281    }
282
283    #[test]
284    fn refresh_networks_list_remove_interface() {
285        let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory");
286
287        let itf1_dir = sys_net_dir.path().join("itf1");
288        let itf2_dir = sys_net_dir.path().join("itf2");
289        fs::create_dir(&itf1_dir).expect("failed to create subdirectory");
290        fs::create_dir(itf2_dir).expect("failed to create subdirectory");
291
292        let mut interfaces = HashMap::new();
293
294        refresh_networks_list_from_sysfs(&mut interfaces, false, sys_net_dir.path());
295        let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect();
296        itf_names.sort();
297        assert_eq!(itf_names, ["itf1", "itf2"]);
298
299        fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory");
300
301        refresh_networks_list_from_sysfs(&mut interfaces, true, sys_net_dir.path());
302        assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf2"]);
303    }
304}