fix(relay): keep unknown unicast on gateway path

The relay models the physical LAN as the gateway port, not as another remote
client. Client-originated unknown unicast now forwards only to the gateway, and
gateway-originated unknown unicast is dropped unless it resolves to a registered
remote client. Broadcast and multicast fanout is unchanged.

This prevents promiscuous gateway capture of unrelated LAN unicast from being
flooded to every remote client. It also keeps client-to-LAN traffic from
needlessly leaking to other clients in the room.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-relay
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
This commit is contained in:
2026-05-21 20:03:13 +02:00
parent 3e2648abc1
commit 587b0516cd
2 changed files with 33 additions and 7 deletions
+31 -7
View File
@@ -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<u32> {
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