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