fix(tunnel): enforce negotiated TAP MTU
The MVP tunnel negotiates an effective TAP MTU and configures the Windows TAP IP interface to that value, but the forwarding path only rejected frames that were standard-Ethernet jumbo frames or exceeded the QUIC datagram budget. A frame could therefore be larger than the negotiated TAP MTU while still fitting inside the QUIC datagram budget. Make the TAP-MTU frame limit an explicit shared protocol helper and enforce it at every data-path boundary: Windows client send/receive, Linux gateway send/receive, and relay forwarding. Such frames now produce TapMtuExceeded in logs and counters instead of being forwarded until a later layer drops or accepts them implicitly. This keeps the no-fragmentation contract honest: one Ethernet frame still maps to one QUIC datagram, but only if that frame also fits the room's negotiated TAP MTU. Test Plan: - cargo fmt --check - cargo test -p lanparty-proto tap_mtu - cargo test -p lanparty-client-core connects_to_relay_control_stream_as_client - cargo test -p lanparty-gateway connects_to_relay_control_stream_as_gateway - cargo test -p lanparty-relay drops_frames_above_effective_tap_mtu - cargo test -p lanparty-relay rate_limits_client_total_bandwidth_after_burst - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - cargo build --release -p lanparty-relay -p lanparty-gateway - git diff --check - git diff --cached --check Refs: MVP no-fragmentation tunnel MTU contract
This commit is contained in:
@@ -33,7 +33,8 @@ use lanparty_obs::{DropReason, TunnelStats};
|
||||
use lanparty_obs::{FrameAction, FrameDirection, FrameLog};
|
||||
use lanparty_proto::{
|
||||
EthernetFrame, FrameType, MacAddr, OVERLAY_FLAGS_NONE, decode_datagram, encode_datagram,
|
||||
gateway_lan_safety_drop_reason, remote_client_safety_drop_reason, validate_datagram_budget,
|
||||
ethernet_frame_exceeds_tap_mtu, gateway_lan_safety_drop_reason,
|
||||
remote_client_safety_drop_reason, validate_datagram_budget,
|
||||
};
|
||||
use quinn::{ClientConfig, Endpoint, crypto::rustls::QuicClientConfig};
|
||||
use rustls::pki_types::CertificateDer;
|
||||
@@ -515,6 +516,10 @@ fn send_gateway_ethernet(
|
||||
stats.record_dropped_frame();
|
||||
return Ok(GatewaySendOutcome::Dropped(DropReason::from(drop_reason)));
|
||||
}
|
||||
if ethernet_frame_exceeds_tap_mtu(ethernet_frame, usize::from(welcome.effective_tap_mtu())) {
|
||||
stats.record_dropped_frame();
|
||||
return Ok(GatewaySendOutcome::Dropped(DropReason::TapMtuExceeded));
|
||||
}
|
||||
|
||||
let datagram = encode_datagram(
|
||||
FrameType::Ethernet,
|
||||
@@ -595,6 +600,17 @@ async fn recv_gateway_ethernet_outcome(
|
||||
},
|
||||
));
|
||||
}
|
||||
if ethernet_frame_exceeds_tap_mtu(ethernet_frame, usize::from(welcome.effective_tap_mtu()))
|
||||
{
|
||||
stats.record_dropped_frame();
|
||||
return Ok(GatewayReceiveOutcome::Filtered(
|
||||
FilteredRelayEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
drop_reason: DropReason::TapMtuExceeded,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return Ok(GatewayReceiveOutcome::Accepted(ReceivedEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
@@ -1207,7 +1223,7 @@ mod tests {
|
||||
let ControlMessage::Stats(stats) = stats_message else {
|
||||
panic!("expected gateway stats event");
|
||||
};
|
||||
assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 5, 1));
|
||||
assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 6, 1));
|
||||
stats_received_tx.send(()).unwrap();
|
||||
|
||||
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
||||
@@ -1264,6 +1280,14 @@ mod tests {
|
||||
.send_ethernet(ðernet_frame_from(MacAddr::BROADCAST, b"invalid source"))
|
||||
.is_err()
|
||||
);
|
||||
let tap_oversized_payload = vec![0; usize::from(gateway.welcome().effective_tap_mtu()) + 1];
|
||||
let tap_mtu_error = gateway
|
||||
.send_ethernet(ðernet_frame(&tap_oversized_payload))
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
tap_mtu_error.to_string().contains("TapMtuExceeded"),
|
||||
"expected TapMtuExceeded error, got {tap_mtu_error:#}"
|
||||
);
|
||||
gateway.send_ethernet(ðernet_frame(b"to relay")).unwrap();
|
||||
let received = tokio::time::timeout(Duration::from_secs(5), gateway.recv_ethernet())
|
||||
.await
|
||||
@@ -1283,7 +1307,7 @@ mod tests {
|
||||
.is_err()
|
||||
);
|
||||
let stats = gateway.stats_snapshot();
|
||||
assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 5, 1));
|
||||
assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 6, 1));
|
||||
|
||||
gateway.send_stats_snapshot().await.unwrap();
|
||||
tokio::time::timeout(Duration::from_secs(5), stats_received_rx)
|
||||
|
||||
Reference in New Issue
Block a user