From d4c96569e3a986c63355e65cecebeed655a68698 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 22:50:05 +0200 Subject: [PATCH] fix(gateway): account malformed LAN captures The Linux AF_PACKET read helper discarded malformed or runt LAN captures before the bridge loop saw them. That made those frames invisible to gateway drop counters and frame logs, which is not great for the phase-one heavy diagnostics called for in the plan. Return raw inbound capture bytes from the read helper and let the bridge loop make the drop decision. Malformed LAN frames are now counted as malformed drops, logged with the normal gateway frame log shape, and skipped without stopping the bridge. Valid LAN frames still flow through the existing send path and budget checks. Document the accounting behavior in the gateway README section. Test Plan: - cargo fmt --check - git diff --check - cargo test -p lanparty-gateway - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings Refs: PLAN.md --- README.md | 10 ++++---- crates/lanparty-gateway/src/lib.rs | 38 +++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 23fb6ad..1884861 100644 --- a/README.md +++ b/README.md @@ -179,10 +179,12 @@ emits small CAM refresh frames so the physical switch keeps those MACs associated with the gateway port. Gateway frame logs include direction, peer id when present, MACs, ethertype/length, frame length, action, and drop reason. The gateway also tracks frame/datagram -counters and periodically sends stats snapshots to the relay. Relay lifecycle -events seed and retire remote-client MACs for CAM refresh even before that -client sends traffic. On shutdown, the gateway sends a best-effort disconnect -control message before closing QUIC so the relay can report the intended reason. +counters and periodically sends stats snapshots to the relay. Malformed or runt +LAN frames are counted and logged as dropped instead of disappearing before +accounting. Relay lifecycle events seed and retire remote-client MACs for CAM +refresh even before that client sends traffic. On shutdown, the gateway sends a +best-effort disconnect control message before closing QUIC so the relay can +report the intended reason. ## Windows Client diff --git a/crates/lanparty-gateway/src/lib.rs b/crates/lanparty-gateway/src/lib.rs index 74acd19..1bd7a48 100644 --- a/crates/lanparty-gateway/src/lib.rs +++ b/crates/lanparty-gateway/src/lib.rs @@ -319,6 +319,22 @@ impl GatewayConnection { } lan_frame = read_lan_ethernet(&packet_socket) => { let lan_frame = lan_frame?; + if EthernetFrame::parse(&lan_frame).is_err() { + stats.record_malformed_frame(); + println!( + "{}", + gateway_frame_log_line( + packet_socket.get_ref().interface(), + FrameDirection::LanToRemote, + Some(welcome.peer_id()), + &lan_frame, + FrameAction::Dropped, + Some(DropReason::Malformed), + ) + ); + continue; + } + let outcome = send_gateway_ethernet( &connection, &welcome, @@ -672,9 +688,7 @@ async fn read_lan_ethernet(packet_socket: &AsyncFd) -> Result { buffer.truncate(len); - if EthernetFrame::parse(&buffer).is_ok() { - return Ok(Bytes::from(buffer)); - } + return Ok(Bytes::from(buffer)); } Ok(Err(error)) => return Err(error).context("failed to read LAN Ethernet frame"), Err(_would_block) => continue, @@ -1186,6 +1200,24 @@ mod tests { assert!(line.contains("drop_reason=DatagramBudget")); } + #[cfg(target_os = "linux")] + #[test] + fn formats_gateway_malformed_lan_drops() { + let line = gateway_frame_log_line( + "eth0", + FrameDirection::LanToRemote, + Some(1), + &[0; 4], + FrameAction::Dropped, + Some(DropReason::Malformed), + ); + + assert_eq!( + line, + "gateway frame interface=eth0 direction=LanToRemote peer_id=1 src=- dst=- ethertype_or_len=- len=4 action=Dropped drop_reason=Malformed" + ); + } + fn test_server_config() -> (ServerConfig, CertificateDer<'static>) { let certified_key = rcgen::generate_simple_self_signed(vec!["lanparty-relay.local".into()]).unwrap();