From d503533a3c62f052711f2e3663602d47b417dc99 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 22:25:28 +0200 Subject: [PATCH] fix(relay): honor advertised egress budgets Peers announce a QUIC datagram budget in their hello, and the relay clamps that value against the transport's negotiated max before room admission. The relay used that clamped value for MTU selection, but stored the raw transport budget in the live peer session. A peer that intentionally advertised a smaller budget could therefore receive egress datagrams larger than it promised to accept. Store the post-clamp hello budget in AcceptedPeer and PeerSession instead. That keeps the existing relay egress skip path tied to the same negotiated size used for room MTU decisions. The handshake regression now advertises a budget below the QUIC transport budget and asserts that the accepted peer records the advertised value. The README decomposition also calls out the per-peer egress-budget invariant. Test Plan: - cargo fmt --check - cargo test -p lanparty-relay - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check - git diff --cached --check Refs: PLAN.md No fragmentation for MVP --- README.md | 1 + crates/lanparty-relay/src/server.rs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a022c5c..0c88a19 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ Public relay binary and relay-owned room state: - one gateway per room, duplicate client MAC rejection, and room limits - stable effective room MTU chosen before Ethernet datagrams flow - live Ethernet datagram forwarding with no ingress reflection +- per-peer egress budget checks against the negotiated datagram size - reliable `PeerJoined`/`PeerLeft` notifications to existing room peers - L2 safety filters for invalid-source, jumbo, switch-control, DHCP-server, and IPv6-RA frames diff --git a/crates/lanparty-relay/src/server.rs b/crates/lanparty-relay/src/server.rs index 11a87c5..a99af70 100644 --- a/crates/lanparty-relay/src/server.rs +++ b/crates/lanparty-relay/src/server.rs @@ -762,6 +762,7 @@ async fn build_handshake_response( Ok(hello) => hello, Err(reject) => return (None, ControlMessage::Reject(reject)), }; + let peer_max_datagram_size = usize::from(hello.max_datagram_size()); let join = rooms.lock().await.join(hello); match join { @@ -771,7 +772,7 @@ async fn build_handshake_response( welcome: join.welcome().clone(), peer: join.peer().clone(), remote_addr: connection.remote_address(), - max_datagram_size: connection_max_datagram_size, + max_datagram_size: peer_max_datagram_size, }; ( @@ -950,10 +951,15 @@ mod tests { .unwrap() .await .unwrap(); + let advertised_datagram_size = 1000; + assert!( + connection.max_datagram_size().unwrap() > usize::from(advertised_datagram_size), + "test must advertise less than the QUIC transport budget" + ); let hello = EndpointHello::client( RoomCode::new("TESTROOM").unwrap(), MacAddr::new([0x02, 0, 0, 0, 0, 1]), - 1400, + advertised_datagram_size, ) .unwrap(); let response = request_control_message(&connection, ControlMessage::Hello(hello)) @@ -966,7 +972,7 @@ mod tests { assert_eq!(welcome.room_id(), 1); assert_eq!(welcome.peer_id(), 1); - assert!(welcome.effective_tap_mtu() <= 1400); + assert!(welcome.effective_tap_mtu() <= advertised_datagram_size); connection.close(0_u32.into(), b"test complete"); client.wait_idle().await; @@ -978,6 +984,10 @@ mod tests { assert_eq!(accepted.room.as_str(), "TESTROOM"); assert_eq!(accepted.peer.peer_id(), 1); assert_eq!(accepted.welcome, welcome); + assert_eq!( + accepted.max_datagram_size, + usize::from(advertised_datagram_size) + ); assert_eq!(rooms.lock().await.room_count(), 0); }