observer: packet capture facility (tcpdump-like) via netcfg
Adds packet observer with ring buffer (256 packets default): - Observer::capture(packet) hooks into forwarding/output paths - AtomicBool enable/disable toggle (no capture overhead when off) - capture/count: live stats (total captured, buffered, enabled) - capture/read: drain hex dump of buffered packets - capture/enable|disable: toggle capture on/off Usage: echo > /scheme/netcfg/capture/enable # start capture cat /scheme/netcfg/capture/read # dump captured packets (hex) cat /scheme/netcfg/capture/count # stats echo > /scheme/netcfg/capture/disable # stop Mirrors Linux AF_PACKET + tcpdump facility.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
//! Packet observer / capture facility — mirrors Linux 7.1's AF_PACKET + tcpdump.
|
||||
//!
|
||||
//! Provides a ring buffer that captures packets flowing through the network
|
||||
//! stack. Exposed via `/scheme/netcfg/capture` for reading by diagnostic tools.
|
||||
//!
|
||||
//! Usage:
|
||||
//! cat /scheme/netcfg/capture → stream captured packets (hex dump)
|
||||
//! cat /scheme/netcfg/capture/count → number of packets in buffer
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
|
||||
const MAX_CAPTURE_PACKETS: usize = 256;
|
||||
const MAX_CAPTURE_BYTES: usize = 65536;
|
||||
|
||||
pub struct Observer {
|
||||
enabled: AtomicBool,
|
||||
buffer: RefCell<Vec<Vec<u8>>>,
|
||||
max_packets: usize,
|
||||
total_captured: AtomicU64,
|
||||
}
|
||||
|
||||
pub type ObserverRef = Rc<Observer>;
|
||||
|
||||
impl Observer {
|
||||
pub fn new() -> ObserverRef {
|
||||
Rc::new(Observer {
|
||||
enabled: AtomicBool::new(false),
|
||||
buffer: RefCell::new(Vec::with_capacity(MAX_CAPTURE_PACKETS)),
|
||||
max_packets: MAX_CAPTURE_PACKETS,
|
||||
total_captured: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enable(&self) {
|
||||
self.enabled.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn disable(&self) {
|
||||
self.enabled.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Push a packet into the capture buffer. If the buffer is full,
|
||||
/// the oldest packet is dropped.
|
||||
pub fn capture(&self, packet: &[u8]) {
|
||||
if !self.is_enabled() {
|
||||
return;
|
||||
}
|
||||
let mut buf = self.buffer.borrow_mut();
|
||||
if buf.len() >= self.max_packets {
|
||||
buf.remove(0);
|
||||
}
|
||||
let mut copy = Vec::with_capacity(packet.len().min(MAX_CAPTURE_BYTES / self.max_packets));
|
||||
copy.extend_from_slice(packet);
|
||||
buf.push(copy);
|
||||
self.total_captured.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Drain all captured packets and return them as a formatted hex dump.
|
||||
pub fn drain_hex(&self) -> String {
|
||||
let mut out = String::new();
|
||||
let mut buf = self.buffer.borrow_mut();
|
||||
for packet in buf.drain(..) {
|
||||
out.push_str(&format!("# {} bytes\n", packet.len()));
|
||||
for chunk in packet.chunks(16) {
|
||||
for byte in chunk {
|
||||
out.push_str(&format!("{:02x} ", byte));
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Number of packets currently in the buffer.
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.borrow().len()
|
||||
}
|
||||
|
||||
/// Total packets captured since startup.
|
||||
pub fn total(&self) -> u64 {
|
||||
self.total_captured.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user