1use crate::arp::NetworkManager;
2use crate::oui::lookup_vendor;
3use crate::ping::PingScanner;
4use futures::stream::{self, StreamExt};
5use ipnet::Ipv4Net;
6use serde::Serialize;
7use std::collections::HashMap;
8use std::net::IpAddr;
9use std::sync::{
10 atomic::{AtomicUsize, Ordering},
11 Arc,
12};
13
14#[derive(Debug, Clone, Serialize)]
15pub struct Host {
16 pub ip: IpAddr,
17 pub hostname: Option<String>,
18 pub mac: Option<String>,
19 pub vendor: Option<String>,
20 pub rtt_ms: Option<u64>,
21}
22
23pub struct DiscoverEngine {
24 ping_scanner: PingScanner,
25 concurrency: usize,
26 ping_timeout_ms: u64,
27 dns_timeout_ms: u64,
28}
29
30#[derive(Debug, Clone, Copy)]
31pub enum DiscoverPhase {
32 Ping,
33 Resolve,
34}
35
36#[derive(Debug, Clone)]
37pub struct DiscoverProgress {
38 pub phase: DiscoverPhase,
39 pub completed: usize,
40 pub total: usize,
41 pub found: usize,
42 pub ip: IpAddr,
43}
44
45impl DiscoverEngine {
46 pub fn new(concurrency: usize) -> Self {
47 Self::new_with_timeouts(
48 concurrency,
49 crate::DEFAULT_PING_TIMEOUT_MS,
50 crate::DEFAULT_DNS_TIMEOUT_MS,
51 )
52 }
53
54 pub fn new_with_timeouts(
55 concurrency: usize,
56 ping_timeout_ms: u64,
57 dns_timeout_ms: u64,
58 ) -> Self {
59 let concurrency = concurrency.max(1);
60 Self {
61 ping_scanner: PingScanner::new(concurrency),
62 concurrency,
63 ping_timeout_ms: ping_timeout_ms.max(1),
64 dns_timeout_ms: dns_timeout_ms.max(1),
65 }
66 }
67
68 pub async fn scan_subnet(&self, subnet: Ipv4Net, resolve: bool) -> Vec<Host> {
69 self.scan_subnet_with_progress(subnet, resolve, None).await
70 }
71
72 pub async fn scan_subnet_with_progress(
73 &self,
74 subnet: Ipv4Net,
75 resolve: bool,
76 progress: Option<Arc<dyn Fn(DiscoverProgress) + Send + Sync>>,
77 ) -> Vec<Host> {
78 let ips: Vec<IpAddr> = subnet.hosts().map(IpAddr::V4).collect();
79
80 let total = ips.len();
82 let completed = Arc::new(AtomicUsize::new(0));
83 let found = Arc::new(AtomicUsize::new(0));
84 let ping_results = stream::iter(ips)
85 .map(|ip| {
86 let scanner = self.ping_scanner.clone();
87 let timeout_ms = self.ping_timeout_ms;
88 let completed = completed.clone();
89 let found = found.clone();
90 let progress = progress.clone();
91 async move {
92 let res = scanner.ping(ip, timeout_ms).await;
93 let done = completed.fetch_add(1, Ordering::SeqCst) + 1;
94 if res.alive {
100 found.fetch_add(1, Ordering::SeqCst);
101 }
102 let found_snapshot = found.load(Ordering::SeqCst);
103
104 if let Some(cb) = &progress {
105 if res.alive || done == total || done.is_multiple_of(10) {
106 cb(DiscoverProgress {
107 phase: DiscoverPhase::Ping,
108 completed: done,
109 total,
110 found: found_snapshot,
111 ip,
112 });
113 }
114 }
115
116 res
117 }
118 })
119 .buffer_unordered(self.concurrency)
120 .collect::<Vec<crate::ping::PingResult>>()
121 .await;
122
123 let alive: Vec<crate::ping::PingResult> =
124 ping_results.into_iter().filter(|r| r.alive).collect();
125
126 let arp_map: HashMap<IpAddr, crate::arp::ArpEntry> = NetworkManager::get_arp_table()
128 .unwrap_or_default()
129 .into_iter()
130 .map(|e| (e.ip, e))
131 .collect();
132
133 let hostname_map: HashMap<IpAddr, Option<String>> = if resolve {
135 let ips = alive.iter().map(|r| r.ip).collect::<Vec<_>>();
136 let concurrency = self.concurrency.min(32);
137 let total = ips.len();
138 let completed = Arc::new(AtomicUsize::new(0));
139 let resolved = Arc::new(AtomicUsize::new(0));
140 stream::iter(ips)
141 .map(|ip| {
142 let dns_timeout_ms = self.dns_timeout_ms;
143 let completed = completed.clone();
144 let resolved = resolved.clone();
145 let progress = progress.clone();
146 async move {
147 let name =
148 crate::dns::reverse_lookup_best_effort_timeout(ip, dns_timeout_ms)
149 .await;
150 let done = completed.fetch_add(1, Ordering::SeqCst) + 1;
151 if name.is_some() {
152 resolved.fetch_add(1, Ordering::SeqCst);
153 }
154 let resolved_count = resolved.load(Ordering::SeqCst);
155
156 if let Some(cb) = &progress {
157 if name.is_some() || done == total || done.is_multiple_of(5) {
158 cb(DiscoverProgress {
159 phase: DiscoverPhase::Resolve,
160 completed: done,
161 total,
162 found: resolved_count,
163 ip,
164 });
165 }
166 }
167
168 (ip, name)
169 }
170 })
171 .buffer_unordered(concurrency)
172 .collect::<Vec<(IpAddr, Option<String>)>>()
173 .await
174 .into_iter()
175 .collect()
176 } else {
177 HashMap::new()
178 };
179
180 alive
181 .into_iter()
182 .map(|r| {
183 let hostname = hostname_map.get(&r.ip).cloned().unwrap_or(None);
184 let mac_entry = arp_map.get(&r.ip);
185 let mac_str = mac_entry.map(|e| e.mac.to_string());
186 let vendor = mac_entry
187 .and_then(|e| e.vendor.clone())
188 .or_else(|| mac_str.as_deref().and_then(lookup_vendor));
189 Host {
190 ip: r.ip,
191 hostname,
192 mac: mac_str,
193 vendor,
194 rtt_ms: r.rtt_ms,
195 }
196 })
197 .collect()
198 }
199}