Files
sabia/crates/sabia-core/src/netlink.rs
T
Lucas e989413018 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
2026-10-23 08:22:04 -03:00

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(())
}