From 881dee5491c921a802761de4e1ebc52e00072e07 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Thu, 21 May 2026 22:17:32 +0200 Subject: [PATCH] feat(relay): filter invalid source MACs PLAN.md requires rejecting broadcast, multicast, and otherwise invalid source MACs. Client ingress already had a forged-source check against the registered client identity, but gateway ingress could still forward Ethernet frames whose source MAC was not valid unicast. Add an explicit `InvalidSourceMac` drop reason and filter invalid Ethernet source MACs before client source authorization and last-seen refresh. This keeps invalid source addresses out of both remote-client and gateway forwarding paths while preserving `UnauthorizedSourceMac` for valid unicast sources that simply belong to another client identity. Document the invalid-source filter in the relay README decomposition. Test Plan: - cargo fmt --check - cargo test -p lanparty-obs -p lanparty-relay - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md --- README.md | 3 ++- crates/lanparty-obs/src/lib.rs | 1 + crates/lanparty-relay/src/lib.rs | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d33777..31d5054 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,8 @@ Public relay binary and relay-owned room state: - stable effective room MTU chosen before Ethernet datagrams flow - live Ethernet datagram forwarding with no ingress reflection - reliable `PeerJoined`/`PeerLeft` notifications to existing room peers -- L2 safety filters for jumbo, switch-control, DHCP-server, and IPv6-RA frames +- L2 safety filters for invalid-source, jumbo, switch-control, DHCP-server, + and IPv6-RA frames - client broadcast/multicast, unknown-unicast, and total bandwidth limiting - malformed peer datagram disconnect threshold - peer stats control events retained for relay diagnostics diff --git a/crates/lanparty-obs/src/lib.rs b/crates/lanparty-obs/src/lib.rs index 237dd4d..1d3d653 100644 --- a/crates/lanparty-obs/src/lib.rs +++ b/crates/lanparty-obs/src/lib.rs @@ -30,6 +30,7 @@ pub enum FrameAction { pub enum DropReason { Malformed, JumboFrame, + InvalidSourceMac, UnauthorizedSourceMac, DuplicateMac, ControlPlaneEtherType, diff --git a/crates/lanparty-relay/src/lib.rs b/crates/lanparty-relay/src/lib.rs index 4d273c5..3fcf86a 100644 --- a/crates/lanparty-relay/src/lib.rs +++ b/crates/lanparty-relay/src/lib.rs @@ -516,6 +516,10 @@ impl Room { Err(_) => return Ok(ForwardingDecision::dropped(DropReason::Malformed)), }; + if !frame.source().is_valid_unicast() { + return Ok(ForwardingDecision::filtered(DropReason::InvalidSourceMac)); + } + if ingress_role == Role::Client { let expected_source = ingress_mac.expect("client peers have MAC addresses"); if frame.source() != expected_source { @@ -1302,6 +1306,25 @@ mod tests { assert_filtered(&decision, DropReason::UnauthorizedSourceMac); } + #[test] + fn filters_invalid_source_macs_from_clients_and_gateway() { + let mut registry = RoomRegistry::default(); + let gateway = registry.join(gateway_hello()).unwrap(); + let client = registry.join(client_hello(1)).unwrap(); + let client_frame = ethernet(MacAddr::BROADCAST, MacAddr::BROADCAST); + let gateway_frame = ethernet(mac(1), MacAddr::ZERO); + + let client_decision = registry + .forward_ethernet(&room(), client.peer().peer_id(), &client_frame) + .unwrap(); + let gateway_decision = registry + .forward_ethernet(&room(), gateway.peer().peer_id(), &gateway_frame) + .unwrap(); + + assert_filtered(&client_decision, DropReason::InvalidSourceMac); + assert_filtered(&gateway_decision, DropReason::InvalidSourceMac); + } + #[test] fn filters_jumbo_frames() { let mut registry = RoomRegistry::default();