use crate::models::host::{HostResult, PingResult}; use crate::net::dns::reverse_lookup; use crate::scanner::netbios::query_netbios; use crate::scanner::ports::{scan_host_ports, DEFAULT_PORTS}; use ping_async::IcmpEchoRequestor; use std::net::IpAddr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tauri::{AppHandle, Emitter}; use tokio::task::JoinSet; const MAX_CONCURRENT: usize = 128; async fn ping_host(ip: IpAddr, timeout: Duration) -> HostResult { let requestor = match IcmpEchoRequestor::new(ip, None, None, Some(timeout)) { Ok(r) => r, Err(e) => { tracing::debug!("Failed to create ping request for {}: {}", ip, e); return HostResult::down(ip); } }; match requestor.send().await { Ok(reply) => match reply.status() { ping_async::IcmpEchoStatus::Success => { let rtt = reply.round_trip_time(); let hostname = reverse_lookup(ip).await; let mut result = HostResult::from_ping( ip, PingResult { rtt_ms: rtt.as_secs_f64() * 1000.0, ttl: None, loss_pct: 0.0, }, ); result.hostname = hostname; result } other => { tracing::debug!("{} returned {:?}", ip, other); HostResult::down(ip) } }, Err(e) => { tracing::debug!("Ping error for {}: {}", ip, e); HostResult::down(ip) } } } pub async fn ping_sweep( app: &AppHandle, ips: &[IpAddr], timeout_ms: u64, cancel: Arc, port_scan: bool, ports: Option>, port_timeout_ms: u64, ) { let total = ips.len() as u32; let timeout = Duration::from_millis(timeout_ms); let mut done: u32 = 0; for chunk in ips.chunks(MAX_CONCURRENT) { if cancel.load(Ordering::Relaxed) { break; } let mut join_set = JoinSet::new(); for &ip in chunk { let timeout = timeout; join_set.spawn(async move { ping_host(ip, timeout).await }); } while let Some(result) = join_set.join_next().await { done += 1; match result { Ok(host_result) => { // Emit immediately so the host shows up in the table let _ = app.emit("scan:host-found", &host_result); // Port scan Up hosts in place if host_result.status == crate::models::host::HostStatus::Up { let app2 = app.clone(); let ports2 = ports.clone(); tokio::spawn(async move { let mut enriched = host_result; // NetBIOS and port scan concurrently let nb_fut = query_netbios(enriched.ip, 500); if port_scan { let scan_ports = ports2 .unwrap_or_else(|| DEFAULT_PORTS.to_vec()); let (nb_result, port_results) = tokio::join!( nb_fut, scan_host_ports(enriched.ip, &scan_ports, port_timeout_ms) ); enriched.netbios = nb_result; enriched.ports = port_results; } else { enriched.netbios = nb_fut.await; } let _ = app2.emit("scan:host-found", &enriched); }); } } Err(e) => { tracing::warn!("Ping task panicked: {}", e); } } let _ = app.emit( "scan:progress", serde_json::json!({ "done": done, "total": total }), ); } } let _ = app.emit( "scan:complete", serde_json::json!({ "total_hosts": done }), ); }