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
This commit is contained in:
2026-05-21 22:25:28 +02:00
parent 733badd2a8
commit d503533a3c
2 changed files with 14 additions and 3 deletions
+1
View File
@@ -98,6 +98,7 @@ Public relay binary and relay-owned room state:
- one gateway per room, duplicate client MAC rejection, and room limits - one gateway per room, duplicate client MAC rejection, and room limits
- stable effective room MTU chosen before Ethernet datagrams flow - stable effective room MTU chosen before Ethernet datagrams flow
- live Ethernet datagram forwarding with no ingress reflection - 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 - reliable `PeerJoined`/`PeerLeft` notifications to existing room peers
- L2 safety filters for invalid-source, jumbo, switch-control, DHCP-server, - L2 safety filters for invalid-source, jumbo, switch-control, DHCP-server,
and IPv6-RA frames and IPv6-RA frames
+13 -3
View File
@@ -762,6 +762,7 @@ async fn build_handshake_response(
Ok(hello) => hello, Ok(hello) => hello,
Err(reject) => return (None, ControlMessage::Reject(reject)), 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); let join = rooms.lock().await.join(hello);
match join { match join {
@@ -771,7 +772,7 @@ async fn build_handshake_response(
welcome: join.welcome().clone(), welcome: join.welcome().clone(),
peer: join.peer().clone(), peer: join.peer().clone(),
remote_addr: connection.remote_address(), 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() .unwrap()
.await .await
.unwrap(); .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( let hello = EndpointHello::client(
RoomCode::new("TESTROOM").unwrap(), RoomCode::new("TESTROOM").unwrap(),
MacAddr::new([0x02, 0, 0, 0, 0, 1]), MacAddr::new([0x02, 0, 0, 0, 0, 1]),
1400, advertised_datagram_size,
) )
.unwrap(); .unwrap();
let response = request_control_message(&connection, ControlMessage::Hello(hello)) let response = request_control_message(&connection, ControlMessage::Hello(hello))
@@ -966,7 +972,7 @@ mod tests {
assert_eq!(welcome.room_id(), 1); assert_eq!(welcome.room_id(), 1);
assert_eq!(welcome.peer_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"); connection.close(0_u32.into(), b"test complete");
client.wait_idle().await; client.wait_idle().await;
@@ -978,6 +984,10 @@ mod tests {
assert_eq!(accepted.room.as_str(), "TESTROOM"); assert_eq!(accepted.room.as_str(), "TESTROOM");
assert_eq!(accepted.peer.peer_id(), 1); assert_eq!(accepted.peer.peer_id(), 1);
assert_eq!(accepted.welcome, welcome); assert_eq!(accepted.welcome, welcome);
assert_eq!(
accepted.max_datagram_size,
usize::from(advertised_datagram_size)
);
assert_eq!(rooms.lock().await.room_count(), 0); assert_eq!(rooms.lock().await.room_count(), 0);
} }