diff --git a/README.md b/README.md index acd9dc8..11eba4e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/crates/lanparty-client-core/src/lib.rs b/crates/lanparty-client-core/src/lib.rs index 1f31292..e1ceac6 100644 --- a/crates/lanparty-client-core/src/lib.rs +++ b/crates/lanparty-client-core/src/lib.rs @@ -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);