1use crate::discover::{DiscoverEngine, Host};
2use crate::error::Result;
3use crate::scan::{PortResult, PortScanner};
4use futures::stream::{self, StreamExt};
5use ipnet::Ipv4Net;
6use serde::Serialize;
7use std::sync::{
8 atomic::{AtomicUsize, Ordering},
9 Arc,
10};
11
12#[derive(Debug, Clone, Serialize)]
13pub struct SweepEntry {
14 pub host: Host,
15 pub open_ports: Vec<PortResult>,
16}
17
18#[derive(Debug, Clone, Copy)]
19pub enum SweepPhase {
20 DiscoverPing,
21 DiscoverResolve,
22 Scan,
23}
24
25#[derive(Debug, Clone)]
26pub struct SweepProgress {
27 pub phase: SweepPhase,
28 pub completed: usize,
29 pub total: usize,
30 pub found: usize,
31 pub ip: std::net::IpAddr,
32}
33
34pub struct SweepEngine {
35 discover: DiscoverEngine,
36 scanner: PortScanner,
37 scan_timeout_ms: u64,
38 host_concurrency: usize,
39}
40
41impl SweepEngine {
42 pub fn new(concurrency: usize) -> Self {
43 Self::new_with_timeouts(
44 concurrency,
45 crate::DEFAULT_PING_TIMEOUT_MS,
46 crate::DEFAULT_SCAN_TIMEOUT_MS,
47 crate::DEFAULT_DNS_TIMEOUT_MS,
48 )
49 }
50
51 pub fn new_with_timeouts(
52 concurrency: usize,
53 ping_timeout_ms: u64,
54 scan_timeout_ms: u64,
55 dns_timeout_ms: u64,
56 ) -> Self {
57 let concurrency = concurrency.max(1);
58 Self {
59 discover: DiscoverEngine::new_with_timeouts(
60 concurrency,
61 ping_timeout_ms,
62 dns_timeout_ms,
63 ),
64 scanner: PortScanner::new(concurrency),
65 scan_timeout_ms: scan_timeout_ms.max(1),
66 host_concurrency: concurrency,
67 }
68 }
69
70 pub async fn sweep(
71 &self,
72 subnet: Ipv4Net,
73 ports: Vec<u16>,
74 resolve_hostnames: bool,
75 ) -> Result<Vec<SweepEntry>> {
76 self.sweep_with_progress(subnet, ports, resolve_hostnames, None)
77 .await
78 }
79
80 pub async fn sweep_with_progress(
84 &self,
85 subnet: Ipv4Net,
86 ports: Vec<u16>,
87 resolve_hostnames: bool,
88 progress: Option<Arc<dyn Fn(SweepProgress) + Send + Sync>>,
89 ) -> Result<Vec<SweepEntry>> {
90 let discover_progress = progress.clone().map(|cb| {
91 Arc::new(move |p: crate::discover::DiscoverProgress| {
92 let phase = match p.phase {
93 crate::discover::DiscoverPhase::Ping => SweepPhase::DiscoverPing,
94 crate::discover::DiscoverPhase::Resolve => SweepPhase::DiscoverResolve,
95 };
96 cb(SweepProgress {
97 phase,
98 completed: p.completed,
99 total: p.total,
100 found: p.found,
101 ip: p.ip,
102 });
103 }) as Arc<dyn Fn(crate::discover::DiscoverProgress) + Send + Sync>
104 });
105
106 let hosts = self
107 .discover
108 .scan_subnet_with_progress(subnet, resolve_hostnames, discover_progress)
109 .await;
110 let total_hosts = hosts.len();
111 let completed = Arc::new(AtomicUsize::new(0));
112 let open_hosts = Arc::new(AtomicUsize::new(0));
113 let ports = Arc::new(ports);
114
115 let scan_timeout_ms = self.scan_timeout_ms;
116 let scanner = self.scanner.clone();
117
118 let mut entries = stream::iter(hosts)
119 .map(|h| {
120 let scanner = scanner.clone();
121 let ports = ports.clone();
122 let completed = completed.clone();
123 let open_hosts = open_hosts.clone();
124 let progress = progress.clone();
125 async move {
126 let host_ip = h.ip;
127 let open_ports = scanner
128 .scan_host(host_ip, (*ports).clone(), scan_timeout_ms)
129 .await
130 .into_iter()
131 .filter(|p| p.open)
132 .collect();
133
134 let entry = SweepEntry {
135 host: h,
136 open_ports,
137 };
138
139 let done = completed.fetch_add(1, Ordering::SeqCst) + 1;
140 if let Some(cb) = &progress {
141 let found = if entry.open_ports.is_empty() {
142 open_hosts.load(Ordering::SeqCst)
143 } else {
144 open_hosts.fetch_add(1, Ordering::SeqCst) + 1
145 };
146 cb(SweepProgress {
147 phase: SweepPhase::Scan,
148 completed: done,
149 total: total_hosts,
150 found,
151 ip: host_ip,
152 });
153 }
154
155 entry
156 }
157 })
158 .buffer_unordered(self.host_concurrency)
159 .collect::<Vec<SweepEntry>>()
160 .await;
161
162 entries.sort_by_key(|e| e.host.ip);
164 Ok(entries)
165 }
166}