Skip to main content

netwatch_rs/platform/
linux.rs

1use crate::{
2    device::{NetworkReader, NetworkStats},
3    error::{NetwatchError, Result},
4};
5use std::fs;
6use std::time::SystemTime;
7
8pub struct LinuxReader;
9
10impl Default for LinuxReader {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl LinuxReader {
17    pub fn new() -> Self {
18        Self
19    }
20
21    fn parse_proc_net_dev(&self, content: &str, device: &str) -> Result<NetworkStats> {
22        for line in content.lines().skip(2) {
23            let parts: Vec<&str> = line.split_whitespace().collect();
24            if parts.is_empty() {
25                continue;
26            }
27
28            let iface_name = parts[0].trim_end_matches(':');
29            if iface_name == device {
30                return Ok(NetworkStats {
31                    timestamp: SystemTime::now(),
32                    bytes_in: parts.get(1).unwrap_or(&"0").parse().unwrap_or(0),
33                    packets_in: parts.get(2).unwrap_or(&"0").parse().unwrap_or(0),
34                    errors_in: parts.get(3).unwrap_or(&"0").parse().unwrap_or(0),
35                    drops_in: parts.get(4).unwrap_or(&"0").parse().unwrap_or(0),
36                    bytes_out: parts.get(9).unwrap_or(&"0").parse().unwrap_or(0),
37                    packets_out: parts.get(10).unwrap_or(&"0").parse().unwrap_or(0),
38                    errors_out: parts.get(11).unwrap_or(&"0").parse().unwrap_or(0),
39                    drops_out: parts.get(12).unwrap_or(&"0").parse().unwrap_or(0),
40                });
41            }
42        }
43
44        Err(NetwatchError::DeviceNotFound(device.to_string()))
45    }
46}
47
48impl NetworkReader for LinuxReader {
49    fn list_devices(&self) -> Result<Vec<String>> {
50        let content = fs::read_to_string("/proc/net/dev")?;
51        let mut devices = Vec::new();
52
53        for line in content.lines().skip(2) {
54            if let Some(device_part) = line.split(':').next() {
55                let device_name = device_part.trim().to_string();
56                if !device_name.is_empty() {
57                    devices.push(device_name);
58                }
59            }
60        }
61
62        // Filter out loopback and virtual interfaces by default
63        devices.retain(|name| {
64            !name.starts_with("lo")
65                && !name.starts_with("docker")
66                && !name.starts_with("veth")
67                && !name.starts_with("br-")
68        });
69
70        Ok(devices)
71    }
72
73    fn read_stats(&self, device: &str) -> Result<NetworkStats> {
74        let content = fs::read_to_string("/proc/net/dev")?;
75        self.parse_proc_net_dev(&content, device)
76    }
77
78    fn is_available(&self) -> bool {
79        std::path::Path::new("/proc/net/dev").exists()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_parse_proc_net_dev() {
89        let reader = LinuxReader::new();
90        let sample_data = r#"Inter-|   Receive                                                |  Transmit
91 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
92    lo: 1234567      100    0    0    0     0          0         0  1234567      100    0    0    0     0       0          0
93  eth0: 9876543210   5000    0    0    0     0          0         0  1234567890   3000    0    0    0     0       0          0
94"#;
95
96        let stats = reader.parse_proc_net_dev(sample_data, "eth0").unwrap();
97        assert_eq!(stats.bytes_in, 9876543210);
98        assert_eq!(stats.bytes_out, 1234567890);
99        assert_eq!(stats.packets_in, 5000);
100        assert_eq!(stats.packets_out, 3000);
101    }
102
103    #[test]
104    fn test_device_not_found() {
105        let reader = LinuxReader::new();
106        let sample_data = r#"Inter-|   Receive                                                |  Transmit
107 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
108    lo: 1234567      100    0    0    0     0          0         0  1234567      100    0    0    0     0       0          0
109"#;
110
111        let result = reader.parse_proc_net_dev(sample_data, "nonexistent");
112        assert!(result.is_err());
113        assert!(matches!(
114            result.unwrap_err(),
115            NetwatchError::DeviceNotFound(_)
116        ));
117    }
118}