1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::io::Read; |
4 | use std::path::Path; |
5 | use std::{fs::File, u8}; |
6 | |
7 | use crate::common::MacAddr; |
8 | use crate::network::refresh_networks_addresses; |
9 | use crate::{NetworkExt, NetworksExt, NetworksIter}; |
10 | use std::collections::{hash_map, HashMap}; |
11 | |
12 | #[doc = include_str!("../../md_doc/networks.md" )] |
13 | pub struct Networks { |
14 | interfaces: HashMap<String, NetworkData>, |
15 | } |
16 | |
17 | macro_rules! old_and_new { |
18 | ($ty_:expr, $name:ident, $old:ident) => {{ |
19 | $ty_.$old = $ty_.$name; |
20 | $ty_.$name = $name; |
21 | }}; |
22 | ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ |
23 | let _tmp = $path; |
24 | $ty_.$old = $ty_.$name; |
25 | $ty_.$name = _tmp; |
26 | }}; |
27 | } |
28 | |
29 | #[allow (clippy::ptr_arg)] |
30 | fn read<P: AsRef<Path>>(parent: P, path: &str, data: &mut Vec<u8>) -> u64 { |
31 | if let Ok(mut f: File) = File::open(path:parent.as_ref().join(path)) { |
32 | if let Ok(size: usize) = f.read(buf:data) { |
33 | let mut i: usize = 0; |
34 | let mut ret: u64 = 0; |
35 | |
36 | while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { |
37 | ret *= 10; |
38 | ret += (data[i] - b'0' ) as u64; |
39 | i += 1; |
40 | } |
41 | return ret; |
42 | } |
43 | } |
44 | 0 |
45 | } |
46 | |
47 | impl Networks { |
48 | pub(crate) fn new() -> Self { |
49 | Networks { |
50 | interfaces: HashMap::new(), |
51 | } |
52 | } |
53 | } |
54 | |
55 | fn refresh_networks_list_from_sysfs( |
56 | interfaces: &mut HashMap<String, NetworkData>, |
57 | sysfs_net: &Path, |
58 | ) { |
59 | if let Ok(dir) = std::fs::read_dir(sysfs_net) { |
60 | let mut data = vec![0; 30]; |
61 | |
62 | for stats in interfaces.values_mut() { |
63 | stats.updated = false; |
64 | } |
65 | |
66 | for entry in dir.flatten() { |
67 | let parent = &entry.path().join("statistics" ); |
68 | let entry = match entry.file_name().into_string() { |
69 | Ok(entry) => entry, |
70 | Err(_) => continue, |
71 | }; |
72 | let rx_bytes = read(parent, "rx_bytes" , &mut data); |
73 | let tx_bytes = read(parent, "tx_bytes" , &mut data); |
74 | let rx_packets = read(parent, "rx_packets" , &mut data); |
75 | let tx_packets = read(parent, "tx_packets" , &mut data); |
76 | let rx_errors = read(parent, "rx_errors" , &mut data); |
77 | let tx_errors = read(parent, "tx_errors" , &mut data); |
78 | // let rx_compressed = read(parent, "rx_compressed", &mut data); |
79 | // let tx_compressed = read(parent, "tx_compressed", &mut data); |
80 | match interfaces.entry(entry) { |
81 | hash_map::Entry::Occupied(mut e) => { |
82 | let interface = e.get_mut(); |
83 | old_and_new!(interface, rx_bytes, old_rx_bytes); |
84 | old_and_new!(interface, tx_bytes, old_tx_bytes); |
85 | old_and_new!(interface, rx_packets, old_rx_packets); |
86 | old_and_new!(interface, tx_packets, old_tx_packets); |
87 | old_and_new!(interface, rx_errors, old_rx_errors); |
88 | old_and_new!(interface, tx_errors, old_tx_errors); |
89 | // old_and_new!(e, rx_compressed, old_rx_compressed); |
90 | // old_and_new!(e, tx_compressed, old_tx_compressed); |
91 | interface.updated = true; |
92 | } |
93 | hash_map::Entry::Vacant(e) => { |
94 | e.insert(NetworkData { |
95 | rx_bytes, |
96 | old_rx_bytes: rx_bytes, |
97 | tx_bytes, |
98 | old_tx_bytes: tx_bytes, |
99 | rx_packets, |
100 | old_rx_packets: rx_packets, |
101 | tx_packets, |
102 | old_tx_packets: tx_packets, |
103 | rx_errors, |
104 | old_rx_errors: rx_errors, |
105 | tx_errors, |
106 | old_tx_errors: tx_errors, |
107 | mac_addr: MacAddr::UNSPECIFIED, |
108 | // rx_compressed, |
109 | // old_rx_compressed: rx_compressed, |
110 | // tx_compressed, |
111 | // old_tx_compressed: tx_compressed, |
112 | updated: true, |
113 | }); |
114 | } |
115 | }; |
116 | } |
117 | |
118 | // Remove interfaces which are gone. |
119 | interfaces.retain(|_, d| d.updated); |
120 | } |
121 | } |
122 | |
123 | impl NetworksExt for Networks { |
124 | fn iter(&self) -> NetworksIter { |
125 | NetworksIter::new(self.interfaces.iter()) |
126 | } |
127 | |
128 | fn refresh(&mut self) { |
129 | let mut v: Vec = vec![0; 30]; |
130 | |
131 | for (interface_name: &String, data: &mut NetworkData) in self.interfaces.iter_mut() { |
132 | data.update(path:interface_name, &mut v); |
133 | } |
134 | } |
135 | |
136 | fn refresh_networks_list(&mut self) { |
137 | refresh_networks_list_from_sysfs(&mut self.interfaces, sysfs_net:Path::new("/sys/class/net/" )); |
138 | refresh_networks_addresses(&mut self.interfaces); |
139 | } |
140 | } |
141 | |
142 | #[doc = include_str!("../../md_doc/network_data.md" )] |
143 | pub struct NetworkData { |
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 | |
180 | impl NetworkData { |
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 | |
224 | impl NetworkExt for NetworkData { |
225 | fn received(&self) -> u64 { |
226 | self.rx_bytes.saturating_sub(self.old_rx_bytes) |
227 | } |
228 | |
229 | fn total_received(&self) -> u64 { |
230 | self.rx_bytes |
231 | } |
232 | |
233 | fn transmitted(&self) -> u64 { |
234 | self.tx_bytes.saturating_sub(self.old_tx_bytes) |
235 | } |
236 | |
237 | fn total_transmitted(&self) -> u64 { |
238 | self.tx_bytes |
239 | } |
240 | |
241 | fn packets_received(&self) -> u64 { |
242 | self.rx_packets.saturating_sub(self.old_rx_packets) |
243 | } |
244 | |
245 | fn total_packets_received(&self) -> u64 { |
246 | self.rx_packets |
247 | } |
248 | |
249 | fn packets_transmitted(&self) -> u64 { |
250 | self.tx_packets.saturating_sub(self.old_tx_packets) |
251 | } |
252 | |
253 | fn total_packets_transmitted(&self) -> u64 { |
254 | self.tx_packets |
255 | } |
256 | |
257 | fn errors_on_received(&self) -> u64 { |
258 | self.rx_errors.saturating_sub(self.old_rx_errors) |
259 | } |
260 | |
261 | fn total_errors_on_received(&self) -> u64 { |
262 | self.rx_errors |
263 | } |
264 | |
265 | fn errors_on_transmitted(&self) -> u64 { |
266 | self.tx_errors.saturating_sub(self.old_tx_errors) |
267 | } |
268 | |
269 | fn total_errors_on_transmitted(&self) -> u64 { |
270 | self.tx_errors |
271 | } |
272 | |
273 | fn mac_address(&self) -> MacAddr { |
274 | self.mac_addr |
275 | } |
276 | } |
277 | |
278 | #[cfg (test)] |
279 | mod test { |
280 | use super::refresh_networks_list_from_sysfs; |
281 | use std::collections::HashMap; |
282 | use std::fs; |
283 | |
284 | #[test ] |
285 | fn refresh_networks_list_add_interface() { |
286 | let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory" ); |
287 | |
288 | fs::create_dir(sys_net_dir.path().join("itf1" )).expect("failed to create subdirectory" ); |
289 | |
290 | let mut interfaces = HashMap::new(); |
291 | |
292 | refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); |
293 | assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf1" ]); |
294 | |
295 | fs::create_dir(sys_net_dir.path().join("itf2" )).expect("failed to create subdirectory" ); |
296 | |
297 | refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); |
298 | let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect(); |
299 | itf_names.sort(); |
300 | assert_eq!(itf_names, ["itf1" , "itf2" ]); |
301 | } |
302 | |
303 | #[test ] |
304 | fn refresh_networks_list_remove_interface() { |
305 | let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory" ); |
306 | |
307 | let itf1_dir = sys_net_dir.path().join("itf1" ); |
308 | let itf2_dir = sys_net_dir.path().join("itf2" ); |
309 | fs::create_dir(&itf1_dir).expect("failed to create subdirectory" ); |
310 | fs::create_dir(itf2_dir).expect("failed to create subdirectory" ); |
311 | |
312 | let mut interfaces = HashMap::new(); |
313 | |
314 | refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); |
315 | let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect(); |
316 | itf_names.sort(); |
317 | assert_eq!(itf_names, ["itf1" , "itf2" ]); |
318 | |
319 | fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory" ); |
320 | |
321 | refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); |
322 | assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf2" ]); |
323 | } |
324 | } |
325 | |