diff --git a/README.md b/README.md index 7afc47d..85f30bc 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ self-signed development certificate; `--dev-cert-der-out` writes that certificate so the gateway and client can pin it in development. Production certificate handling remains future work. Ethernet forwarding decisions are logged with room, peer, MAC, ethertype, action, drop reason, and target count. +Unknown unicast from a client is forwarded only to the gateway port; unknown +unicast from the gateway is dropped instead of flooded to every remote client. ## Gateway diff --git a/crates/lanparty-relay/src/lib.rs b/crates/lanparty-relay/src/lib.rs index 069a296..e3fafff 100644 --- a/crates/lanparty-relay/src/lib.rs +++ b/crates/lanparty-relay/src/lib.rs @@ -507,7 +507,10 @@ impl Room { { return Ok(ForwardingDecision::rate_limited()); } - self.all_peer_ids_except(ingress_peer_id) + match ingress_role { + Role::Client => self.gateway_peer_id_except(ingress_peer_id), + Role::Gateway => Vec::new(), + } }; if targets.is_empty() { @@ -564,6 +567,13 @@ impl Room { peer_ids.sort_unstable(); peer_ids } + + fn gateway_peer_id_except(&self, ingress_peer_id: u32) -> Vec { + self.gateway + .as_ref() + .filter(|gateway| gateway.peer_id() != ingress_peer_id) + .map_or_else(Vec::new, |gateway| vec![gateway.peer_id()]) + } } #[derive(Debug, Clone)] @@ -933,18 +943,35 @@ mod tests { fn forwards_unknown_client_unicast_to_gateway() { let mut registry = RoomRegistry::default(); let gateway = registry.join(gateway_hello()).unwrap(); - let client = 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 frame = ethernet(MacAddr::new([0x00, 1, 2, 3, 4, 5]), mac(1)); let decision = registry - .forward_ethernet(&room(), client.peer().peer_id(), &frame) + .forward_ethernet(&room(), client_one.peer().peer_id(), &frame) .unwrap(); assert_eq!(decision.action(), FrameAction::Forwarded); assert_eq!(decision.targets(), &[gateway.peer().peer_id()]); + assert!(!decision.targets().contains(&client_two.peer().peer_id())); assert_eq!(decision.drop_reason(), None); } + #[test] + fn drops_gateway_unicast_to_unknown_remote_mac() { + let mut registry = RoomRegistry::default(); + let gateway = registry.join(gateway_hello()).unwrap(); + registry.join(client_hello(1)).unwrap(); + registry.join(client_hello(2)).unwrap(); + let frame = ethernet(physical_mac(), MacAddr::new([0x00, 1, 2, 3, 4, 5])); + + let decision = registry + .forward_ethernet(&room(), gateway.peer().peer_id(), &frame) + .unwrap(); + + assert_dropped(&decision, DropReason::UnknownDestination); + } + #[test] fn forwards_gateway_unicast_to_matching_client() { let mut registry = RoomRegistry::default(); @@ -1032,10 +1059,7 @@ mod tests { .forward_ethernet_at(&room(), client_one.peer().peer_id(), &unknown_unicast, now) .unwrap(); assert_eq!(decision.action(), FrameAction::Forwarded); - assert_eq!( - decision.targets(), - &[gateway.peer().peer_id(), client_two.peer().peer_id()] - ); + assert_eq!(decision.targets(), &[gateway.peer().peer_id()]); } let decision = registry