- 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
104 lines
3.0 KiB
Rust
104 lines
3.0 KiB
Rust
//! Parser mínimo de cabeçalho Radiotap — só os campos que usamos
|
|
//! (canal/frequência e força de sinal). Especificação:
|
|
//! <https://www.radiotap.org/>
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub struct RadiotapInfo {
|
|
pub freq_mhz: Option<u16>,
|
|
pub signal_dbm: Option<i8>,
|
|
pub header_len: usize,
|
|
}
|
|
|
|
// Índices de bit no bitmap "present" que nos interessam.
|
|
const BIT_FLAGS: u32 = 1 << 1;
|
|
const BIT_RATE: u32 = 1 << 2;
|
|
const BIT_CHANNEL: u32 = 1 << 3;
|
|
const BIT_ANTSIGNAL: u32 = 1 << 5;
|
|
|
|
/// Retorna as infos extraídas e quantos bytes o cabeçalho ocupa (para o
|
|
/// chamador saber onde o frame 802.11 propriamente começa).
|
|
pub fn parse(buf: &[u8]) -> Option<RadiotapInfo> {
|
|
if buf.len() < 8 || buf[0] != 0 {
|
|
return None; // version deve ser 0
|
|
}
|
|
let header_len = u16::from_le_bytes([buf[2], buf[3]]) as usize;
|
|
if header_len > buf.len() || header_len < 8 {
|
|
return None;
|
|
}
|
|
|
|
// Pode haver múltiplas palavras de "present" (bit 31 = continua).
|
|
let mut present_words = Vec::new();
|
|
let mut offset = 4;
|
|
loop {
|
|
if offset + 4 > buf.len() {
|
|
return None;
|
|
}
|
|
let word = u32::from_le_bytes([buf[offset], buf[offset + 1], buf[offset + 2], buf[offset + 3]]);
|
|
present_words.push(word);
|
|
offset += 4;
|
|
if word & (1 << 31) == 0 {
|
|
break;
|
|
}
|
|
}
|
|
let present = present_words[0]; // só o primeiro conjunto de campos padrão nos interessa
|
|
|
|
let mut info = RadiotapInfo { header_len, ..Default::default() };
|
|
let mut cursor = offset;
|
|
|
|
// TSFT (u64, align 8) viria antes de Flags; para o nosso subconjunto de
|
|
// campos simplificamos assumindo captura via mac80211 (ordem padrão:
|
|
// TSFT, Flags, Rate, Channel, ..., AntSignal). Avançamos campo a campo
|
|
// respeitando alinhamento, sem travar se algum bit não estiver setado.
|
|
macro_rules! align {
|
|
($a:expr) => {{
|
|
let rem = cursor % $a;
|
|
if rem != 0 {
|
|
cursor += $a - rem;
|
|
}
|
|
}};
|
|
}
|
|
|
|
if present & (1 << 0) != 0 {
|
|
align!(8);
|
|
cursor += 8; // TSFT
|
|
}
|
|
if present & BIT_FLAGS != 0 {
|
|
align!(1);
|
|
cursor += 1;
|
|
}
|
|
if present & BIT_RATE != 0 {
|
|
align!(1);
|
|
cursor += 1;
|
|
}
|
|
if present & BIT_CHANNEL != 0 {
|
|
align!(2);
|
|
if cursor + 4 <= buf.len() {
|
|
let freq = u16::from_le_bytes([buf[cursor], buf[cursor + 1]]);
|
|
info.freq_mhz = Some(freq);
|
|
}
|
|
cursor += 4;
|
|
}
|
|
// FHSS (bit 4) — raro, ignorado.
|
|
if present & (1 << 4) != 0 {
|
|
align!(2);
|
|
cursor += 2;
|
|
}
|
|
if present & BIT_ANTSIGNAL != 0 {
|
|
align!(1);
|
|
if cursor < buf.len() {
|
|
info.signal_dbm = Some(buf[cursor] as i8);
|
|
}
|
|
}
|
|
|
|
Some(info)
|
|
}
|
|
|
|
pub fn freq_to_channel(freq_mhz: u16) -> Option<u32> {
|
|
match freq_mhz {
|
|
2412..=2472 => Some(((freq_mhz - 2412) / 5 + 1) as u32),
|
|
2484 => Some(14),
|
|
5000..=5900 => Some(((freq_mhz - 5000) / 5) as u32),
|
|
_ => None,
|
|
}
|
|
}
|