Esqueleto inicial: sabia-core + sabia-cli + UI local, OpenWolf

- sabia-core: descoberta de interfaces, controle de modo monitor/canal
  via iw (ponte até ter nl80211 nativo), captura crua 802.11 via
  AF_PACKET (sem libpcap), parsing de radiotap e quadros de
  gerenciamento (beacon/probe/EAPOL), agregação em tabela de APs/
  clientes, escrita de .pcap, trilha de auditoria encadeada por hash,
  auto-atualização via GitHub releases (curl, sem dependências de
  rede extras)
- sabia-cli: `sabia iface`, `sabia scan`, `sabia update`,
  `sabia audit-verify`, `sabia ui` (servidor local + navegador,
  protegido por token, mesma filosofia do Guará)
- OpenWolf inicializado para memória de projeto entre agentes
This commit is contained in:
Lucas committed 2026-10-23 08:22:04 -03:00
commit e989413018
76 files changed
+8386

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
//! Captura crua de quadros 802.11 via `AF_PACKET`, sem depender de libpcap.
//!
//! A interface já precisa estar em modo monitor (ver [`crate::netlink`]).
//! Isso só funciona em Linux — é exatamente o que o libpcap faz por baixo
//! quando usado em modo monitor, então reimplementar aqui é pouco código e
//! remove uma dependência de sistema.
#![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 {
/// Abre um socket raw ligado à interface `iface` (já em modo monitor).
/// Exige `CAP_NET_RAW` (tipicamente 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 as u16).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 })
}
/// Define um timeout de recepção, para o loop de captura poder checar
/// periodicamente um sinal de parada sem travar para sempre em `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(())
}
/// Lê um quadro. Retorna `Ok(None)` em timeout (ver `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)
}
/// Garante, de forma best-effort, que o processo tem privilégio para abrir
/// sockets raw — útil para dar um erro claro antes de tentar o resto.
pub fn check_capabilities() -> bool {
let euid = unsafe { libc::geteuid() };
euid == 0
}