//! Raw 802.11 frame capture via Linux `AF_PACKET` sockets without requiring `libpcap`. //! //! The interface must already be in monitor mode (see [`crate::netlink`]). //! Uses the same kernel packet socket interface that `libpcap` uses on Linux //! without adding an external system library dependency. //! #![cfg(target_os = "linux")] use crate::error::{Result, SabiaError}; use std::ffi::CString; use std::io; use std::mem; use std::os::fd::{AsRawFd, RawFd}; use std::time::Duration; const ETH_P_ALL: u16 = 0x0003; pub struct RawCapture { fd: RawFd, } impl RawCapture { /// Opens a raw `AF_PACKET` socket bound to `iface` (must already be in monitor mode). /// Requires `CAP_NET_RAW` (typically root). pub fn open(iface: &str) -> Result { let fd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_RAW, ETH_P_ALL as i32) }; if fd < 0 { let err = io::Error::last_os_error(); return Err(if err.raw_os_error() == Some(libc::EPERM) { SabiaError::PermissionDenied } else { SabiaError::Io(err) }); } let ifindex = if_index(iface)?; let mut sll: libc::sockaddr_ll = unsafe { mem::zeroed() }; sll.sll_family = libc::AF_PACKET as u16; sll.sll_protocol = ETH_P_ALL.to_be(); sll.sll_ifindex = ifindex; let ret = unsafe { libc::bind( fd, &sll as *const libc::sockaddr_ll as *const libc::sockaddr, mem::size_of::() as u32, ) }; if ret < 0 { let err = io::Error::last_os_error(); unsafe { libc::close(fd) }; return Err(SabiaError::Io(err)); } Ok(Self { fd }) } /// Sets `SO_RCVTIMEO` so the capture loop can periodically check its stop flag /// without blocking indefinitely in `recv`. pub fn set_read_timeout(&self, timeout: Duration) -> Result<()> { let tv = libc::timeval { tv_sec: timeout.as_secs() as libc::time_t, tv_usec: timeout.subsec_micros() as libc::suseconds_t, }; let ret = unsafe { libc::setsockopt( self.fd, libc::SOL_SOCKET, libc::SO_RCVTIMEO, &tv as *const _ as *const libc::c_void, mem::size_of::() as u32, ) }; if ret < 0 { return Err(SabiaError::Io(io::Error::last_os_error())); } Ok(()) } /// Reads a single frame. Returns `Ok(None)` on receive timeout (see `set_read_timeout`). pub fn recv(&self, buf: &mut [u8]) -> Result> { let n = unsafe { libc::recv(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) }; if n < 0 { let err = io::Error::last_os_error(); if matches!(err.raw_os_error(), Some(libc::EAGAIN)) { return Ok(None); } return Err(SabiaError::Io(err)); } Ok(Some(n as usize)) } } impl AsRawFd for RawCapture { fn as_raw_fd(&self) -> RawFd { self.fd } } impl Drop for RawCapture { fn drop(&mut self) { unsafe { libc::close(self.fd) }; } } fn if_index(name: &str) -> Result { let cname = CString::new(name).map_err(|_| SabiaError::InterfaceNotFound(name.to_string()))?; let idx = unsafe { libc::if_nametoindex(cname.as_ptr()) }; if idx == 0 { return Err(SabiaError::InterfaceNotFound(name.to_string())); } Ok(idx as i32) } /// Best-effort check that the process has privileges to open raw packet sockets. /// pub fn check_capabilities() -> bool { let euid = unsafe { libc::geteuid() }; euid == 0 }