Skip to main content

netscli_core/ops/
scan.rs

1use std::net::IpAddr;
2use std::sync::Arc;
3
4use super::config::Ops;
5use super::validation::parse_limited_ipv4_subnet;
6use crate::error::Result;
7use crate::{
8    default_ipv4_subnet_string, default_ports, validate_ports, DiscoverEngine, Host, InspectEngine,
9    InspectResult, PortResult, PortScanner, SweepEngine, SweepEntry,
10};
11
12/// Resolve the caller's port list, validating it.
13///
14/// Every scanning entry point funnels through here so the limits hold no
15/// matter which surface the call arrives on. Previously the 4,096 cap and
16/// the port-0 rejection lived only in the *parsers* — `parse_ports_checked`
17/// for the CLI/GUI and the MCP server's own `normalize_ports` — so the
18/// answers diverged (`netscli scan -p 0` reported "Scanned 1 port" while
19/// the MCP tool rejected the same input), and a library consumer calling
20/// `Ops` with a hand-built `Vec<u16>` bypassed both.
21fn resolve_ports(ports: Option<Vec<u16>>) -> Result<Vec<u16>> {
22    match ports {
23        Some(ports) => {
24            validate_ports(&ports)?;
25            Ok(ports)
26        }
27        None => Ok(default_ports()),
28    }
29}
30
31impl Ops {
32    pub async fn discover_ipv4(
33        &self,
34        subnet: Option<String>,
35        resolve: bool,
36    ) -> Result<(String, Vec<Host>)> {
37        self.discover_ipv4_with_progress(subnet, resolve, None)
38            .await
39    }
40
41    pub async fn discover_ipv4_with_progress(
42        &self,
43        subnet: Option<String>,
44        resolve: bool,
45        progress: Option<Arc<dyn Fn(crate::discover::DiscoverProgress) + Send + Sync>>,
46    ) -> Result<(String, Vec<Host>)> {
47        let subnet_str = subnet.unwrap_or_else(default_ipv4_subnet_string);
48        let net = parse_limited_ipv4_subnet(&subnet_str)?;
49        let engine = DiscoverEngine::new_with_timeouts(
50            self.cfg.concurrency,
51            self.cfg.ping_timeout_ms,
52            self.cfg.dns_timeout_ms,
53        );
54        let hosts = engine
55            .scan_subnet_with_progress(net, resolve, progress)
56            .await?;
57        Ok((subnet_str, hosts))
58    }
59
60    pub async fn scan_ports(
61        &self,
62        host: &str,
63        ports: Option<Vec<u16>>,
64    ) -> Result<(IpAddr, Vec<PortResult>)> {
65        self.scan_ports_with_progress(host, ports, None).await
66    }
67
68    pub async fn scan_ports_with_progress(
69        &self,
70        host: &str,
71        ports: Option<Vec<u16>>,
72        progress: Option<Arc<dyn Fn(crate::scan::PortScanProgress) + Send + Sync>>,
73    ) -> Result<(IpAddr, Vec<PortResult>)> {
74        let ip = self.resolve_host_ip(host).await?;
75        let ports = resolve_ports(ports)?;
76        let scanner = PortScanner::new(self.cfg.concurrency);
77        let results = scanner
78            .scan_host_with_progress(ip, ports, self.cfg.scan_timeout_ms, progress)
79            .await?;
80        Ok((ip, results))
81    }
82
83    pub async fn inspect_host(
84        &self,
85        host: String,
86        ports: Option<Vec<u16>>,
87    ) -> Result<InspectResult> {
88        let ports = resolve_ports(ports)?;
89        let engine = InspectEngine::new_with_timeouts(
90            self.cfg.concurrency,
91            self.cfg.ping_timeout_ms,
92            self.cfg.scan_timeout_ms,
93            self.cfg.dns_timeout_ms,
94        );
95        engine.inspect(host, ports).await
96    }
97
98    pub async fn sweep_ipv4(
99        &self,
100        subnet: Option<String>,
101        ports: Option<Vec<u16>>,
102        resolve_hostnames: bool,
103    ) -> Result<(String, Vec<SweepEntry>)> {
104        self.sweep_ipv4_with_progress(subnet, ports, resolve_hostnames, None)
105            .await
106    }
107
108    pub async fn sweep_ipv4_with_progress(
109        &self,
110        subnet: Option<String>,
111        ports: Option<Vec<u16>>,
112        resolve_hostnames: bool,
113        progress: Option<Arc<dyn Fn(crate::sweep::SweepProgress) + Send + Sync>>,
114    ) -> Result<(String, Vec<SweepEntry>)> {
115        let subnet_str = subnet.unwrap_or_else(default_ipv4_subnet_string);
116        let net = parse_limited_ipv4_subnet(&subnet_str)?;
117        let ports = resolve_ports(ports)?;
118        let engine = SweepEngine::new_with_timeouts(
119            self.cfg.concurrency,
120            self.cfg.ping_timeout_ms,
121            self.cfg.scan_timeout_ms,
122            self.cfg.dns_timeout_ms,
123        );
124        let results = engine
125            .sweep_with_progress(net, ports, resolve_hostnames, progress)
126            .await?;
127        Ok((subnet_str, results))
128    }
129}