fix(relay): filter remote VLAN-tagged frames
The MVP bridge treats each remote player as a normal host on the LAN, not as a trunk port. Allowing client-origin VLAN-tagged frames would let a remote client send traffic outside the simple untagged Ethernet model, and could also hide IPv4/IPv6 control traffic behind an outer VLAN EtherType that the existing safety filters do not parse. Filter 802.1Q, 802.1ad, and common QinQ-tagged frames from remote clients before they can reach the physical LAN. LAN-origin tagged frames are still allowed back toward clients so the gateway remains a transparent receiver for whatever the local wired network emits. Add a dedicated drop reason so relay logs make the policy clear. Test Plan: - cargo fmt --check - cargo test -p lanparty-relay -p lanparty-obs - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: MVP relay L2 safety filters
This commit is contained in:
@@ -32,6 +32,9 @@ const ETHERTYPE_EAPOL: u16 = 0x888e;
|
||||
const ETHERTYPE_SLOW_PROTOCOLS: u16 = 0x8809;
|
||||
const ETHERTYPE_LLDP: u16 = 0x88cc;
|
||||
const ETHERTYPE_IPV6: u16 = 0x86dd;
|
||||
const ETHERTYPE_8021Q: u16 = 0x8100;
|
||||
const ETHERTYPE_8021AD: u16 = 0x88a8;
|
||||
const ETHERTYPE_QINQ: u16 = 0x9100;
|
||||
const IP_PROTOCOL_UDP: u8 = 17;
|
||||
const IPV6_NEXT_HEADER_HOP_BY_HOP: u8 = 0;
|
||||
const IPV6_NEXT_HEADER_ROUTING: u8 = 43;
|
||||
@@ -732,6 +735,10 @@ fn safety_drop_reason(ingress_role: Role, frame: EthernetFrame<'_>) -> Option<Dr
|
||||
return Some(DropReason::ControlPlaneEtherType);
|
||||
}
|
||||
|
||||
if ingress_role == Role::Client && is_vlan_tagged_frame(frame) {
|
||||
return Some(DropReason::VlanTaggedFrame);
|
||||
}
|
||||
|
||||
if ingress_role == Role::Client && is_dhcp_server_reply(frame) {
|
||||
return Some(DropReason::DhcpServerReply);
|
||||
}
|
||||
@@ -754,6 +761,13 @@ fn is_control_plane_frame(frame: EthernetFrame<'_>) -> bool {
|
||||
) || is_link_local_control_destination(frame.destination())
|
||||
}
|
||||
|
||||
fn is_vlan_tagged_frame(frame: EthernetFrame<'_>) -> bool {
|
||||
matches!(
|
||||
frame.ethertype_or_len(),
|
||||
ETHERTYPE_8021Q | ETHERTYPE_8021AD | ETHERTYPE_QINQ
|
||||
)
|
||||
}
|
||||
|
||||
fn is_link_local_control_destination(mac: MacAddr) -> bool {
|
||||
let [a, b, c, d, e, f] = mac.octets();
|
||||
|
||||
@@ -1515,6 +1529,33 @@ mod tests {
|
||||
assert_filtered(&gateway_decision, DropReason::ControlPlaneEtherType);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_remote_vlan_tagged_frames_but_allows_lan_tags() {
|
||||
let mut registry = RoomRegistry::default();
|
||||
let gateway = registry.join(gateway_hello()).unwrap();
|
||||
let client = registry.join(client_hello(1)).unwrap();
|
||||
let payload = [0, 42, 0x08, 0x00, 1, 2, 3, 4];
|
||||
let client_frame =
|
||||
ethernet_with_payload(MacAddr::BROADCAST, mac(1), ETHERTYPE_8021Q, &payload);
|
||||
let gateway_frame = ethernet_with_payload(
|
||||
MacAddr::BROADCAST,
|
||||
physical_mac(),
|
||||
ETHERTYPE_8021AD,
|
||||
&payload,
|
||||
);
|
||||
|
||||
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::VlanTaggedFrame);
|
||||
assert_eq!(gateway_decision.action(), FrameAction::Forwarded);
|
||||
assert_eq!(gateway_decision.targets(), &[client.peer().peer_id()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_remote_dhcp_server_replies_but_allows_lan_replies() {
|
||||
let mut registry = RoomRegistry::default();
|
||||
|
||||
Reference in New Issue
Block a user