From 608e1a6f55b45798da7c3fd1f1ab6025cc109a57 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Fri, 22 May 2026 06:58:20 +0200 Subject: [PATCH] feat(obs): distinguish one-way broadcast flow The client previously reported "Broadcast traffic flowing" as soon as either broadcast TX or RX was nonzero. During the MVP DHCP/ARP proof, one-way broadcast is useful but weaker evidence than bidirectional broadcast. Keep the existing healthy message for two-way broadcast, but report outbound-only broadcast as a warning that the client is still waiting for a LAN broadcast reply, and report inbound-only broadcast separately. This makes the Windows client status lines more precise when DHCP is stuck. Test Plan: - cargo test -p lanparty-obs - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check - git diff --cached --check Refs: MVP Windows DHCP diagnostics --- README.md | 3 +- TESTING.md | 2 ++ crates/lanparty-obs/src/lib.rs | 62 ++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f26f3bb..130360e 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,8 @@ after bridging starts become visible in later status lines, preferring a non-link-local IPv4 address when Windows reports several TAP addresses. Each snapshot also emits short user-facing lines such as relay/gateway connection status, relay-route and TAP readiness warnings, DHCP address presence, relay RTT, and -broadcast-flow confirmation when those signals are observed. Malformed frames +broadcast-flow confirmation. One-way broadcast diagnostics distinguish frames +sent toward the LAN from broadcast frames received back from the LAN. Malformed frames read from TAP, invalid or unauthorized source-MAC frames, L2 control-plane traffic, remote VLAN tags, DHCP server replies, IPv6 Router Advertisements, IPv6 fragments, jumbo frames, and TAP frames whose encoded datagrams exceed the diff --git a/TESTING.md b/TESTING.md index 0e63876..e49519d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -246,6 +246,8 @@ Client health: ```text Relay RTT: 23 ms Broadcast traffic flowing +Broadcast sent toward LAN; waiting for LAN broadcast reply +LAN broadcast received client frame direction=TapToRelay ... action=Forwarded drop_reason=- client frame direction=RelayToTap ... action=Forwarded drop_reason=- ``` diff --git a/crates/lanparty-obs/src/lib.rs b/crates/lanparty-obs/src/lib.rs index 87d0c7b..14e2d3a 100644 --- a/crates/lanparty-obs/src/lib.rs +++ b/crates/lanparty-obs/src/lib.rs @@ -447,11 +447,23 @@ impl ClientDiagnostics { )); } - if self.stats.broadcast_frames_tx() > 0 || self.stats.broadcast_frames_rx() > 0 { - diagnostics.push(UserDiagnostic::new( + match ( + self.stats.broadcast_frames_tx() > 0, + self.stats.broadcast_frames_rx() > 0, + ) { + (true, true) => diagnostics.push(UserDiagnostic::new( UserDiagnosticLevel::Info, "Broadcast traffic flowing", - )); + )), + (true, false) => diagnostics.push(UserDiagnostic::new( + UserDiagnosticLevel::Warning, + "Broadcast sent toward LAN; waiting for LAN broadcast reply", + )), + (false, true) => diagnostics.push(UserDiagnostic::new( + UserDiagnosticLevel::Info, + "LAN broadcast received", + )), + (false, false) => {} } diagnostics @@ -723,6 +735,50 @@ mod tests { assert_eq!(user_diagnostics[2].level(), UserDiagnosticLevel::Warning); } + #[test] + fn distinguishes_one_way_broadcast_diagnostics() { + let mut diagnostics = ClientDiagnostics::new( + RelayDiagnostics::new(true, true, true), + QuicDiagnostics::new(true, Some(1400)), + TapDiagnostics::new( + true, + Some(MacAddr::new([0x02, 1, 2, 3, 4, 5])), + Some(1200), + None, + ), + TunnelStats::default().with_broadcast_frames(1, 0), + ); + let broadcast = diagnostics + .user_diagnostics() + .into_iter() + .find(|diagnostic| diagnostic.message().contains("Broadcast")) + .expect("outbound broadcast should produce a diagnostic"); + assert_eq!(broadcast.level(), UserDiagnosticLevel::Warning); + assert_eq!( + broadcast.message(), + "Broadcast sent toward LAN; waiting for LAN broadcast reply" + ); + + diagnostics = ClientDiagnostics::new( + RelayDiagnostics::new(true, true, true), + QuicDiagnostics::new(true, Some(1400)), + TapDiagnostics::new( + true, + Some(MacAddr::new([0x02, 1, 2, 3, 4, 5])), + Some(1200), + None, + ), + TunnelStats::default().with_broadcast_frames(0, 1), + ); + let broadcast = diagnostics + .user_diagnostics() + .into_iter() + .find(|diagnostic| diagnostic.message().contains("broadcast")) + .expect("inbound broadcast should produce a diagnostic"); + assert_eq!(broadcast.level(), UserDiagnosticLevel::Info); + assert_eq!(broadcast.message(), "LAN broadcast received"); + } + #[test] fn reports_link_local_tap_ipv4_as_waiting_for_lan_dhcp() { let diagnostic = tap_ip_user_diagnostic(Some("169.254.10.20".parse().unwrap())).unwrap();