feat(obs): report broadcast frame counters
PLAN.md calls out "Broadcast traffic flowing" as a user-facing diagnostic. The tunnel stats only reported total Ethernet frame counts, so the client could not distinguish whether broadcast traffic was actually crossing the tunnel. Add defaulted broadcast tx/rx counters to TunnelStats while preserving the existing constructor and old JSON compatibility. Client and gateway accounting now increments those counters from validated Ethernet frames, and the client diagnostics line reports the broadcast flow next to total frame counts. Relay peer stats logs include the new counters so operators can see broadcast activity from forwarded stats snapshots too. Test Plan: - cargo fmt --check - cargo test -p lanparty-obs -p lanparty-client-core -p lanparty-gateway \ -p lanparty-client-win -p lanparty-relay - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md
This commit is contained in:
@@ -328,10 +328,13 @@ impl ClientRelayIo {
|
||||
}
|
||||
|
||||
pub fn send_ethernet(&self, frame: &[u8]) -> Result<()> {
|
||||
if let Err(error) = EthernetFrame::parse(frame) {
|
||||
self.stats.record_malformed_frame();
|
||||
return Err(error).context("client Ethernet frame is malformed");
|
||||
}
|
||||
let ethernet_frame = match EthernetFrame::parse(frame) {
|
||||
Ok(frame) => frame,
|
||||
Err(error) => {
|
||||
self.stats.record_malformed_frame();
|
||||
return Err(error).context("client Ethernet frame is malformed");
|
||||
}
|
||||
};
|
||||
let datagram = encode_datagram(
|
||||
FrameType::Ethernet,
|
||||
self.welcome.room_id(),
|
||||
@@ -344,7 +347,7 @@ impl ClientRelayIo {
|
||||
self.connection
|
||||
.send_datagram(Bytes::from(datagram))
|
||||
.context("failed to send client Ethernet datagram")?;
|
||||
self.stats.record_ethernet_tx();
|
||||
self.stats.record_ethernet_tx(ethernet_frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -365,12 +368,15 @@ impl ClientRelayIo {
|
||||
self.stats.record_dropped_frame();
|
||||
continue;
|
||||
}
|
||||
if EthernetFrame::parse(packet.payload()).is_err() {
|
||||
self.stats.record_malformed_frame();
|
||||
continue;
|
||||
}
|
||||
let ethernet_frame = match EthernetFrame::parse(packet.payload()) {
|
||||
Ok(frame) => frame,
|
||||
Err(_) => {
|
||||
self.stats.record_malformed_frame();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
self.stats.record_ethernet_rx();
|
||||
self.stats.record_ethernet_rx(ethernet_frame);
|
||||
return Ok(ReceivedEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
@@ -393,6 +399,8 @@ impl ClientRelayIo {
|
||||
struct ClientTunnelStats {
|
||||
ethernet_frames_tx: AtomicU64,
|
||||
ethernet_frames_rx: AtomicU64,
|
||||
broadcast_frames_tx: AtomicU64,
|
||||
broadcast_frames_rx: AtomicU64,
|
||||
datagrams_tx: AtomicU64,
|
||||
datagrams_rx: AtomicU64,
|
||||
dropped_frames: AtomicU64,
|
||||
@@ -400,13 +408,19 @@ struct ClientTunnelStats {
|
||||
}
|
||||
|
||||
impl ClientTunnelStats {
|
||||
fn record_ethernet_tx(&self) {
|
||||
fn record_ethernet_tx(&self, frame: EthernetFrame<'_>) {
|
||||
self.ethernet_frames_tx.fetch_add(1, Ordering::Relaxed);
|
||||
if frame.is_broadcast() {
|
||||
self.broadcast_frames_tx.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
self.datagrams_tx.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn record_ethernet_rx(&self) {
|
||||
fn record_ethernet_rx(&self, frame: EthernetFrame<'_>) {
|
||||
self.ethernet_frames_rx.fetch_add(1, Ordering::Relaxed);
|
||||
if frame.is_broadcast() {
|
||||
self.broadcast_frames_rx.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn record_datagram_rx(&self) {
|
||||
@@ -431,6 +445,10 @@ impl ClientTunnelStats {
|
||||
self.dropped_frames.load(Ordering::Relaxed),
|
||||
self.malformed_frames.load(Ordering::Relaxed),
|
||||
)
|
||||
.with_broadcast_frames(
|
||||
self.broadcast_frames_tx.load(Ordering::Relaxed),
|
||||
self.broadcast_frames_rx.load(Ordering::Relaxed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,6 +824,8 @@ mod tests {
|
||||
let stats = relay_io.stats_snapshot();
|
||||
assert_eq!(stats.ethernet_frames_tx(), 1);
|
||||
assert_eq!(stats.ethernet_frames_rx(), 1);
|
||||
assert_eq!(stats.broadcast_frames_tx(), 0);
|
||||
assert_eq!(stats.broadcast_frames_rx(), 0);
|
||||
assert_eq!(stats.datagrams_tx(), 1);
|
||||
assert_eq!(stats.datagrams_rx(), 1);
|
||||
assert_eq!(stats.dropped_frames(), 1);
|
||||
@@ -827,16 +847,22 @@ mod tests {
|
||||
#[test]
|
||||
fn snapshots_client_tunnel_stats() {
|
||||
let stats = ClientTunnelStats::default();
|
||||
let broadcast_tx_bytes = broadcast_ethernet_frame(b"broadcast tx");
|
||||
let broadcast_rx_bytes = broadcast_ethernet_frame(b"broadcast rx");
|
||||
let broadcast_tx = EthernetFrame::parse(&broadcast_tx_bytes).unwrap();
|
||||
let broadcast_rx = EthernetFrame::parse(&broadcast_rx_bytes).unwrap();
|
||||
|
||||
stats.record_ethernet_tx();
|
||||
stats.record_ethernet_tx(broadcast_tx);
|
||||
stats.record_datagram_rx();
|
||||
stats.record_ethernet_rx();
|
||||
stats.record_ethernet_rx(broadcast_rx);
|
||||
stats.record_dropped_frame();
|
||||
stats.record_malformed_frame();
|
||||
let snapshot = stats.snapshot();
|
||||
|
||||
assert_eq!(snapshot.ethernet_frames_tx(), 1);
|
||||
assert_eq!(snapshot.ethernet_frames_rx(), 1);
|
||||
assert_eq!(snapshot.broadcast_frames_tx(), 1);
|
||||
assert_eq!(snapshot.broadcast_frames_rx(), 1);
|
||||
assert_eq!(snapshot.datagrams_tx(), 1);
|
||||
assert_eq!(snapshot.datagrams_rx(), 1);
|
||||
assert_eq!(snapshot.dropped_frames(), 2);
|
||||
@@ -876,6 +902,15 @@ mod tests {
|
||||
frame
|
||||
}
|
||||
|
||||
fn broadcast_ethernet_frame(payload: &[u8]) -> Vec<u8> {
|
||||
let mut frame = Vec::new();
|
||||
frame.extend_from_slice(&MacAddr::BROADCAST.octets());
|
||||
frame.extend_from_slice(&[0x02, 0, 0, 0, 0, 1]);
|
||||
frame.extend_from_slice(&0x0800_u16.to_be_bytes());
|
||||
frame.extend_from_slice(payload);
|
||||
frame
|
||||
}
|
||||
|
||||
fn unique_temp_identity_path() -> std::path::PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
||||
Reference in New Issue
Block a user