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();