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
+38 -14
View File
@@ -92,21 +92,33 @@ impl PacketSocket {
}
pub fn recv_frame(&self, buffer: &mut [u8]) -> io::Result<usize> {
let received = unsafe {
// SAFETY: buffer.as_mut_ptr() is valid for buffer.len() bytes for the duration of
// recv, and recv initializes at most that many bytes.
libc::recv(
self.fd.as_raw_fd(),
buffer.as_mut_ptr().cast::<libc::c_void>(),
buffer.len(),
0,
)
};
if received < 0 {
return Err(io::Error::last_os_error());
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 {
// SAFETY: buffer.as_mut_ptr() is valid for buffer.len() bytes for the duration
// of recvfrom, and recvfrom initializes at most that many bytes. address points
// to a sockaddr_ll-sized output buffer and address_len carries that size.
libc::recvfrom(
self.fd.as_raw_fd(),
buffer.as_mut_ptr().cast::<libc::c_void>(),
buffer.len(),
0,
(&mut address as *mut libc::sockaddr_ll).cast::<libc::sockaddr>(),
&mut address_len,
)
};
if received < 0 {
return Err(io::Error::last_os_error());
}
if is_inbound_packet_type(address.sll_pkttype) {
return Ok(received as usize);
}
}
Ok(received as usize)
}
}
@@ -142,6 +154,10 @@ pub fn interface_index(interface: &str) -> io::Result<u32> {
Ok(index)
}
fn is_inbound_packet_type(packet_type: u8) -> bool {
packet_type != libc::PACKET_OUTGOING
}
#[cfg(test)]
mod tests {
use super::*;
@@ -164,4 +180,12 @@ mod tests {
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));
}
}