fix(gateway): ignore self-injected packet frames

AF_PACKET sockets can report packets sent by the host as well as packets
received from the LAN. The gateway writes remote-client frames onto the wired
interface, so treating those outgoing packets as fresh LAN input can reflect
self-injected traffic back to the relay.

Read packet metadata with `recvfrom` and skip `PACKET_OUTGOING` frames before
returning a LAN frame to the bridge loop. This keeps capture scoped to inbound
LAN traffic and is a prerequisite for periodic CAM refresh frames.

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

Refs: PLAN.md gateway AF_PACKET bridge
This commit is contained in:
2026-05-21 18:27:38 +02:00
parent 34ba2f2375
commit fe10f6ed37
+29 -5
View File
@@ -92,21 +92,33 @@ impl PacketSocket {
} }
pub fn recv_frame(&self, buffer: &mut [u8]) -> io::Result<usize> { pub fn recv_frame(&self, buffer: &mut [u8]) -> io::Result<usize> {
loop {
let mut address = unsafe {
// SAFETY: sockaddr_ll is a plain old data kernel ABI struct; zero is a valid
// base before recvfrom initializes the peer address.
std::mem::zeroed::<libc::sockaddr_ll>()
};
let mut address_len = std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t;
let received = unsafe { let received = unsafe {
// SAFETY: buffer.as_mut_ptr() is valid for buffer.len() bytes for the duration of // SAFETY: buffer.as_mut_ptr() is valid for buffer.len() bytes for the duration
// recv, and recv initializes at most that many bytes. // of recvfrom, and recvfrom initializes at most that many bytes. address points
libc::recv( // to a sockaddr_ll-sized output buffer and address_len carries that size.
libc::recvfrom(
self.fd.as_raw_fd(), self.fd.as_raw_fd(),
buffer.as_mut_ptr().cast::<libc::c_void>(), buffer.as_mut_ptr().cast::<libc::c_void>(),
buffer.len(), buffer.len(),
0, 0,
(&mut address as *mut libc::sockaddr_ll).cast::<libc::sockaddr>(),
&mut address_len,
) )
}; };
if received < 0 { if received < 0 {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
} }
if is_inbound_packet_type(address.sll_pkttype) {
Ok(received as usize) return Ok(received as usize);
}
}
} }
} }
@@ -142,6 +154,10 @@ pub fn interface_index(interface: &str) -> io::Result<u32> {
Ok(index) Ok(index)
} }
fn is_inbound_packet_type(packet_type: u8) -> bool {
packet_type != libc::PACKET_OUTGOING
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -164,4 +180,12 @@ mod tests {
assert_ne!(error.kind(), io::ErrorKind::InvalidInput); assert_ne!(error.kind(), io::ErrorKind::InvalidInput);
} }
#[test]
fn classifies_inbound_packet_types() {
assert!(!is_inbound_packet_type(libc::PACKET_OUTGOING));
assert!(is_inbound_packet_type(libc::PACKET_HOST));
assert!(is_inbound_packet_type(libc::PACKET_BROADCAST));
assert!(is_inbound_packet_type(libc::PACKET_MULTICAST));
}
} }