fix(gateway): request promiscuous AF_PACKET membership
The gateway now asks Linux for PACKET_MR_PROMISC membership on the bound AF_PACKET socket. The tunnel depends on receiving LAN frames addressed to remote client MACs, not only frames addressed to the gateway NIC's own MAC. The switch may learn those remote MACs on the gateway port, but the NIC can still filter the unicast frames unless the packet socket requests promiscuous packet membership. This keeps the promiscuous lifetime scoped to the packet socket. If the kernel rejects the membership request, gateway startup fails instead of running in a mode that cannot reliably capture remote-client replies from the LAN. Test Plan: - cargo fmt --check - cargo test -p lanparty-gateway - cargo clippy -p lanparty-gateway --all-targets -- -D warnings - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md
This commit is contained in:
@@ -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<u32> {
|
||||
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::<libc::c_void>(),
|
||||
std::mem::size_of::<libc::packet_mreq>() 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::<libc::packet_mreq>()
|
||||
};
|
||||
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<CString> {
|
||||
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));
|
||||
|
||||
Reference in New Issue
Block a user