Skip to main content

ferrix_lib/
net.rs

1/* net.rs
2 *
3 * Copyright 2026 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Network statistics
22
23use anyhow::{Result, anyhow};
24use serde::{Deserialize, Serialize};
25use std::{
26    fs::{read_dir, read_to_string},
27    path::{Path, PathBuf},
28};
29
30use crate::traits::ToJson;
31
32const NET_DIR: &str = "/sys/class/net/";
33
34#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct Networks {
36    pub networks: Vec<Network>,
37}
38
39impl Networks {
40    pub fn new() -> Result<Self> {
41        let net_dirs = read_dir(NET_DIR)?;
42        let mut networks = Vec::new();
43        for dir in net_dirs {
44            let dir = dir?.path();
45            networks.push(Network::new(&dir)?);
46        }
47        Ok(Self { networks })
48    }
49}
50
51impl ToJson for Networks {}
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
54pub struct Network {
55    pub name: String,
56    pub address: String,
57    pub broadcast: String,
58    pub mtu: u64,
59    pub operstate: String,
60    pub statistics: Statistics,
61}
62
63impl Network {
64    pub fn new(path: &PathBuf) -> Result<Self> {
65        let name = path.strip_prefix(NET_DIR)?.display().to_string();
66        let address = read_to_string(path.join("address"))
67            .and_then(|address| Ok(address.trim().to_string()))?;
68        let broadcast = read_to_string(path.join("broadcast"))
69            .and_then(|broadcast| Ok(broadcast.trim().to_string()))?;
70        let mtu = read_to_string(path.join("mtu"))
71            .and_then(|mtu| Ok(mtu.trim().parse::<u64>().unwrap_or(0)))?;
72        let operstate = read_to_string(path.join("operstate"))
73            .and_then(|opstate| Ok(opstate.trim().to_string()))?;
74        let statistics = Statistics::new(&name)?;
75
76        Ok(Self {
77            name,
78            address,
79            broadcast,
80            mtu,
81            operstate,
82            statistics,
83        })
84    }
85}
86
87#[derive(Debug, Clone, Deserialize, Serialize)]
88pub struct Statistics {
89    pub collisions: u64,
90    pub multicast: u64,
91
92    pub rx_bytes: u64,
93    pub rx_compressed: u64,
94    pub rx_crc_errors: u64,
95    pub rx_dropped: u64,
96    pub rx_errors: u64,
97    pub rx_fifo_errors: u64,
98    pub rx_frame_errors: u64,
99    pub rx_length_erorrs: u64,
100    pub rx_missed_errors: u64,
101    pub rx_nohandler: u64,
102    pub rx_over_errors: u64,
103    pub rx_packets: u64,
104
105    pub tx_aborted_errors: u64,
106    pub tx_bytes: u64,
107    pub tx_carrier_errors: u64,
108    pub tx_compressed: u64,
109    pub tx_dropped: u64,
110    pub tx_errors: u64,
111    pub tx_fifo_errors: u64,
112    pub tx_heartbeat_errors: u64,
113    pub tx_packets: u64,
114    pub tx_window_errors: u64,
115}
116
117impl Statistics {
118    pub fn new(interface: &str) -> Result<Self> {
119        let dir = Path::new(NET_DIR).join(interface).join("statistics");
120        if !dir.is_dir() {
121            return Err(anyhow!(
122                "Failed to open '{}' directory: not found",
123                dir.display(),
124            ));
125        }
126
127        let read = |file: &str| -> Result<u64> {
128            let file = dir.join(file);
129            let contents = read_to_string(&file)
130                .map_err(|err| anyhow!("Failed to read '{}' file: {err}", file.display()))?;
131
132            contents
133                .trim()
134                .parse::<u64>()
135                .map_err(|err| anyhow!("Failed to parse '{interface}' value: {err}"))
136        };
137
138        let collisions = read("collisions")?;
139        let multicast = read("multicast")?;
140
141        let rx_bytes = read("rx_bytes")?;
142        let rx_compressed = read("rx_compressed")?;
143        let rx_crc_errors = read("rx_crc_errors")?;
144        let rx_dropped = read("rx_dropped")?;
145        let rx_errors = read("rx_errors")?;
146        let rx_fifo_errors = read("rx_fifo_errors")?;
147        let rx_frame_errors = read("rx_frame_errors")?;
148        let rx_length_erorrs = read("rx_length_errors")?;
149        let rx_missed_errors = read("rx_missed_errors")?;
150        let rx_nohandler = read("rx_nohandler")?;
151        let rx_over_errors = read("rx_over_errors")?;
152        let rx_packets = read("rx_packets")?;
153
154        let tx_aborted_errors = read("tx_aborted_errors")?;
155        let tx_bytes = read("tx_bytes")?;
156        let tx_carrier_errors = read("tx_carrier_errors")?;
157        let tx_compressed = read("tx_compressed")?;
158        let tx_dropped = read("tx_dropped")?;
159        let tx_errors = read("tx_errors")?;
160        let tx_fifo_errors = read("tx_fifo_errors")?;
161        let tx_heartbeat_errors = read("tx_heartbeat_errors")?;
162        let tx_packets = read("tx_packets")?;
163        let tx_window_errors = read("tx_window_errors")?;
164
165        Ok(Self {
166            collisions,
167            multicast,
168            rx_bytes,
169            rx_compressed,
170            rx_crc_errors,
171            rx_dropped,
172            rx_errors,
173            rx_fifo_errors,
174            rx_frame_errors,
175            rx_length_erorrs,
176            rx_missed_errors,
177            rx_nohandler,
178            rx_over_errors,
179            rx_packets,
180            tx_aborted_errors,
181            tx_bytes,
182            tx_carrier_errors,
183            tx_compressed,
184            tx_dropped,
185            tx_errors,
186            tx_fifo_errors,
187            tx_heartbeat_errors,
188            tx_packets,
189            tx_window_errors,
190        })
191    }
192}
193
194#[derive(Debug, Clone, Deserialize, Serialize)]
195pub struct ARP {
196    pub tables: Vec<ARPTable>,
197}
198
199impl ToJson for ARP {}
200
201impl ARP {
202    pub fn new() -> Result<Self> {
203        let contents = read_to_string("/proc/net/arp")?;
204        let lines = contents.lines().skip(1);
205        let mut tables = vec![];
206
207        for line in lines {
208            tables.push(ARPTable::try_from(line)?);
209        }
210        tables.shrink_to_fit();
211
212        Ok(Self { tables })
213    }
214}
215
216#[derive(Debug, Clone, Deserialize, Serialize)]
217pub struct ARPTable {
218    pub ip_addr: String,
219    pub hw_type: String,
220    pub flags: String,
221    pub hw_addr: String,
222    pub mask: String,
223    pub device: String,
224}
225
226impl TryFrom<&str> for ARPTable {
227    type Error = anyhow::Error;
228
229    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
230        let chunks = value.split_whitespace().collect::<Vec<_>>();
231        if chunks.len() != 6 {
232            return Err(anyhow!(
233                "ARP Table parsing failed: String '{value}' is incorrect!"
234            ));
235        }
236
237        let ip_addr = chunks[0].to_string();
238        let hw_type = chunks[1].to_string();
239        let flags = chunks[2].to_string();
240        let hw_addr = chunks[3].to_string();
241        let mask = chunks[4].to_string();
242        let device = chunks[5].to_string();
243
244        Ok(Self {
245            ip_addr,
246            hw_type,
247            flags,
248            hw_addr,
249            mask,
250            device,
251        })
252    }
253}