diff --git a/README.md b/README.md index eaa66e4..e7ac0be 100644 --- a/README.md +++ b/README.md @@ -264,8 +264,9 @@ 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. 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. +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 +counted and skipped; 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 13fcf90..2fabec5 100644 --- a/crates/lanparty-client-core/src/lib.rs +++ b/crates/lanparty-client-core/src/lib.rs @@ -449,6 +449,10 @@ impl ClientRelayIo { self.stats.record_dropped_frame(); continue; } + if !is_accepted_relay_destination(ethernet_frame, self.virtual_mac) { + self.stats.record_dropped_frame(); + continue; + } return Ok(ReceivedEthernetFrame { source_peer_id: header.peer_id(), @@ -468,6 +472,11 @@ impl ClientRelayIo { } } +fn is_accepted_relay_destination(frame: EthernetFrame<'_>, virtual_mac: MacAddr) -> bool { + let destination = frame.destination(); + destination == virtual_mac || destination.is_multicast() +} + #[derive(Debug, Default)] struct ClientTunnelStats { ethernet_frames_tx: AtomicU64, @@ -823,9 +832,26 @@ mod tests { .send_datagram(Bytes::from(filtered_response)) .unwrap(); - let response = - encode_datagram(FrameType::Ethernet, 7, 1, 0, ðernet_frame(b"from relay")) - .unwrap(); + let misdirected_response = encode_datagram( + FrameType::Ethernet, + 7, + 1, + 0, + &misdirected_unicast_ethernet_frame(), + ) + .unwrap(); + connection + .send_datagram(Bytes::from(misdirected_response)) + .unwrap(); + + let response = encode_datagram( + FrameType::Ethernet, + 7, + 1, + 0, + &relay_ethernet_frame(b"from relay"), + ) + .unwrap(); connection.send_datagram(Bytes::from(response)).unwrap(); let event = encode_control_message(&ControlMessage::PeerJoined( @@ -842,7 +868,7 @@ mod tests { let ControlMessage::Stats(stats) = stats_message else { panic!("expected client stats event"); }; - assert_eq!(stats, TunnelStats::new(1, 2, 1, 2, 8, 1)); + assert_eq!(stats, TunnelStats::new(1, 3, 1, 3, 9, 1)); stats_received_tx.send(()).unwrap(); let mut disconnect_recv = connection.accept_uni().await.unwrap(); @@ -906,7 +932,10 @@ mod tests { .unwrap() .unwrap(); assert_eq!(received.source_peer_id(), 1); - assert_eq!(received.payload(), ethernet_frame(b"from relay").as_slice()); + assert_eq!( + received.payload(), + relay_ethernet_frame(b"from relay").as_slice() + ); let event = tokio::time::timeout(Duration::from_secs(5), client.recv_control_event()) .await @@ -968,12 +997,12 @@ mod tests { ); let stats = relay_io.stats_snapshot(); assert_eq!(stats.ethernet_frames_tx(), 1); - assert_eq!(stats.ethernet_frames_rx(), 2); + assert_eq!(stats.ethernet_frames_rx(), 3); 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(), 2); - assert_eq!(stats.dropped_frames(), 8); + assert_eq!(stats.datagrams_rx(), 3); + assert_eq!(stats.dropped_frames(), 9); assert_eq!(stats.malformed_frames(), 1); assert_eq!(client.stats_snapshot(), stats); @@ -1075,6 +1104,24 @@ mod tests { ) } + fn relay_ethernet_frame(payload: &[u8]) -> Vec { + ethernet_frame_with_headers( + MacAddr::new([0x02, 0, 0, 0, 0, 1]), + MacAddr::new([0x02, 0, 0, 0, 0, 2]), + lanparty_proto::ETHERTYPE_IPV4, + payload, + ) + } + + fn misdirected_unicast_ethernet_frame() -> Vec { + ethernet_frame_with_headers( + MacAddr::new([0x02, 0, 0, 0, 0, 9]), + MacAddr::new([0x02, 0, 0, 0, 0, 2]), + lanparty_proto::ETHERTYPE_IPV4, + b"wrong client", + ) + } + fn ethernet_frame_with_headers( destination: MacAddr, source: MacAddr,