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
This commit is contained in:
2026-05-21 22:50:05 +02:00
parent cd8a536771
commit d4c96569e3
2 changed files with 41 additions and 7 deletions
+35 -3
View File
@@ -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<PacketSocket>) -> Result<Byte
match guard.try_io(|inner| inner.get_ref().recv_frame(&mut buffer)) {
Ok(Ok(len)) => {
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();