1use std::net::IpAddr;
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use dashmap::DashMap;
21use is_root::is_root;
22use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
23use tokio::task::JoinHandle;
24
25use crate::core::config::ZondConfig;
26use crate::core::handle::ScanHandle;
27use crate::core::models::{host::Host, ip::set::IpSet, target::TargetMap};
28use crate::core::session::{ScanEvent, ScanSession};
29use crate::scanner::resolver::HostnameResolver;
30use crate::system::interface;
31use crate::{error, info, success, warn};
32use local::LocalScanner;
33use routed::RoutedScanner;
34
35mod connect;
36pub mod dispatcher;
37mod local;
38mod resolver;
39mod routed;
40
41#[async_trait]
42trait NetworkExplorer {
43 async fn discover_hosts(&mut self) -> anyhow::Result<()>;
44}
45
46pub async fn scan(
47 target_map: TargetMap,
48) -> anyhow::Result<(ScanSession, JoinHandle<anyhow::Result<()>>)> {
49 if not_root() {
50 warn!("Privileged port scanning (SYN) not yet implemented; using TCP connect fallback");
52 }
53
54 let (session, scan_handle, events_tx) = ScanSession::new();
55 let store = session.store.clone();
56
57 let join_handle = tokio::spawn(async move {
58 let dispatcher = dispatcher::Dispatcher::new(target_map);
59 let rx = dispatcher.run_shuffled(&scan_handle);
60 connect::scan(rx, &scan_handle, 50, store, events_tx).await
61 });
62
63 Ok((session, join_handle))
64}
65
66pub async fn discover(
77 targets: IpSet,
78 cfg: &ZondConfig,
79) -> anyhow::Result<(ScanSession, JoinHandle<anyhow::Result<()>>)> {
80 let with_dns: bool = !cfg.no_dns;
81 let (session, scan_handle, events_tx) = ScanSession::new();
82 let store = session.store.clone();
83
84 if not_root() {
85 let store_clone = store.clone();
86 let events_tx_clone = events_tx.clone();
87 let handle_clone = scan_handle.clone();
88
89 let join_handle = tokio::spawn(async move {
90 connect::discover(targets, &handle_clone, store_clone.clone(), events_tx_clone).await?;
91 if with_dns {
92 resolver::resolve_hosts_async(store_clone).await;
93 }
94 Ok(())
95 });
96 return Ok((session, join_handle));
97 }
98
99 let (dns_tx, resolver_task) = if with_dns {
100 let (tx, rx) = mpsc::unbounded_channel();
101 let task = spawn_resolver(rx).await;
102 (Some(tx), Some(task))
103 } else {
104 info!("DNS resolution skipped by user flag");
105 (None, None)
106 };
107
108 let scanner_handles = spawn_explorers(
109 targets,
110 &scan_handle,
111 dns_tx,
112 store.clone(),
113 events_tx.clone(),
114 )
115 .await;
116
117 let join_handle = tokio::spawn(async move {
118 for handle in scanner_handles {
119 match handle.await {
120 Ok(Ok(())) => {}
121 Ok(Err(e)) => error!("Scanner task failed: {e}"),
122 Err(e) => error!("Task panicked: {e}"),
123 }
124 }
125
126 if let Some(task) = resolver_task
127 && let Ok(Some(mut resolver)) = task.await
128 {
129 resolver.resolve_hosts(store);
130 }
131
132 Ok(())
133 });
134
135 Ok((session, join_handle))
136}
137
138async fn spawn_explorers(
139 targets: IpSet,
140 scan_handle: &ScanHandle,
141 dns_tx: Option<UnboundedSender<IpAddr>>,
142 store: Arc<DashMap<IpAddr, Host>>,
143 events_tx: UnboundedSender<ScanEvent>,
144) -> Vec<JoinHandle<anyhow::Result<()>>> {
145 let mut handles = Vec::new();
146
147 let (interface_map, unmapped_ips) = interface::map_ips_to_interfaces(targets);
148
149 for (intf, (local_ips, routed_ips)) in interface_map {
150 if !local_ips.is_empty() {
152 info!(verbosity = 1, "Spawning LOCAL scanner for {}", intf.name);
153 let tx = dns_tx.clone();
154 let intf_c = intf.clone();
155
156 let scan_handle_clone = scan_handle.clone();
157 let store_clone = store.clone();
158 let events_tx_clone = events_tx.clone();
159 let handle = tokio::spawn(async move {
160 let mut scanner = LocalScanner::new(
161 intf_c,
162 local_ips,
163 scan_handle_clone,
164 tx,
165 store_clone,
166 events_tx_clone,
167 )?;
168 scanner.discover_hosts().await
169 });
170 handles.push(handle);
171 }
172
173 if !routed_ips.is_empty() {
175 info!(verbosity = 1, "Spawning ROUTED scanner for {}", intf.name);
176 let tx = dns_tx.clone();
177 let intf_c = intf.clone();
178
179 let scan_handle_clone = scan_handle.clone();
180 let store_clone = store.clone();
181 let events_tx_clone = events_tx.clone();
182 let handle = tokio::spawn(async move {
183 let mut scanner = RoutedScanner::new(
184 intf_c,
185 routed_ips,
186 scan_handle_clone,
187 tx,
188 store_clone,
189 events_tx_clone,
190 )?;
191 scanner.discover_hosts().await
192 });
193 handles.push(handle);
194 }
195 }
196
197 if !unmapped_ips.is_empty() {
199 info!(
200 verbosity = 1,
201 "Spawning FALLBACK scanner for unmapped targets"
202 );
203 let scan_handle_clone = scan_handle.clone();
204 let store_clone = store.clone();
205 let events_tx_clone = events_tx.clone();
206 let handle = tokio::spawn(async move {
207 connect::discover(
208 unmapped_ips,
209 &scan_handle_clone,
210 store_clone,
211 events_tx_clone,
212 )
213 .await
214 });
215 handles.push(handle);
216 }
217
218 handles
219}
220
221async fn spawn_resolver(dns_rx: UnboundedReceiver<IpAddr>) -> JoinHandle<Option<HostnameResolver>> {
222 tokio::spawn(async move {
223 match HostnameResolver::new(dns_rx) {
224 Ok(resolver) => {
225 success!("Successfully initialized hostname resolver");
226 Some(resolver.run().await)
227 }
228 Err(e) => {
229 error!("Resolver failed to start: {e}");
230 None
231 }
232 }
233 })
234}
235
236fn not_root() -> bool {
237 if !is_root() {
238 warn!("Root privileges missing, defaulting to unprivileged TCP scan");
239 return true;
240 }
241
242 success!("Root privileges detected, raw socket scan enabled");
243 false
244}