netfilter: add /scheme/netfilter/stats node

Exposes per-chain filter statistics:
  input: packets=0 bytes=0 policy=ACCEPT
  output: packets=0 bytes=0 policy=ACCEPT
  forward: packets=1423 bytes=456789 policy=DROP
  prerouting: packets=0 bytes=0 policy=ACCEPT
  postrouting: packets=0 bytes=0 policy=ACCEPT
  rules: 5 active (snat=3 dnat=2)
  conntrack: 127

Mirrors iptables -L -n -v output format.
Per-chain (packets, bytes) from FilterTable::chain_counters.
Rule breakdown shows active count + snat/dnat split.
Conntrack entry count from live table.
This commit is contained in:
Red Bear OS
2026-07-08 17:20:24 +03:00
parent 98c43529e7
commit 7abd45bf2e
+19 -1
View File
@@ -44,7 +44,7 @@ use syscall::schemev2::NewFdFlags;
use syscall::{Error as SyscallError, Result as SyscallResult};
use crate::error::{Error, Result};
use crate::filter::{parse_nat_rule, parse_rule, FilterTable, Hook, Verdict};
use crate::filter::{parse_nat_rule, parse_rule, FilterTable, Hook, NatType, Verdict};
const WRITE_BUFFER_MAX_SIZE: usize = 0xffff;
@@ -195,6 +195,24 @@ impl NetFilterSchemeInner {
(format!("{}\n", policy.name()).into_bytes(), true, true)
}
["reset"] => (Vec::new(), false, true),
["stats"] => {
let table = self.table.borrow();
let mut out = String::new();
for &hook in &Hook::ALL {
let name = hook.name();
let (pkts, bytes) = table.chain_counters.get(&hook).copied().unwrap_or((0, 0));
let policy = table.default_policy.get(&hook).copied().unwrap_or(Verdict::Accept);
out.push_str(&format!("{}: packets={} bytes={} policy={}\n", name, pkts, bytes, policy.name()));
}
let snat_count = table.nat_table.rules.iter().filter(|r| r.nat_type == NatType::Snat).count();
let dnat_count = table.nat_table.rules.len() - snat_count;
out.push_str(&format!("rules: {} active (snat={} dnat={})\n",
table.rules.len(), snat_count, dnat_count));
if let Some(ref ct) = table.conntrack {
out.push_str(&format!("conntrack: {}\n", ct.len()));
}
(out.into_bytes(), true, false)
},
_ => return Err(SyscallError::new(syscall::ENOENT)),
};