From d2cf20f597547369916fc838af8853415eae7960 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 22:13:18 +0200 Subject: [PATCH] feat(relay): log egress datagram budget skips PLAN.md keeps MVP forwarding to one Ethernet frame per QUIC datagram with no fragmentation. The relay already skipped targets whose negotiated datagram budget could not carry an encoded forwarded frame, but that skip was silent. Retain the no-fragmentation behavior and log the skipped egress target with the room, ingress peer, target peer, encoded length, and target datagram budget. Store the peer id in relay sessions so the diagnostic can identify the skipped target directly. Document the egress budget-skip diagnostic in the relay README section. Test Plan: - cargo fmt --check - cargo test -p lanparty-relay - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md --- README.md | 2 ++ crates/lanparty-relay/src/server.rs | 40 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/README.md b/README.md index 3530880..1d33777 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ Safety-policy rejects use the `filtered` action so they are distinguishable from malformed/unknown-destination drops and rate limits. Malformed peer datagrams log their per-peer count before the relay disconnects peers that cross the malformed-datagram threshold. +Relay egress skips caused by a target peer's smaller datagram budget are logged +with the ingress peer, target peer, encoded length, and target budget. Unknown unicast from a client is forwarded only to the gateway port; unknown unicast from the gateway is dropped instead of flooded to every remote client. When a peer joins or leaves, the relay sends a reliable lifecycle control event diff --git a/crates/lanparty-relay/src/server.rs b/crates/lanparty-relay/src/server.rs index fba5570..11a87c5 100644 --- a/crates/lanparty-relay/src/server.rs +++ b/crates/lanparty-relay/src/server.rs @@ -56,6 +56,7 @@ impl PeerKey { #[derive(Debug, Clone)] struct PeerSession { connection: quinn::Connection, + peer_id: u32, max_datagram_size: usize, latest_stats: Option, } @@ -299,6 +300,7 @@ async fn register_peer( PeerKey::from_accepted(accepted), PeerSession { connection, + peer_id: accepted.peer.peer_id(), max_datagram_size: accepted.max_datagram_size, latest_stats: None, }, @@ -429,6 +431,16 @@ async fn forward_peer_datagram( for target in target_sessions { if outgoing.len() > target.max_datagram_size { + eprintln!( + "{}", + egress_budget_skip_log_line( + &accepted.room, + accepted.peer.peer_id(), + target.peer_id, + outgoing.len(), + target.max_datagram_size, + ) + ); continue; } @@ -590,6 +602,19 @@ fn malformed_datagram_log_line( ) } +fn egress_budget_skip_log_line( + room: &RoomCode, + ingress_peer_id: u32, + target_peer_id: u32, + datagram_len: usize, + max_datagram_size: usize, +) -> String { + format!( + "relay egress skipped room={} peer_id={} target_peer_id={} len={} max_datagram_size={} reason=datagram_budget", + room, ingress_peer_id, target_peer_id, datagram_len, max_datagram_size + ) +} + async fn collect_target_sessions( sessions: &Arc>>, room: &RoomCode, @@ -1030,6 +1055,21 @@ mod tests { assert!(line.contains("disconnecting=true")); } + #[test] + fn formats_egress_budget_skip_log_line() { + let room = RoomCode::new("TESTROOM").unwrap(); + + let line = egress_budget_skip_log_line(&room, 2, 7, 1401, 1400); + + assert!(line.contains("relay egress skipped")); + assert!(line.contains("room=TESTROOM")); + assert!(line.contains("peer_id=2")); + assert!(line.contains("target_peer_id=7")); + assert!(line.contains("len=1401")); + assert!(line.contains("max_datagram_size=1400")); + assert!(line.contains("reason=datagram_budget")); + } + #[tokio::test] async fn classifies_bad_peer_datagrams_as_malformed() { let rooms = Arc::new(Mutex::new(RoomRegistry::default()));