131 lines
4.3 KiB
Rust
131 lines
4.3 KiB
Rust
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<AtomicBool>,
|
|
port_scan: bool,
|
|
ports: Option<Vec<u16>>,
|
|
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
|
|
}),
|
|
);
|
|
}
|