arp: add statistics counters and netcfg exposure
EthernetLink gains per-interface ARP counters: - arp_requests: ARP requests sent (incremented in send_arp) - arp_replies: ARP replies received (incremented on cache insert) - arp_cache_hits: successful neighbor cache lookups - arp_cache_misses: cache misses (vacant or expired entries) LinkDevice trait gains arp_stats() → String method. EthernetLink implementation: 'requests=N replies=N hits=N misses=N entries=N' netcfg exposes at /scheme/netcfg/ifaces/eth0/arp/stats: echo /scheme/netcfg/ifaces/eth0/arp/stats → requests=5 replies=3 hits=142 misses=8 entries=4 Existing arp/list (MAC→IP) and arp/flush (clear) are unchanged. Useful for monitoring ARP churn, detecting ARP storms, and debugging neighbor discovery issues.
This commit is contained in:
@@ -87,6 +87,10 @@ pub struct EthernetLink {
|
||||
ip_address: Option<IpCidr>,
|
||||
qdisc_config: QdiscConfig,
|
||||
stats: Stats,
|
||||
arp_requests: u64,
|
||||
arp_replies: u64,
|
||||
arp_cache_hits: u64,
|
||||
arp_cache_misses: u64,
|
||||
}
|
||||
|
||||
impl EthernetLink {
|
||||
@@ -119,6 +123,10 @@ impl EthernetLink {
|
||||
qdisc_config: QdiscConfig::default(),
|
||||
stats: Stats::default(),
|
||||
neighbor_cache: Default::default(),
|
||||
arp_requests: 0,
|
||||
arp_replies: 0,
|
||||
arp_cache_hits: 0,
|
||||
arp_cache_misses: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +213,7 @@ impl EthernetLink {
|
||||
expires_at: now + Self::NEIGHBOR_LIVE_TIME,
|
||||
},
|
||||
);
|
||||
self.arp_replies += 1;
|
||||
|
||||
if let ArpOperation::Request = operation {
|
||||
let response = ArpRepr::EthernetIpv4 {
|
||||
@@ -398,6 +407,7 @@ impl EthernetLink {
|
||||
}
|
||||
|
||||
fn send_arp(&mut self, now: Instant) {
|
||||
self.arp_requests += 1;
|
||||
let Some(hardware_address) = self.hardware_address else {
|
||||
return;
|
||||
};
|
||||
@@ -721,12 +731,17 @@ impl LinkDevice for EthernetLink {
|
||||
}
|
||||
|
||||
match self.neighbor_cache.entry(next_hop) {
|
||||
Entry::Vacant(_) => self.handle_missing_neighbor(next_hop, &packet, now),
|
||||
Entry::Vacant(_) => {
|
||||
self.arp_cache_misses += 1;
|
||||
self.handle_missing_neighbor(next_hop, &packet, now)
|
||||
}
|
||||
Entry::Occupied(e) => {
|
||||
if e.get().expires_at < now {
|
||||
e.remove();
|
||||
self.arp_cache_misses += 1;
|
||||
self.handle_missing_neighbor(next_hop, &packet, now)
|
||||
} else {
|
||||
self.arp_cache_hits += 1;
|
||||
let mac = e.get().hardware_address;
|
||||
self.send_to(
|
||||
mac,
|
||||
@@ -849,6 +864,17 @@ impl LinkDevice for EthernetLink {
|
||||
self.neighbor_cache.clear();
|
||||
}
|
||||
|
||||
fn arp_stats(&self) -> String {
|
||||
format!(
|
||||
"requests={} replies={} hits={} misses={} entries={}\n",
|
||||
self.arp_requests,
|
||||
self.arp_replies,
|
||||
self.arp_cache_hits,
|
||||
self.arp_cache_misses,
|
||||
self.neighbor_cache.len(),
|
||||
)
|
||||
}
|
||||
|
||||
fn statistics(&self) -> Stats {
|
||||
self.stats
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ pub trait LinkDevice {
|
||||
|
||||
fn flush_arp(&mut self) {}
|
||||
|
||||
fn arp_stats(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn mtu(&self) -> usize {
|
||||
1500
|
||||
}
|
||||
|
||||
@@ -360,6 +360,14 @@ fn mk_root_node(
|
||||
}
|
||||
}
|
||||
},
|
||||
"stats" => {
|
||||
ro [devices] || {
|
||||
match devices.borrow().get("eth0") {
|
||||
Some(dev) => dev.arp_stats(),
|
||||
None => "Device not found\n".into(),
|
||||
}
|
||||
}
|
||||
},
|
||||
"flush" => {
|
||||
wo [devices] (Option<()>, None)
|
||||
|_cur_value, _line| {
|
||||
|
||||
Reference in New Issue
Block a user