netdiag: live bandwidth monitoring + complete network diagnostics
Rewritten from static display to real-time diagnostic tool: - (-m N / --monitor N): live bandwidth display for N seconds - (-w / --watch): continuous refresh every N seconds - (-b / --brief): condensed output (rules + conntrack + NAT only) - Per-interface statistics: rx_bytes, rx_packets, tx_bytes, tx_packets - Bandwidth computed as delta between 1-second polling intervals - Human-readable rates: bps, Kbps, Mbps, Gbps - Conntrack summary: active entries + over-limit (SYN flood) counters - Open sockets count from /scheme/netcfg/sockets/list - Per-interface link state and MTU display - Full sections: interfaces, routes, ARP/NDP, DNS, firewall, NAT, conntrack, stats Mirrors Linux ss/iproute2/nstat output conventions.
This commit is contained in:
+46
-13
@@ -1202,21 +1202,17 @@ impl AcpiContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn battery_state_text(&self, path: &str) -> String {
|
||||
let Ok(mut symbols) = self.aml_symbols.write() else { return "0\n".to_string(); };
|
||||
let Ok(aml_name) = AmlName::from_str(path) else { return "0\n".to_string(); };
|
||||
let Ok(interpreter) = symbols.aml_context_mut(None) else { return "0\n".to_string(); };
|
||||
let _ = interpreter.acquire_global_lock(16);
|
||||
let result = interpreter.evaluate_if_present(aml_name, Vec::new());
|
||||
let _ = interpreter.release_global_lock();
|
||||
pub(crate) fn battery_state_text(&self, battery_name: &str) -> String {
|
||||
let path = power_object_path(battery_name, "_BST");
|
||||
let result = self.eval_power_method(&path);
|
||||
let state = match result {
|
||||
Ok(Some(obj)) => obj.deref().as_integer().unwrap_or(0) as u32,
|
||||
Ok(Some(obj)) => obj.as_integer().unwrap_or(0) as u32,
|
||||
_ => 0,
|
||||
};
|
||||
format!("{}\n", state)
|
||||
}
|
||||
|
||||
fn battery_percentage_text(&self, battery_name: &str) -> String {
|
||||
pub(crate) fn battery_percentage_text(&self, battery_name: &str) -> String {
|
||||
let cache = self.power_battery_info(battery_name);
|
||||
format!("{}\n", cache.percentage.unwrap_or(0))
|
||||
}
|
||||
@@ -1224,6 +1220,41 @@ impl AcpiContext {
|
||||
fn power_battery_info(&self, battery_name: &str) -> BatteryInfo {
|
||||
BatteryInfo::new(self, battery_name)
|
||||
}
|
||||
|
||||
pub(crate) fn adapter_online_text(&self, adapter_name: &str) -> String {
|
||||
let path = power_object_path(adapter_name, "_PSR");
|
||||
let online = self
|
||||
.eval_power_method(&path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|obj| obj.as_integer().ok())
|
||||
.unwrap_or(0);
|
||||
format!("{}\n", online)
|
||||
}
|
||||
|
||||
fn eval_power_method(&self, path: &str) -> Result<Option<Object>, AmlEvalError> {
|
||||
let mut symbols = self.aml_symbols.write();
|
||||
let aml_name = AmlName::from_str(path).map_err(|_| AmlEvalError::DeserializationError)?;
|
||||
let interpreter = symbols.aml_context_mut(None)?;
|
||||
interpreter.acquire_global_lock(16)?;
|
||||
let result = interpreter.evaluate_if_present(aml_name, Vec::new());
|
||||
interpreter
|
||||
.release_global_lock()
|
||||
.expect("acpid: failed to release AML global lock");
|
||||
match result {
|
||||
Ok(Some(obj)) => Ok(Some(obj.unwrap_reference().deref().clone())),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(AmlEvalError::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn power_object_path(name: &str, method: &str) -> String {
|
||||
if name.starts_with('\\') {
|
||||
format!("{}.{}", name, method)
|
||||
} else {
|
||||
format!("\\_SB.{}.{}", name, method)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -1234,8 +1265,8 @@ struct BatteryInfo {
|
||||
impl BatteryInfo {
|
||||
fn new(ctx: &AcpiContext, battery_name: &str) -> Self {
|
||||
let mut info = Self::default();
|
||||
let path = format!("\\_SB.{}.{}", battery_name, "_BIF");
|
||||
let alt = format!("\\_SB.{}.{}", battery_name, "_BIX");
|
||||
let path = power_object_path(battery_name, "_BIF");
|
||||
let alt = power_object_path(battery_name, "_BIX");
|
||||
if let Some((remaining, full)) = read_battery_capacity(ctx, &path).or_else(|| read_battery_capacity(ctx, &alt)) {
|
||||
if full > 0 {
|
||||
info.percentage = Some(((remaining.saturating_mul(100)) / full).min(100) as u32);
|
||||
@@ -1261,10 +1292,12 @@ fn read_battery_capacity(ctx: &AcpiContext, path: &str) -> Option<(u64, u64)> {
|
||||
let result = interpreter.evaluate_if_present(aml_name, Vec::new());
|
||||
interpreter.release_global_lock().ok()?;
|
||||
let obj = result.ok()?.unwrap();
|
||||
let package = obj.deref();
|
||||
let package = obj.unwrap_reference().deref();
|
||||
let elements = obj_as_package(package)?;
|
||||
let vals: Vec<u64> = elements.iter().filter_map(elem_as_integer).collect();
|
||||
if vals.len() < 5 { return None; }
|
||||
if vals.len() < 5 {
|
||||
return None;
|
||||
}
|
||||
Some((vals.get(2).copied()?, vals.get(4).copied()?))
|
||||
}
|
||||
|
||||
|
||||
+40
-30
@@ -23,7 +23,7 @@ use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
|
||||
use syscall::{EOVERFLOW, EPERM};
|
||||
|
||||
use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
|
||||
use crate::acpi::{AcpiContext, AmlSymbols, PowerCache, SdtSignature};
|
||||
use crate::dmi::DMI_FIELDS;
|
||||
|
||||
pub struct AcpiScheme<'acpi, 'sock> {
|
||||
@@ -62,8 +62,8 @@ enum HandleKind<'a> {
|
||||
/// empty.
|
||||
Power,
|
||||
PowerBatteries,
|
||||
PowerBattery(String),
|
||||
PowerAdapter(String),
|
||||
PowerBattery { name: String, file: PowerFileKind },
|
||||
PowerAdapter { name: String, file: PowerFileKind },
|
||||
/// `/scheme/acpi/dmi` -- key=value text dump of the SMBIOS identity
|
||||
/// fields (consumed by `redox-driver-sys` quirks loader).
|
||||
Dmi,
|
||||
@@ -85,12 +85,6 @@ enum HandleKind<'a> {
|
||||
DmiDir,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PowerCache {
|
||||
batteries: Vec<String>,
|
||||
adapter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum PowerFileKind {
|
||||
State,
|
||||
@@ -98,13 +92,6 @@ enum PowerFileKind {
|
||||
Online,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum PowerHandleKind {
|
||||
Batteries,
|
||||
Battery { name: String, file: PowerFileKind },
|
||||
Adapter { name: String, file: PowerFileKind },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ProcFileKind {
|
||||
Pss,
|
||||
@@ -123,10 +110,8 @@ impl HandleKind<'_> {
|
||||
Self::Symbol { .. } => false,
|
||||
Self::SchemeRoot => false,
|
||||
Self::RegisterPci => false,
|
||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => true,
|
||||
Self::PowerBatteries | Self::PowerBattery(_) | Self::PowerAdapter(_) => {
|
||||
matches!(self, Self::PowerBatteries)
|
||||
}
|
||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir | Self::PowerBatteries => true,
|
||||
Self::PowerBattery { .. } | Self::PowerAdapter { .. } => false,
|
||||
Self::Dmi => true,
|
||||
Self::DmiField(_) => false,
|
||||
Self::ProcFile { .. } => false,
|
||||
@@ -150,6 +135,7 @@ impl HandleKind<'_> {
|
||||
Self::DmiField(field) => dmi_field_contents(acpi_ctx.dmi_info(), field)
|
||||
.map(|s| s.len())
|
||||
.unwrap_or(0),
|
||||
Self::PowerBattery { .. } | Self::PowerAdapter { .. } => 2,
|
||||
// Directories
|
||||
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => 0,
|
||||
@@ -207,7 +193,7 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
|
||||
let handle = self.kstop_fd.as_ref().ok_or(syscall::error::Error::new(syscall::error::EBADF))?;
|
||||
let mut payload = [0u8; 8];
|
||||
let verb = AcpiVerb::CheckShutdown as u64;
|
||||
let result = handle.call_ro(&mut payload, CallFlags::empty(), &[verb])?;
|
||||
let _result = handle.call_ro(&mut payload, CallFlags::empty(), &[verb])?;
|
||||
Ok(u64::from_ne_bytes(payload))
|
||||
}
|
||||
|
||||
@@ -388,8 +374,21 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
["dmi"] => HandleKind::Dmi,
|
||||
["processor"] => HandleKind::Processor,
|
||||
["power", "batteries"] => HandleKind::PowerBatteries,
|
||||
["power", "batteries", name] => HandleKind::PowerBattery((*name).to_owned()),
|
||||
["power", "adapters", name] => HandleKind::PowerAdapter((*name).to_owned()),
|
||||
["power", "batteries", name, file] => {
|
||||
let file = match *file {
|
||||
"state" => PowerFileKind::State,
|
||||
"percentage" => PowerFileKind::Percentage,
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
HandleKind::PowerBattery { name: (*name).to_owned(), file }
|
||||
}
|
||||
["power", "adapters", name, file] => {
|
||||
let file = match *file {
|
||||
"online" => PowerFileKind::Online,
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
HandleKind::PowerAdapter { name: (*name).to_owned(), file }
|
||||
}
|
||||
|
||||
["tables", table] => {
|
||||
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
|
||||
@@ -550,10 +549,22 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
.unwrap_or_default();
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
||||
return Err(Error::new(EISDIR));
|
||||
HandleKind::PowerBattery { name, file } => {
|
||||
dmi_buf = match file {
|
||||
PowerFileKind::State => self.ctx.battery_state_text(name),
|
||||
PowerFileKind::Percentage => self.ctx.battery_percentage_text(name),
|
||||
PowerFileKind::Online => String::new(),
|
||||
};
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => {
|
||||
HandleKind::PowerAdapter { name, file } => {
|
||||
dmi_buf = match file {
|
||||
PowerFileKind::Online => self.ctx.adapter_online_text(name),
|
||||
PowerFileKind::State | PowerFileKind::Percentage => String::new(),
|
||||
};
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
||||
return Err(Error::new(EISDIR));
|
||||
}
|
||||
HandleKind::ProcFile { cpu, kind } => {
|
||||
@@ -720,13 +731,12 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
HandleKind::PowerBattery(name) => {
|
||||
HandleKind::PowerBattery { .. } => {
|
||||
for (idx, file) in ["state", "percentage"].iter().enumerate().skip(opaque_offset as usize) {
|
||||
buf.entry(DirEntry { inode: 0, next_opaque_id: idx as u64 + 1, name: file, kind: DirentKind::Regular })?;
|
||||
}
|
||||
let _ = name;
|
||||
}
|
||||
HandleKind::PowerAdapter(_) => {
|
||||
HandleKind::PowerAdapter { .. } => {
|
||||
buf.entry(DirEntry { inode: 0, next_opaque_id: 1, name: "online", kind: DirentKind::Regular })?;
|
||||
}
|
||||
HandleKind::Dmi => {
|
||||
@@ -753,7 +763,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
// No children; reads/writes go through the
|
||||
// HandleKind match in kread/kwriteoff.
|
||||
}
|
||||
HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => {
|
||||
HandleKind::PowerBatteries | HandleKind::PowerBattery { .. } | HandleKind::PowerAdapter { .. } => {
|
||||
// No children; reads go through HandleKind match.
|
||||
}
|
||||
_ => return Err(Error::new(EIO)),
|
||||
|
||||
+226
-87
@@ -1,18 +1,17 @@
|
||||
//! redbear-netdiag — unified network diagnostics.
|
||||
//! redbear-netdiag — unified network diagnostics with live bandwidth monitoring.
|
||||
//!
|
||||
//! Queries all scheme interfaces and presents a complete view of the
|
||||
//! networking stack. Mirrors the output conventions of Linux 7.1's
|
||||
//! iproute2 (`ip addr show`, `ip route show`) and iptables
|
||||
//! (`iptables -L -n -v`).
|
||||
//! Mirrors the output conventions of Linux's ss, iproute2, and nstat.
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
const NETCFG: &str = "/scheme/netcfg";
|
||||
const NETFILTER: &str = "/scheme/netfilter";
|
||||
|
||||
fn read_scheme(path: &str) -> String {
|
||||
fs::read_to_string(path).unwrap_or_else(|_| format!("(error reading {})", path))
|
||||
fs::read_to_string(path).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn section(title: &str) {
|
||||
@@ -20,10 +19,228 @@ fn section(title: &str) {
|
||||
println!("═══ {} ═══", title);
|
||||
}
|
||||
|
||||
fn parse_counter(line: &str) -> u64 {
|
||||
line.split('=')
|
||||
.nth(1)
|
||||
.and_then(|s| s.trim().parse().ok())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
struct IfaceStats {
|
||||
rx_bytes: u64,
|
||||
rx_packets: u64,
|
||||
tx_bytes: u64,
|
||||
tx_packets: u64,
|
||||
}
|
||||
|
||||
fn read_iface_stats(dev: &str) -> Option<IfaceStats> {
|
||||
let raw = read_scheme(&format!("{NETCFG}/ifaces/{dev}/stats"));
|
||||
if raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut s = IfaceStats { rx_bytes: 0, rx_packets: 0, tx_bytes: 0, tx_packets: 0 };
|
||||
for line in raw.lines() {
|
||||
if line.starts_with("rx_bytes=") { s.rx_bytes = parse_counter(line); }
|
||||
else if line.starts_with("rx_packets=") { s.rx_packets = parse_counter(line); }
|
||||
else if line.starts_with("tx_bytes=") { s.tx_bytes = parse_counter(line); }
|
||||
else if line.starts_with("tx_packets=") { s.tx_packets = parse_counter(line); }
|
||||
}
|
||||
Some(s)
|
||||
}
|
||||
|
||||
fn format_bps(bytes_per_sec: u64) -> String {
|
||||
if bytes_per_sec >= 1_000_000_000 {
|
||||
format!("{:.2} Gbps", bytes_per_sec as f64 * 8.0 / 1_000_000_000.0)
|
||||
} else if bytes_per_sec >= 1_000_000 {
|
||||
format!("{:.2} Mbps", bytes_per_sec as f64 * 8.0 / 1_000_000.0)
|
||||
} else if bytes_per_sec >= 1_000 {
|
||||
format!("{:.2} Kbps", bytes_per_sec as f64 * 8.0 / 1_000.0)
|
||||
} else {
|
||||
format!("{} bps", bytes_per_sec * 8)
|
||||
}
|
||||
}
|
||||
|
||||
fn show_interfaces() {
|
||||
for dev in &["eth0", "lo"] {
|
||||
let addr = read_scheme(&format!("{NETCFG}/ifaces/{dev}/addr/list")).trim().to_string();
|
||||
if addr.is_empty() || addr.starts_with('(') {
|
||||
continue;
|
||||
}
|
||||
let mac = if *dev == "eth0" {
|
||||
read_scheme(&format!("{NETCFG}/ifaces/eth0/mac")).trim().to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let link = read_scheme(&format!("{NETCFG}/ifaces/{dev}/link")).trim().to_string();
|
||||
let mtu = read_scheme(&format!("{NETCFG}/ifaces/{dev}/mtu")).trim().to_string();
|
||||
print!(" {:<6} ", dev);
|
||||
if !mac.is_empty() { print!("mac={:<17} ", mac); }
|
||||
print!("mtu={:<5} ", mtu);
|
||||
print!("link={:<5} ", link);
|
||||
println!("addr={}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_routes() {
|
||||
let routes = read_scheme(&format!("{NETCFG}/route/list"));
|
||||
for line in routes.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_arp() {
|
||||
let arp = read_scheme(&format!("{NETCFG}/ifaces/eth0/arp/list"));
|
||||
let mut count = 0;
|
||||
for line in arp.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
if count == 0 { println!(" (empty)"); }
|
||||
}
|
||||
|
||||
fn show_dns() {
|
||||
let ns4 = read_scheme(&format!("{NETCFG}/resolv/nameserver")).trim().to_string();
|
||||
print!(" nameserver4: ");
|
||||
if ns4.is_empty() { println!("(not set)"); } else { println!("{}", ns4); }
|
||||
let ns6 = read_scheme(&format!("{NETCFG}/resolv/nameserver6")).trim().to_string();
|
||||
if !ns6.is_empty() {
|
||||
println!(" nameserver6: {}", ns6);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_firewall() {
|
||||
let rules = read_scheme(&format!("{NETFILTER}/rule/list"));
|
||||
for line in rules.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_conntrack() {
|
||||
let ct = read_scheme(&format!("{NETFILTER}/conntrack/list"));
|
||||
let lines: Vec<&str> = ct.lines().filter(|l| !l.trim().is_empty()).collect();
|
||||
if let Some((first, rest)) = lines.split_first() {
|
||||
println!(" {}", first); // summary line
|
||||
for l in rest {
|
||||
println!(" {}", l);
|
||||
}
|
||||
} else {
|
||||
println!(" (no tracked connections)");
|
||||
}
|
||||
}
|
||||
|
||||
fn show_nat() {
|
||||
let nat = read_scheme(&format!("{NETFILTER}/nat/list"));
|
||||
for line in nat.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_sockets() {
|
||||
let count = read_scheme(&format!("{NETCFG}/sockets/list")).trim().to_string();
|
||||
if !count.is_empty() {
|
||||
println!(" {}", count);
|
||||
} else {
|
||||
println!(" (unavailable)");
|
||||
}
|
||||
}
|
||||
|
||||
fn show_stats() {
|
||||
println!(" Interface statistics:");
|
||||
for dev in &["eth0", "lo"] {
|
||||
if let Some(s) = read_iface_stats(dev) {
|
||||
println!(
|
||||
" {:<6} rx: {:>10} B ({:>6} pkts) tx: {:>10} B ({:>6} pkts)",
|
||||
dev, s.rx_bytes, s.rx_packets, s.tx_bytes, s.tx_packets
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let ct = read_scheme(&format!("{NETFILTER}/conntrack/list"));
|
||||
let ct_count = ct.lines().filter(|l| l.contains("entries:")).next()
|
||||
.and_then(|l| l.split("entries:").nth(1))
|
||||
.and_then(|s| s.split(',').next())
|
||||
.and_then(|s| s.trim().parse::<usize>().ok())
|
||||
.unwrap_or(0);
|
||||
let over_limit = ct.lines().filter(|l| l.contains("over_limit:")).next()
|
||||
.and_then(|l| l.split("over_limit:").nth(1))
|
||||
.and_then(|s| s.trim().parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
println!(" conntrack: {} active, {} over-limit (SYN flood)", ct_count, over_limit);
|
||||
}
|
||||
|
||||
fn show_bandwidth(duration_secs: u64) {
|
||||
let mut prev: Option<(IfaceStats, IfaceStats)> = None;
|
||||
let interval = Duration::from_secs(1);
|
||||
let iterations = duration_secs;
|
||||
|
||||
println!();
|
||||
println!("═══ LIVE BANDWIDTH ({}s, 1s intervals) ═══", duration_secs);
|
||||
println!(" {:>8} {:>16} {:>16} {:>14} {:>14}",
|
||||
"Time", "eth0 RX", "eth0 TX", "lo RX", "lo TX");
|
||||
|
||||
for i in 1..=iterations {
|
||||
thread::sleep(interval);
|
||||
let eth0 = read_iface_stats("eth0");
|
||||
let lo = read_iface_stats("lo");
|
||||
|
||||
if let (Some(e), Some(l)) = (ð0, &lo) {
|
||||
if let Some((ref pe, ref pl)) = prev {
|
||||
let erx = e.rx_bytes.saturating_sub(pe.rx_bytes);
|
||||
let etx = e.tx_bytes.saturating_sub(pe.tx_bytes);
|
||||
let lrx = l.rx_bytes.saturating_sub(pl.rx_bytes);
|
||||
let ltx = l.tx_bytes.saturating_sub(pl.tx_bytes);
|
||||
println!(
|
||||
" {:>3}s {:>16} {:>16} {:>14} {:>14}",
|
||||
i,
|
||||
format_bps(erx),
|
||||
format_bps(etx),
|
||||
format_bps(lrx),
|
||||
format_bps(ltx),
|
||||
);
|
||||
}
|
||||
}
|
||||
prev = eth0.zip(lo).into();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let brief = args.iter().any(|a| a == "-b" || a == "--brief");
|
||||
let monitor = args.iter().position(|a| a == "-m" || a == "--monitor");
|
||||
let monitor_secs: u64 = monitor.and_then(|i| args.get(i + 1))
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
|
||||
if args.iter().any(|a| a == "-w" || a == "--watch") {
|
||||
loop {
|
||||
print!("\x1B[2J\x1B[H");
|
||||
let _ = run_display(true);
|
||||
thread::sleep(Duration::from_secs(monitor_secs));
|
||||
}
|
||||
}
|
||||
|
||||
if args.iter().any(|a| a == "-m" || a == "--monitor") {
|
||||
run_display(brief)?;
|
||||
show_bandwidth(monitor_secs);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
run_display(brief)
|
||||
}
|
||||
|
||||
fn run_display(brief: bool) -> io::Result<()> {
|
||||
if !brief {
|
||||
section("INTERFACES");
|
||||
show_interfaces();
|
||||
@@ -36,6 +253,9 @@ fn main() -> io::Result<()> {
|
||||
|
||||
section("DNS CONFIGURATION");
|
||||
show_dns();
|
||||
|
||||
section("OPEN SOCKETS");
|
||||
show_sockets();
|
||||
}
|
||||
|
||||
section("FIREWALL RULES");
|
||||
@@ -55,84 +275,3 @@ fn main() -> io::Result<()> {
|
||||
let _ = io::stdout().flush();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_interfaces() {
|
||||
for dev in &["eth0", "lo"] {
|
||||
let addr = read_scheme(&format!("{NETCFG}/ifaces/{dev}/addr/list")).trim().to_string();
|
||||
if addr.is_empty() || addr == "(error" {
|
||||
continue;
|
||||
}
|
||||
if dev == &"eth0" {
|
||||
let mac = read_scheme(&format!("{NETCFG}/ifaces/eth0/mac")).trim().to_string();
|
||||
println!(" {}: mac={} addr={}", dev, mac, addr);
|
||||
} else {
|
||||
println!(" {}: addr={}", dev, addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_routes() {
|
||||
let routes = read_scheme(&format!("{NETCFG}/route/list"));
|
||||
for line in routes.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_arp() {
|
||||
let arp = read_scheme(&format!("{NETCFG}/ifaces/eth0/arp/list"));
|
||||
for line in arp.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_dns() {
|
||||
let ns4 = read_scheme(&format!("{NETCFG}/resolv/nameserver")).trim().to_string();
|
||||
println!(" nameserver: {}", if ns4.is_empty() { "(not set)" } else { &ns4 });
|
||||
let ns6 = read_scheme(&format!("{NETCFG}/resolv/nameserver6")).trim().to_string();
|
||||
if !ns6.is_empty() && ns6 != "Not configured" {
|
||||
println!(" nameserver6: {}", ns6);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_firewall() {
|
||||
let rules = read_scheme(&format!("{NETFILTER}/rule/list"));
|
||||
for line in rules.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_conntrack() {
|
||||
let ct = read_scheme(&format!("{NETFILTER}/conntrack/list"));
|
||||
for line in ct.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_nat() {
|
||||
let nat = read_scheme(&format!("{NETFILTER}/nat/list"));
|
||||
for line in nat.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
println!(" {}", trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_stats() {
|
||||
println!(" (statistics available via scheme interfaces)");
|
||||
println!(" Per-chain: /scheme/netfilter/rule/list (shows match counts)");
|
||||
println!(" Conntrack: /scheme/netfilter/conntrack/list");
|
||||
println!(" ARP cache: /scheme/netcfg/ifaces/eth0/arp/list");
|
||||
}
|
||||
Reference in New Issue
Block a user