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
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
123 lines
3.7 KiB
Rust
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
|
|
}
|