diff --git a/README.md b/README.md index c6926bb..7fbc8ca 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,10 @@ cargo run -p lanparty-gateway -- \ The gateway connects to the relay as `role = gateway`, completes the control-stream hello/welcome handshake, opens an AF_PACKET socket on the LAN -interface, and bridges Ethernet frames between the relay and wired LAN until -shutdown. It tracks remote-client source MACs seen from relay traffic and -periodically emits small CAM refresh frames so the physical switch keeps those -MACs associated with the gateway port. +interface with promiscuous packet membership, and bridges Ethernet frames +between the relay and wired LAN until shutdown. It tracks remote-client source +MACs seen from relay traffic and periodically emits small CAM refresh frames so +the physical switch keeps those MACs associated with the gateway port. ## Windows Client diff --git a/crates/lanparty-gateway/src/packet.rs b/crates/lanparty-gateway/src/packet.rs index 8136655..f090341 100644 --- a/crates/lanparty-gateway/src/packet.rs +++ b/crates/lanparty-gateway/src/packet.rs @@ -58,6 +58,7 @@ impl PacketSocket { if result < 0 { return Err(io::Error::last_os_error()); } + enable_promiscuous_membership(fd.as_raw_fd(), interface_index)?; let interface_mac = interface_hardware_addr(fd.as_raw_fd(), interface)?; Ok(Self { @@ -153,6 +154,37 @@ pub fn interface_index(interface: &str) -> io::Result { Ok(index) } +fn enable_promiscuous_membership(fd: RawFd, interface_index: u32) -> io::Result<()> { + let membership = promiscuous_membership(interface_index); + let result = unsafe { + // SAFETY: membership points to a fully initialized packet_mreq and the provided length + // matches that kernel ABI struct. The socket fd is owned by PacketSocket::open. + libc::setsockopt( + fd, + libc::SOL_PACKET, + libc::PACKET_ADD_MEMBERSHIP, + (&membership as *const libc::packet_mreq).cast::(), + std::mem::size_of::() as libc::socklen_t, + ) + }; + if result < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(()) +} + +fn promiscuous_membership(interface_index: u32) -> libc::packet_mreq { + let mut membership = unsafe { + // SAFETY: packet_mreq is a plain kernel ABI struct; zeroing leaves unused fields empty. + std::mem::zeroed::() + }; + membership.mr_ifindex = interface_index as libc::c_int; + membership.mr_type = libc::PACKET_MR_PROMISC as libc::c_ushort; + + membership +} + fn interface_name(interface: &str) -> io::Result { if interface.trim().is_empty() { return Err(io::Error::new( @@ -258,6 +290,16 @@ mod tests { assert_ne!(error.kind(), io::ErrorKind::InvalidInput); } + #[test] + fn builds_promiscuous_packet_membership_request() { + let membership = promiscuous_membership(12); + + assert_eq!(membership.mr_ifindex, 12); + assert_eq!(membership.mr_type, libc::PACKET_MR_PROMISC as u16); + assert_eq!(membership.mr_alen, 0); + assert_eq!(membership.mr_address, [0; 8]); + } + #[test] fn classifies_inbound_packet_types() { assert!(!is_inbound_packet_type(libc::PACKET_OUTGOING));