//! 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 { 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 { 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> { 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::() { 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(()) }