Files
sabia/crates/sabia-core/src/capture.rs
T
Lucas RayanandClaude Sonnet 5 55e19a8e5b
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s
chore(i18n): finish translating code comments and scripts to English
Completes the English-only standardization for code, comments, scripts
and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en,
untouched). Covers the remaining crates/, apps/gui/ui/ and
apps/gui/src-tauri/ comments, .gitea/ workflow and template comments,
.githooks/ hooks, and the scripts/ source comments for the files
already renamed in a prior commit (check.sh, generate-logo.py,
install-hooks.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-30 20:44:39 -03:00

123 lines
3.7 KiB
Rust

//! 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<Self> {
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::<libc::sockaddr_ll>() 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::<libc::timeval>() 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<Option<usize>> {
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<i32> {
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
}