fix(client): filter relayed LAN frames before TAP writes
LAN-to-remote switch-control filtering is enforced by the gateway and relay, but the Windows client is the final boundary before frames enter the TAP adapter. A malformed or buggy relay path should not be able to make the client write LAN control traffic, invalid-source frames, or jumbo frames into TAP. Reuse the shared gateway/LAN safety classifier on received relay Ethernet frames. Filtered frames are counted and skipped, and recv_ethernet only returns frames that are safe to hand to the platform TAP writer. Extend the client relay-session test so the mock relay sends a filtered frame before the valid one, then document the receive-side TAP boundary in the README. Test Plan: - cargo test -p lanparty-client-core - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - cargo fmt --check - git diff --check Refs: PLAN.md LAN-to-remote control-plane filtering
This commit is contained in:
@@ -260,7 +260,10 @@ read from TAP, invalid or unauthorized source-MAC frames, L2 control-plane
|
||||
traffic, remote VLAN tags, DHCP server replies, IPv6 Router Advertisements, IPv6
|
||||
fragments, jumbo frames, and TAP frames whose encoded datagrams exceed the
|
||||
negotiated QUIC budget are counted and dropped before relay send without
|
||||
stopping the bridge; TAP device read/write errors still stop the bridge.
|
||||
stopping the bridge. Relayed LAN frames are also safety-checked before TAP
|
||||
writes, so switch-control traffic, invalid-source frames, and jumbo frames stay
|
||||
out of the Windows adapter even if they reached the client over QUIC; TAP
|
||||
device read/write errors still stop the bridge.
|
||||
Relay lifecycle events are logged as they arrive, including gateway joins and
|
||||
peer leaves. The client remembers peer identities from join and catch-up events
|
||||
so later leave logs can identify a disconnected LAN gateway or client MAC when
|
||||
|
||||
@@ -28,7 +28,7 @@ use lanparty_ctrl::{
|
||||
use lanparty_obs::{DropReason, QuicDiagnostics, TunnelStats};
|
||||
use lanparty_proto::{
|
||||
EthernetFrame, FrameType, MacAddr, decode_datagram, encode_datagram,
|
||||
remote_client_safety_drop_reason, validate_datagram_budget,
|
||||
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;
|
||||
@@ -445,6 +445,11 @@ impl ClientRelayIo {
|
||||
};
|
||||
|
||||
self.stats.record_ethernet_rx(ethernet_frame);
|
||||
if gateway_lan_safety_drop_reason(ethernet_frame).is_some() {
|
||||
self.stats.record_dropped_frame();
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(ReceivedEthernetFrame {
|
||||
source_peer_id: header.peer_id(),
|
||||
payload: Bytes::copy_from_slice(packet.payload()),
|
||||
@@ -806,6 +811,18 @@ mod tests {
|
||||
assert_eq!(header.peer_id(), 2);
|
||||
assert_eq!(packet.payload(), ethernet_frame(b"to relay").as_slice());
|
||||
|
||||
let filtered_response = encode_datagram(
|
||||
FrameType::Ethernet,
|
||||
7,
|
||||
1,
|
||||
0,
|
||||
&control_plane_ethernet_frame(),
|
||||
)
|
||||
.unwrap();
|
||||
connection
|
||||
.send_datagram(Bytes::from(filtered_response))
|
||||
.unwrap();
|
||||
|
||||
let response =
|
||||
encode_datagram(FrameType::Ethernet, 7, 1, 0, ðernet_frame(b"from relay"))
|
||||
.unwrap();
|
||||
@@ -825,7 +842,7 @@ mod tests {
|
||||
let ControlMessage::Stats(stats) = stats_message else {
|
||||
panic!("expected client stats event");
|
||||
};
|
||||
assert_eq!(stats, TunnelStats::new(1, 1, 1, 1, 7, 1));
|
||||
assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 8, 1));
|
||||
stats_received_tx.send(()).unwrap();
|
||||
|
||||
let mut disconnect_recv = connection.accept_uni().await.unwrap();
|
||||
@@ -951,12 +968,12 @@ mod tests {
|
||||
);
|
||||
let stats = relay_io.stats_snapshot();
|
||||
assert_eq!(stats.ethernet_frames_tx(), 1);
|
||||
assert_eq!(stats.ethernet_frames_rx(), 1);
|
||||
assert_eq!(stats.ethernet_frames_rx(), 2);
|
||||
assert_eq!(stats.broadcast_frames_tx(), 0);
|
||||
assert_eq!(stats.broadcast_frames_rx(), 0);
|
||||
assert_eq!(stats.datagrams_tx(), 1);
|
||||
assert_eq!(stats.datagrams_rx(), 1);
|
||||
assert_eq!(stats.dropped_frames(), 7);
|
||||
assert_eq!(stats.datagrams_rx(), 2);
|
||||
assert_eq!(stats.dropped_frames(), 8);
|
||||
assert_eq!(stats.malformed_frames(), 1);
|
||||
assert_eq!(client.stats_snapshot(), stats);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user