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:
@@ -103,6 +103,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
|
||||||
|
- forwarding drops for Ethernet frames above the negotiated TAP MTU
|
||||||
- per-peer egress budget checks against the negotiated datagram size
|
- 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, remote VLAN
|
- L2 safety filters for invalid-source, jumbo, switch-control, remote VLAN
|
||||||
@@ -185,14 +186,15 @@ frames between the relay and wired LAN until shutdown. It captures whole LAN
|
|||||||
frames up to the
|
frames up to the
|
||||||
overlay payload-length ceiling before deciding whether they fit the tunnel. It
|
overlay payload-length ceiling before deciding whether they fit the tunnel. It
|
||||||
never fragments Ethernet frames; LAN frames with invalid source MACs, L2
|
never fragments Ethernet frames; LAN frames with invalid source MACs, L2
|
||||||
control-plane traffic, jumbo frames, or encoded datagrams exceeding the
|
control-plane traffic, jumbo frames, frames above the negotiated TAP MTU, or
|
||||||
negotiated QUIC budget are counted, dropped, and logged locally instead of
|
encoded datagrams exceeding the negotiated QUIC budget are counted, dropped,
|
||||||
stopping the bridge or consuming relay bandwidth. Remote frames received from
|
and logged locally instead of stopping the bridge or consuming relay bandwidth.
|
||||||
|
Remote frames received from
|
||||||
the relay are safety-checked again before LAN injection and must use the
|
the relay are safety-checked again before LAN injection and must use the
|
||||||
announced virtual MAC for their source peer, so invalid-source, forged-source,
|
announced virtual MAC for their source peer, so invalid-source, forged-source,
|
||||||
L2 control-plane, remote VLAN, DHCP-server, IPv6 Router Advertisement, IPv6
|
L2 control-plane, remote VLAN, DHCP-server, IPv6 Router Advertisement, IPv6
|
||||||
fragment, and jumbo frames cannot cross the gateway's final physical-LAN
|
fragment, jumbo, and over-TAP-MTU frames cannot cross the gateway's final
|
||||||
boundary even if they reached the gateway over QUIC.
|
physical-LAN boundary even if they reached the gateway over QUIC.
|
||||||
`--relay` accepts a DNS name or socket address; bare hosts default to UDP/443.
|
`--relay` accepts a DNS name or socket address; bare hosts default to UDP/443.
|
||||||
The gateway rejects Linux interfaces that sysfs identifies as Wi-Fi, and rejects
|
The gateway rejects Linux interfaces that sysfs identifies as Wi-Fi, and rejects
|
||||||
wired interfaces whose sysfs carrier state reports no link; managed wireless
|
wired interfaces whose sysfs carrier state reports no link; managed wireless
|
||||||
@@ -275,11 +277,12 @@ broadcast-flow confirmation. One-way broadcast diagnostics distinguish frames
|
|||||||
sent toward the LAN from broadcast frames received back from the LAN. Malformed frames
|
sent toward the LAN from broadcast frames received back from the LAN. Malformed frames
|
||||||
read from TAP, invalid or unauthorized source-MAC frames, L2 control-plane
|
read from TAP, invalid or unauthorized source-MAC frames, L2 control-plane
|
||||||
traffic, remote VLAN tags, DHCP server replies, IPv6 Router Advertisements, IPv6
|
traffic, remote VLAN tags, DHCP server replies, IPv6 Router Advertisements, IPv6
|
||||||
fragments, jumbo frames, and TAP frames whose encoded datagrams exceed the
|
fragments, jumbo frames, frames above the negotiated TAP MTU, and TAP frames
|
||||||
negotiated QUIC budget are counted and dropped before relay send without
|
whose encoded datagrams exceed the negotiated QUIC budget are counted and
|
||||||
stopping the bridge. Relayed LAN frames are also safety-checked before TAP
|
dropped before relay send without stopping the bridge. Relayed LAN frames are
|
||||||
writes, so switch-control traffic, invalid-source frames, and jumbo frames stay
|
also safety-checked before TAP writes, so switch-control traffic,
|
||||||
out of the Windows adapter even if they reached the client over QUIC.
|
invalid-source frames, jumbo frames, and over-TAP-MTU frames stay out of the
|
||||||
|
Windows adapter even if they reached the client over QUIC.
|
||||||
Misdirected unicast frames not addressed to the client's virtual MAC are also
|
Misdirected unicast frames not addressed to the client's virtual MAC are also
|
||||||
counted and skipped; accepted TAP-to-relay and relay-to-TAP frames are logged
|
counted and skipped; accepted TAP-to-relay and relay-to-TAP frames are logged
|
||||||
with direction, peer id, MACs, ethertype/length, frame length, action, and drop
|
with direction, peer id, MACs, ethertype/length, frame length, action, and drop
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ Drops that can be normal during testing:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
drop_reason=UnknownDestination
|
drop_reason=UnknownDestination
|
||||||
|
drop_reason=TapMtuExceeded
|
||||||
drop_reason=DatagramBudget
|
drop_reason=DatagramBudget
|
||||||
drop_reason=RateLimit
|
drop_reason=RateLimit
|
||||||
```
|
```
|
||||||
@@ -263,6 +264,9 @@ drop_reason=RateLimit
|
|||||||
On gateway `LanToRemote` logs, `UnknownDestination` usually means the gateway
|
On gateway `LanToRemote` logs, `UnknownDestination` usually means the gateway
|
||||||
captured unrelated LAN unicast and dropped it locally instead of sending it to
|
captured unrelated LAN unicast and dropped it locally instead of sending it to
|
||||||
the relay.
|
the relay.
|
||||||
|
`TapMtuExceeded` means a host emitted an Ethernet frame larger than the
|
||||||
|
negotiated tunnel MTU; occasional drops can happen while testing software that
|
||||||
|
does not honor the smaller adapter MTU yet.
|
||||||
|
|
||||||
Drops that should be investigated if they dominate:
|
Drops that should be investigated if they dominate:
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ use lanparty_ctrl::{
|
|||||||
use lanparty_obs::{DropReason, QuicDiagnostics, TunnelStats};
|
use lanparty_obs::{DropReason, QuicDiagnostics, TunnelStats};
|
||||||
use lanparty_proto::{
|
use lanparty_proto::{
|
||||||
EthernetFrame, FrameType, MacAddr, OVERLAY_FLAGS_NONE, decode_datagram, encode_datagram,
|
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 quinn::{ClientConfig, Endpoint, crypto::rustls::QuicClientConfig};
|
||||||
use rustls::pki_types::CertificateDer;
|
use rustls::pki_types::CertificateDer;
|
||||||
@@ -396,6 +397,13 @@ impl ClientRelayIo {
|
|||||||
self.stats.record_dropped_frame();
|
self.stats.record_dropped_frame();
|
||||||
return Ok(ClientSendOutcome::Dropped(DropReason::from(drop_reason)));
|
return Ok(ClientSendOutcome::Dropped(DropReason::from(drop_reason)));
|
||||||
}
|
}
|
||||||
|
if ethernet_frame_exceeds_tap_mtu(
|
||||||
|
ethernet_frame,
|
||||||
|
usize::from(self.welcome.effective_tap_mtu()),
|
||||||
|
) {
|
||||||
|
self.stats.record_dropped_frame();
|
||||||
|
return Ok(ClientSendOutcome::Dropped(DropReason::TapMtuExceeded));
|
||||||
|
}
|
||||||
|
|
||||||
let datagram = encode_datagram(
|
let datagram = encode_datagram(
|
||||||
FrameType::Ethernet,
|
FrameType::Ethernet,
|
||||||
@@ -449,6 +457,13 @@ impl ClientRelayIo {
|
|||||||
self.stats.record_dropped_frame();
|
self.stats.record_dropped_frame();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ethernet_frame_exceeds_tap_mtu(
|
||||||
|
ethernet_frame,
|
||||||
|
usize::from(self.welcome.effective_tap_mtu()),
|
||||||
|
) {
|
||||||
|
self.stats.record_dropped_frame();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if !is_accepted_relay_destination(ethernet_frame, self.virtual_mac) {
|
if !is_accepted_relay_destination(ethernet_frame, self.virtual_mac) {
|
||||||
self.stats.record_dropped_frame();
|
self.stats.record_dropped_frame();
|
||||||
continue;
|
continue;
|
||||||
@@ -868,7 +883,7 @@ mod tests {
|
|||||||
let ControlMessage::Stats(stats) = stats_message else {
|
let ControlMessage::Stats(stats) = stats_message else {
|
||||||
panic!("expected client stats event");
|
panic!("expected client stats event");
|
||||||
};
|
};
|
||||||
assert_eq!(stats, TunnelStats::new(1, 3, 1, 3, 9, 1));
|
assert_eq!(stats, TunnelStats::new(1, 3, 1, 3, 10, 1));
|
||||||
stats_received_tx.send(()).unwrap();
|
stats_received_tx.send(()).unwrap();
|
||||||
|
|
||||||
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
||||||
@@ -976,6 +991,13 @@ mod tests {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
ClientSendOutcome::Dropped(DropReason::UnauthorizedSourceMac)
|
ClientSendOutcome::Dropped(DropReason::UnauthorizedSourceMac)
|
||||||
);
|
);
|
||||||
|
let tap_oversized_payload = vec![0; usize::from(client.welcome().effective_tap_mtu()) + 1];
|
||||||
|
assert_eq!(
|
||||||
|
relay_io
|
||||||
|
.send_ethernet_with_outcome(ðernet_frame(&tap_oversized_payload))
|
||||||
|
.unwrap(),
|
||||||
|
ClientSendOutcome::Dropped(DropReason::TapMtuExceeded)
|
||||||
|
);
|
||||||
let jumbo_payload = vec![0; lanparty_proto::MAX_STANDARD_ETHERNET_PAYLOAD_LEN + 1];
|
let jumbo_payload = vec![0; lanparty_proto::MAX_STANDARD_ETHERNET_PAYLOAD_LEN + 1];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relay_io
|
relay_io
|
||||||
@@ -1002,7 +1024,7 @@ mod tests {
|
|||||||
assert_eq!(stats.broadcast_frames_rx(), 0);
|
assert_eq!(stats.broadcast_frames_rx(), 0);
|
||||||
assert_eq!(stats.datagrams_tx(), 1);
|
assert_eq!(stats.datagrams_tx(), 1);
|
||||||
assert_eq!(stats.datagrams_rx(), 3);
|
assert_eq!(stats.datagrams_rx(), 3);
|
||||||
assert_eq!(stats.dropped_frames(), 9);
|
assert_eq!(stats.dropped_frames(), 10);
|
||||||
assert_eq!(stats.malformed_frames(), 1);
|
assert_eq!(stats.malformed_frames(), 1);
|
||||||
assert_eq!(client.stats_snapshot(), stats);
|
assert_eq!(client.stats_snapshot(), stats);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ use lanparty_obs::{DropReason, TunnelStats};
|
|||||||
use lanparty_obs::{FrameAction, FrameDirection, FrameLog};
|
use lanparty_obs::{FrameAction, FrameDirection, FrameLog};
|
||||||
use lanparty_proto::{
|
use lanparty_proto::{
|
||||||
EthernetFrame, FrameType, MacAddr, OVERLAY_FLAGS_NONE, decode_datagram, encode_datagram,
|
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 quinn::{ClientConfig, Endpoint, crypto::rustls::QuicClientConfig};
|
||||||
use rustls::pki_types::CertificateDer;
|
use rustls::pki_types::CertificateDer;
|
||||||
@@ -515,6 +516,10 @@ fn send_gateway_ethernet(
|
|||||||
stats.record_dropped_frame();
|
stats.record_dropped_frame();
|
||||||
return Ok(GatewaySendOutcome::Dropped(DropReason::from(drop_reason)));
|
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(
|
let datagram = encode_datagram(
|
||||||
FrameType::Ethernet,
|
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 {
|
return Ok(GatewayReceiveOutcome::Accepted(ReceivedEthernetFrame {
|
||||||
source_peer_id: header.peer_id(),
|
source_peer_id: header.peer_id(),
|
||||||
@@ -1207,7 +1223,7 @@ mod tests {
|
|||||||
let ControlMessage::Stats(stats) = stats_message else {
|
let ControlMessage::Stats(stats) = stats_message else {
|
||||||
panic!("expected gateway stats event");
|
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();
|
stats_received_tx.send(()).unwrap();
|
||||||
|
|
||||||
let mut disconnect_recv = connection.accept_uni().await.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"))
|
.send_ethernet(ðernet_frame_from(MacAddr::BROADCAST, b"invalid source"))
|
||||||
.is_err()
|
.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();
|
gateway.send_ethernet(ðernet_frame(b"to relay")).unwrap();
|
||||||
let received = tokio::time::timeout(Duration::from_secs(5), gateway.recv_ethernet())
|
let received = tokio::time::timeout(Duration::from_secs(5), gateway.recv_ethernet())
|
||||||
.await
|
.await
|
||||||
@@ -1283,7 +1307,7 @@ mod tests {
|
|||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
let stats = gateway.stats_snapshot();
|
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();
|
gateway.send_stats_snapshot().await.unwrap();
|
||||||
tokio::time::timeout(Duration::from_secs(5), stats_received_rx)
|
tokio::time::timeout(Duration::from_secs(5), stats_received_rx)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub enum DropReason {
|
|||||||
Ipv6RouterAdvertisement,
|
Ipv6RouterAdvertisement,
|
||||||
Ipv6Fragment,
|
Ipv6Fragment,
|
||||||
VlanTaggedFrame,
|
VlanTaggedFrame,
|
||||||
|
TapMtuExceeded,
|
||||||
DatagramBudget,
|
DatagramBudget,
|
||||||
UnknownDestination,
|
UnknownDestination,
|
||||||
RateLimit,
|
RateLimit,
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ pub use ethernet::{
|
|||||||
pub use mac::{MacAddr, MacParseError};
|
pub use mac::{MacAddr, MacParseError};
|
||||||
pub use mtu::{
|
pub use mtu::{
|
||||||
DEFAULT_DATAGRAM_SAFETY_MARGIN, DEFAULT_TAP_MTU, MIN_USEFUL_TAP_MTU, MtuError,
|
DEFAULT_DATAGRAM_SAFETY_MARGIN, DEFAULT_TAP_MTU, MIN_USEFUL_TAP_MTU, MtuError,
|
||||||
max_tap_mtu_for_datagram, recommended_tap_mtu,
|
ethernet_frame_exceeds_tap_mtu, max_ethernet_frame_len_for_tap_mtu, max_tap_mtu_for_datagram,
|
||||||
|
recommended_tap_mtu,
|
||||||
};
|
};
|
||||||
pub use overlay::{
|
pub use overlay::{
|
||||||
FrameType, OVERLAY_FLAGS_NONE, OVERLAY_HEADER_LEN, OVERLAY_MAGIC, OVERLAY_VERSION,
|
FrameType, OVERLAY_FLAGS_NONE, OVERLAY_HEADER_LEN, OVERLAY_MAGIC, OVERLAY_VERSION,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ETHERNET_HEADER_LEN, OVERLAY_HEADER_LEN};
|
use crate::{ETHERNET_HEADER_LEN, EthernetFrame, OVERLAY_HEADER_LEN};
|
||||||
|
|
||||||
pub const DEFAULT_TAP_MTU: usize = 1200;
|
pub const DEFAULT_TAP_MTU: usize = 1200;
|
||||||
pub const MIN_USEFUL_TAP_MTU: usize = 576;
|
pub const MIN_USEFUL_TAP_MTU: usize = 576;
|
||||||
@@ -40,6 +40,16 @@ pub fn recommended_tap_mtu(quic_max_datagram_size: usize) -> Result<usize, MtuEr
|
|||||||
Ok(max.min(DEFAULT_TAP_MTU))
|
Ok(max.min(DEFAULT_TAP_MTU))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn max_ethernet_frame_len_for_tap_mtu(tap_mtu: usize) -> usize {
|
||||||
|
ETHERNET_HEADER_LEN + tap_mtu
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn ethernet_frame_exceeds_tap_mtu(frame: EthernetFrame<'_>, tap_mtu: usize) -> bool {
|
||||||
|
frame.len() > max_ethernet_frame_len_for_tap_mtu(tap_mtu)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -57,6 +67,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn checks_ethernet_frame_len_against_tap_mtu() {
|
||||||
|
let frame = vec![0; ETHERNET_HEADER_LEN + 1200];
|
||||||
|
let frame = EthernetFrame::parse(&frame).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(max_ethernet_frame_len_for_tap_mtu(1200), 1214);
|
||||||
|
assert!(!ethernet_frame_exceeds_tap_mtu(frame, 1200));
|
||||||
|
|
||||||
|
let oversized = vec![0; ETHERNET_HEADER_LEN + 1201];
|
||||||
|
let oversized = EthernetFrame::parse(&oversized).unwrap();
|
||||||
|
|
||||||
|
assert!(ethernet_frame_exceeds_tap_mtu(oversized, 1200));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_too_small_datagram_budget() {
|
fn rejects_too_small_datagram_budget() {
|
||||||
let error = recommended_tap_mtu(128).unwrap_err();
|
let error = recommended_tap_mtu(128).unwrap_err();
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use lanparty_ctrl::{
|
|||||||
};
|
};
|
||||||
use lanparty_obs::{DropReason, FrameAction};
|
use lanparty_obs::{DropReason, FrameAction};
|
||||||
use lanparty_proto::{
|
use lanparty_proto::{
|
||||||
EthernetFrame, MacAddr, gateway_lan_safety_drop_reason, recommended_tap_mtu,
|
EthernetFrame, MacAddr, ethernet_frame_exceeds_tap_mtu, gateway_lan_safety_drop_reason,
|
||||||
remote_client_safety_drop_reason,
|
recommended_tap_mtu, remote_client_safety_drop_reason,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -31,8 +31,6 @@ const CLIENT_UNKNOWN_UNICAST_REFILL_FRAMES_PER_SECOND: u32 = 32;
|
|||||||
const CLIENT_TOTAL_BANDWIDTH_BURST_BYTES: u64 = 4 * MEBIBYTE;
|
const CLIENT_TOTAL_BANDWIDTH_BURST_BYTES: u64 = 4 * MEBIBYTE;
|
||||||
const CLIENT_TOTAL_BANDWIDTH_REFILL_BYTES_PER_SECOND: u64 = 2 * MEBIBYTE;
|
const CLIENT_TOTAL_BANDWIDTH_REFILL_BYTES_PER_SECOND: u64 = 2 * MEBIBYTE;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use lanparty_proto::ETHERNET_HEADER_LEN;
|
|
||||||
#[cfg(test)]
|
|
||||||
const ETHERTYPE_IPV4: u16 = 0x0800;
|
const ETHERTYPE_IPV4: u16 = 0x0800;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
const ETHERTYPE_LLDP: u16 = 0x88cc;
|
const ETHERTYPE_LLDP: u16 = 0x88cc;
|
||||||
@@ -561,6 +559,11 @@ impl Room {
|
|||||||
if let Some(drop_reason) = safety_drop_reason {
|
if let Some(drop_reason) = safety_drop_reason {
|
||||||
return Ok(ForwardingDecision::filtered(DropReason::from(drop_reason)));
|
return Ok(ForwardingDecision::filtered(DropReason::from(drop_reason)));
|
||||||
}
|
}
|
||||||
|
if let Some(effective_tap_mtu) = self.effective_tap_mtu
|
||||||
|
&& ethernet_frame_exceeds_tap_mtu(frame, usize::from(effective_tap_mtu))
|
||||||
|
{
|
||||||
|
return Ok(ForwardingDecision::dropped(DropReason::TapMtuExceeded));
|
||||||
|
}
|
||||||
|
|
||||||
if ingress_role == Role::Client
|
if ingress_role == Role::Client
|
||||||
&& !self.allow_client_total_bandwidth(ingress_peer_id, frame.len() as u64, now)
|
&& !self.allow_client_total_bandwidth(ingress_peer_id, frame.len() as u64, now)
|
||||||
@@ -1263,7 +1266,7 @@ mod tests {
|
|||||||
let mut registry = RoomRegistry::default();
|
let mut registry = RoomRegistry::default();
|
||||||
let client_one = registry.join(client_hello(1)).unwrap();
|
let client_one = registry.join(client_hello(1)).unwrap();
|
||||||
let client_two = registry.join(client_hello(2)).unwrap();
|
let client_two = registry.join(client_hello(2)).unwrap();
|
||||||
let payload = vec![0; MAX_STANDARD_ETHERNET_FRAME_LEN - ETHERNET_HEADER_LEN];
|
let payload = vec![0; usize::from(client_one.welcome().effective_tap_mtu())];
|
||||||
let frame = ethernet_with_payload(mac(2), mac(1), ETHERTYPE_IPV4, &payload);
|
let frame = ethernet_with_payload(mac(2), mac(1), ETHERTYPE_IPV4, &payload);
|
||||||
let frame_len = frame.len() as u64;
|
let frame_len = frame.len() as u64;
|
||||||
let burst_frames = CLIENT_TOTAL_BANDWIDTH_BURST_BYTES / frame_len;
|
let burst_frames = CLIENT_TOTAL_BANDWIDTH_BURST_BYTES / frame_len;
|
||||||
@@ -1353,6 +1356,32 @@ mod tests {
|
|||||||
assert_filtered(&decision, DropReason::JumboFrame);
|
assert_filtered(&decision, DropReason::JumboFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drops_frames_above_effective_tap_mtu() {
|
||||||
|
let mut registry = RoomRegistry::default();
|
||||||
|
let gateway = registry.join(gateway_hello()).unwrap();
|
||||||
|
let client = registry.join(client_hello(1)).unwrap();
|
||||||
|
let oversized_payload = vec![0; usize::from(client.welcome().effective_tap_mtu()) + 1];
|
||||||
|
let client_frame = ethernet_with_payload(
|
||||||
|
MacAddr::BROADCAST,
|
||||||
|
mac(1),
|
||||||
|
ETHERTYPE_IPV4,
|
||||||
|
&oversized_payload,
|
||||||
|
);
|
||||||
|
let gateway_frame =
|
||||||
|
ethernet_with_payload(mac(1), physical_mac(), ETHERTYPE_IPV4, &oversized_payload);
|
||||||
|
|
||||||
|
let client_decision = registry
|
||||||
|
.forward_ethernet(&room(), client.peer().peer_id(), &client_frame)
|
||||||
|
.unwrap();
|
||||||
|
let gateway_decision = registry
|
||||||
|
.forward_ethernet(&room(), gateway.peer().peer_id(), &gateway_frame)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_dropped(&client_decision, DropReason::TapMtuExceeded);
|
||||||
|
assert_dropped(&gateway_decision, DropReason::TapMtuExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn filters_l2_control_plane_frames_from_clients_and_gateway() {
|
fn filters_l2_control_plane_frames_from_clients_and_gateway() {
|
||||||
let mut registry = RoomRegistry::default();
|
let mut registry = RoomRegistry::default();
|
||||||
|
|||||||
Reference in New Issue
Block a user