//! Diagnóstico do ambiente: privilégios, ferramentas externas, interfaces, //! suporte a modo monitor e integridade da trilha de auditoria. use crate::audit::AuditLog; use crate::iface::{self, Interface}; use crate::netlink::{self, command_exists}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, serde::Serialize)] pub struct IfaceDiagnosis { pub name: String, pub mac: String, pub mode: String, pub monitor_supported: bool, pub driver: Option, pub up: bool, } #[derive(Debug, Clone, serde::Serialize)] pub struct DoctorReport { pub version: String, pub platform: String, pub root: bool, pub iw: bool, pub ip: bool, pub curl: bool, pub capture_backend: String, pub audit_path: String, pub audit_ok: bool, pub interfaces: Vec, pub notes: Vec, } pub fn diagnose(version: &str, audit_path: impl AsRef) -> DoctorReport { let audit_path = audit_path.as_ref().to_path_buf(); let root = netlink::is_root(); let iw = command_exists("iw"); let ip = command_exists("ip"); let curl = command_exists("curl"); let (audit_ok, mut notes) = audit_status(&audit_path); let all: Vec = iface::list_all().unwrap_or_default(); let interfaces = all .into_iter() .filter(|i| i.phy.is_some()) .map(|i| IfaceDiagnosis { monitor_supported: netlink::monitor_supported(&i.name), driver: netlink::driver_name(&i.name), name: i.name, mac: i.mac, mode: format!("{:?}", i.mode).to_lowercase(), up: i.up, }) .collect::>(); if !root { notes.push( "sem root: modo monitor, captura e injeção vão falhar (use sudo ou o app desktop)" .into(), ); } if !iw { notes.push("`iw` não encontrado no PATH — instale iw/iproute2".into()); } if !ip { notes.push("`ip` não encontrado no PATH — instale iproute2".into()); } if interfaces.is_empty() { notes.push("nenhuma interface Wi-Fi encontrada".into()); } else if !interfaces.iter().any(|i| i.monitor_supported) { notes.push("nenhuma interface anuncia suporte a modo monitor".into()); } if !curl { notes.push("`curl` não encontrado — a auto-atualização não funciona".into()); } if !audit_ok { notes.push("a trilha de auditoria existente está corrompida".into()); } DoctorReport { version: version.to_string(), platform: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH), root, iw, ip, curl, capture_backend: if cfg!(target_os = "linux") { "AF_PACKET (Linux)".into() } else { "indisponível nesta plataforma".into() }, audit_path: audit_path.display().to_string(), audit_ok, interfaces, notes, } } fn audit_status(path: &PathBuf) -> (bool, Vec) { let mut notes = Vec::new(); if !path.exists() { notes.push(format!( "trilha de auditoria ainda não criada em {}", path.display() )); return (true, notes); } match AuditLog::verify(path) { Ok(None) => (true, notes), Ok(Some(idx)) => { notes.push(format!("cadeia de auditoria rompida na entrada {idx}")); (false, notes) } Err(e) => { notes.push(format!("não foi possível ler a trilha de auditoria: {e}")); (false, notes) } } } #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; #[test] fn reports_environment_without_panicking() { let dir = TempDir::new().unwrap(); let report = diagnose("9.9.9", dir.path().join("nao-existe.log")); assert_eq!(report.version, "9.9.9"); assert!(report.audit_ok); assert!(report.platform.contains('-')); assert!(!report.capture_backend.is_empty()); } }