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