- 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
96 lines
3.4 KiB
Rust
96 lines
3.4 KiB
Rust
//! Controle de modo (managed/monitor) e canal via `iw`.
|
|
//!
|
|
//! Isso é uma ponte deliberada: o protocolo real é nl80211 por netlink, e
|
|
//! trocar este módulo por uma implementação nativa (sem depender do binário
|
|
//! `iw` instalado) é o próximo passo natural. Por ora, `iw` é amplamente
|
|
//! disponível, testado e evita reimplementar genl/nl80211 antes de validar
|
|
//! o resto do pipeline com o adaptador real.
|
|
|
|
use crate::error::{Result, SabiaError};
|
|
use crate::iface::Mode;
|
|
use std::process::Command;
|
|
|
|
fn run(cmd: &str, args: &[&str]) -> Result<String> {
|
|
let out = Command::new(cmd).args(args).output().map_err(|e| SabiaError::CommandFailed {
|
|
cmd: format!("{cmd} {}", args.join(" ")),
|
|
detail: e.to_string(),
|
|
})?;
|
|
if !out.status.success() {
|
|
let detail = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
|
let detail = if detail.is_empty() {
|
|
String::from_utf8_lossy(&out.stdout).trim().to_string()
|
|
} else {
|
|
detail
|
|
};
|
|
if detail.contains("Operation not permitted") || detail.contains("nl80211 not found") {
|
|
return Err(SabiaError::PermissionDenied);
|
|
}
|
|
return Err(SabiaError::CommandFailed {
|
|
cmd: format!("{cmd} {}", args.join(" ")),
|
|
detail,
|
|
});
|
|
}
|
|
Ok(String::from_utf8_lossy(&out.stdout).into_owned())
|
|
}
|
|
|
|
pub fn iw_dev_type(iface: &str) -> Option<Mode> {
|
|
let out = run("iw", &["dev", iface, "info"]).ok()?;
|
|
for line in out.lines() {
|
|
let line = line.trim();
|
|
if let Some(t) = line.strip_prefix("type ") {
|
|
return Some(match t.trim() {
|
|
"monitor" => Mode::Monitor,
|
|
"managed" => Mode::Managed,
|
|
_ => Mode::Unknown,
|
|
});
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Lista (canal, frequência) suportados pelo phy da interface.
|
|
pub fn supported_channels(iface: &str) -> Result<Vec<u32>> {
|
|
let out = run("iw", &["dev", iface, "info"])?;
|
|
let phy = out
|
|
.lines()
|
|
.find_map(|l| l.trim().strip_prefix("wiphy "))
|
|
.map(|n| format!("phy{n}"))
|
|
.ok_or_else(|| SabiaError::CommandFailed {
|
|
cmd: "iw dev info".into(),
|
|
detail: "wiphy não encontrado na saída".into(),
|
|
})?;
|
|
let info = run("iw", &["phy", &phy, "info"])?;
|
|
let mut channels = Vec::new();
|
|
for line in info.lines() {
|
|
let line = line.trim();
|
|
// formato: "* 2412 MHz [1] (20.0 dBm)"
|
|
if let Some(rest) = line.strip_prefix("* ") {
|
|
if let Some(start) = rest.find('[') {
|
|
if let Some(end) = rest[start..].find(']') {
|
|
if let Ok(ch) = rest[start + 1..start + end].parse::<u32>() {
|
|
channels.push(ch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
channels.sort_unstable();
|
|
channels.dedup();
|
|
Ok(channels)
|
|
}
|
|
|
|
/// Põe a interface em modo monitor. Requer `CAP_NET_ADMIN` (normalmente root).
|
|
pub fn set_monitor_mode(iface: &str, enable: bool) -> Result<()> {
|
|
run("ip", &["link", "set", iface, "down"])?;
|
|
let target = if enable { "monitor" } else { "managed" };
|
|
run("iw", &["dev", iface, "set", "type", target])?;
|
|
run("ip", &["link", "set", iface, "up"])?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Troca o canal atual da interface (precisa já estar em modo monitor).
|
|
pub fn set_channel(iface: &str, channel: u32) -> Result<()> {
|
|
run("iw", &["dev", iface, "set", "channel", &channel.to_string()])?;
|
|
Ok(())
|
|
}
|