Skip to main content

netscli_core/
inspect.rs

1use crate::error::Result;
2use crate::ping::{PingResult, PingScanner};
3use crate::scan::{PortResult, PortScanner};
4use serde::Serialize;
5use std::net::IpAddr;
6
7#[derive(Debug, Clone, Serialize)]
8pub struct InspectResult {
9    pub host: String,
10    pub ip: Option<IpAddr>,
11    pub ping: Option<PingResult>,
12    pub ports: Vec<PortResult>,
13    pub open_ports: Vec<PortResult>,
14    pub hostname: Option<String>,
15}
16
17pub struct InspectEngine {
18    ping: PingScanner,
19    scan: PortScanner,
20    ping_timeout_ms: u64,
21    scan_timeout_ms: u64,
22    dns_timeout_ms: u64,
23}
24
25impl InspectEngine {
26    pub fn new(concurrency: usize) -> Self {
27        Self::new_with_timeouts(
28            concurrency,
29            crate::DEFAULT_PING_TIMEOUT_MS,
30            crate::DEFAULT_SCAN_TIMEOUT_MS,
31            crate::DEFAULT_DNS_TIMEOUT_MS,
32        )
33    }
34
35    pub fn new_with_timeouts(
36        concurrency: usize,
37        ping_timeout_ms: u64,
38        scan_timeout_ms: u64,
39        dns_timeout_ms: u64,
40    ) -> Self {
41        let concurrency = concurrency.clamp(1, crate::MAX_CONCURRENCY);
42        Self {
43            ping: PingScanner::new(concurrency),
44            scan: PortScanner::new(concurrency),
45            ping_timeout_ms: ping_timeout_ms.max(1),
46            scan_timeout_ms: scan_timeout_ms.max(1),
47            dns_timeout_ms: dns_timeout_ms.max(1),
48        }
49    }
50
51    pub async fn inspect(&self, host: String, ports: Vec<u16>) -> Result<InspectResult> {
52        // Validate before doing any work. `Ops::inspect_host` checks too, but
53        // this engine is public API and was reachable with a hand-built
54        // `Vec<u16>` that skipped both the 4,096-port cap and the port-0
55        // rejection.
56        if !ports.is_empty() {
57            crate::validate_ports(&ports)?;
58        }
59
60        let ip_for_scan =
61            crate::ops::resolve_host_ip_with_timeout(&host, self.dns_timeout_ms).await?;
62
63        // Run ping, scan, and reverse-DNS concurrently to keep inspect latency bounded
64        // by the slowest of the three rather than their sum.
65        let ping_fut = self.ping.ping(ip_for_scan, self.ping_timeout_ms);
66        let scan_fut = self
67            .scan
68            .scan_host(ip_for_scan, ports, self.scan_timeout_ms);
69        let hostname_fut =
70            crate::dns::reverse_lookup_best_effort_timeout(ip_for_scan, self.dns_timeout_ms);
71
72        let (ping_res, ports_res, hostname) = tokio::join!(ping_fut, scan_fut, hostname_fut);
73
74        let ports = ports_res?;
75        let open_ports = ports.iter().filter(|p| p.open).cloned().collect();
76
77        Ok(InspectResult {
78            host,
79            ip: Some(ip_for_scan),
80            ping: Some(ping_res),
81            ports,
82            open_ports,
83            hostname,
84        })
85    }
86}