Skip to main content

netui/scanner/
mod.rs

1//! Network scanner module for host discovery and packet capture.
2//!
3//! This module provides the main [`Scanner`] struct that coordinates
4//! network scanning operations using a backend-agnostic architecture.
5
6use std::collections::HashSet;
7use std::net::Ipv4Addr;
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10use tokio::sync::mpsc::{self, channel, Sender};
11use tokio::task::JoinHandle;
12use tokio_util::sync::CancellationToken;
13
14use crate::backend::{BackendConfig, BackendFactory, BackendType};
15use crate::error::AppResult;
16use crate::event::Event;
17use crate::event::ScannerEvent;
18use crate::interface_utils;
19use crate::utils::recover_or_log;
20
21#[cfg(feature = "ebpf-backend")]
22use crate::backend::EbpfBackendFactory;
23use crate::backend::PnetBackendFactory;
24
25// Private sub-modules
26mod arp_scanner;
27pub mod arp_validator;
28mod dns_resolver;
29mod gateway;
30// Public for benchmarking
31pub mod packet_processor;
32pub mod tasks;
33
34// Enum for scanner input events (public for use in tasks)
35pub enum ScannerInputEvent {
36    StartScanning,
37}
38
39/// Main network scanner struct.
40///
41/// The scanner coordinates network discovery and monitoring operations
42/// including ARP scanning, packet capture, and host detection.
43pub struct Scanner {
44    scanner_input_tx: Sender<ScannerInputEvent>,
45    scanner_outputs: Sender<Event>,
46    interface_name: String,
47    cancel_token: CancellationToken,
48    task_handles: Vec<JoinHandle<()>>,
49    local_ips: HashSet<Ipv4Addr>,
50    discovered_hosts: Arc<Mutex<HashSet<Ipv4Addr>>>,
51    /// ARP validator for rejecting unsolicited ARP replies
52    arp_validator: Arc<Mutex<arp_validator::ArpValidator>>,
53    /// Default gateway IP for Internet traffic detection
54    gateway_ip: Option<Ipv4Addr>,
55}
56
57impl Scanner {
58    /// Creates a new [`Scanner`] with the specified configuration.
59    ///
60    /// # Arguments
61    /// * `scanner_outputs` - Channel for sending scanner events
62    /// * `interface_name` - Name of the network interface to use
63    /// * `backend_type` - Type of backend to use (pnet or eBPF)
64    ///
65    /// # Returns
66    /// A new Scanner instance, or an error if initialization fails
67    pub fn new(
68        scanner_outputs: mpsc::Sender<Event>,
69        interface_name: String,
70        backend_type: BackendType,
71    ) -> AppResult<Self> {
72        let backend_factory: Box<dyn BackendFactory> = match backend_type {
73            BackendType::Pnet => Box::new(PnetBackendFactory),
74            #[cfg(feature = "ebpf-backend")]
75            BackendType::Ebpf => Box::new(EbpfBackendFactory),
76            #[cfg(not(feature = "ebpf-backend"))]
77            BackendType::Ebpf => {
78                return Err("eBPF backend not compiled in. Enable 'ebpf-backend' feature.".into())
79            }
80        };
81        let backend_config = BackendConfig::new(interface_name.clone());
82        let (packet_source, packet_sink) = backend_factory
83            .create(backend_config)
84            .map_err(|e| format!("Failed to create backend: {}", e))?;
85
86        if let Err(e) = scanner_outputs.try_send(Event::Scanner(
87            crate::event::ScannerEvent::InterfaceName(interface_name.clone()),
88        )) {
89            return Err(format!("Failed to send interface name event: {}", e).into());
90        }
91
92        const SCANNER_INPUT_CAPACITY: usize = 100;
93        let (scanner_input_tx, scanner_input_rx) =
94            channel::<ScannerInputEvent>(SCANNER_INPUT_CAPACITY);
95        let cancel_token = CancellationToken::new();
96
97        // Get local IPs and MAC from the interface for direction detection
98        let local_ips: HashSet<Ipv4Addr> = pnet_datalink::interfaces()
99            .iter()
100            .filter(|nif| {
101                interface_utils::is_interface_active(nif)
102                    && interface_utils::interface_matches(nif, &interface_name)
103            })
104            .flat_map(interface_utils::get_interface_ipv4_addrs)
105            .collect();
106
107        let mut scanner = Self {
108            scanner_outputs,
109            scanner_input_tx,
110            interface_name,
111            cancel_token,
112            task_handles: Vec::new(),
113            local_ips,
114            discovered_hosts: Arc::new(Mutex::new(HashSet::new())),
115            // Create ARP validator with a 15-second timeout
116            // This accommodates full ARP scans on /24 networks (~9.5s for 256 hosts)
117            // while still protecting against delayed unsolicited ARP replies
118            arp_validator: Arc::new(Mutex::new(arp_validator::ArpValidator::new(
119                Duration::from_secs(15),
120            ))),
121            // Detect default gateway for Internet traffic identification
122            gateway_ip: gateway::detect_default_gateway(),
123        };
124
125        // Log detected gateway (if any)
126        if let Some(gw) = scanner.gateway_ip {
127            tracing::info!("Detected default gateway: {}", gw);
128        } else {
129            tracing::info!("No default gateway detected (may be expected in some environments)");
130        }
131
132        // Add local device to hosts list immediately
133        // This ensures that local machine appears in the "Devices" tab
134        for local_ip in &scanner.local_ips {
135            let local_host = crate::host::Host {
136                ipv4: *local_ip,
137                mac: crate::types::MacAddr::default(),
138                hostname: None,
139                time: chrono::Local::now(),
140                speed: None,
141            };
142            if let Err(e) = scanner
143                .scanner_outputs
144                .try_send(Event::Scanner(ScannerEvent::HostFound(local_host)))
145            {
146                tracing::warn!(
147                    "Failed to send HostFound event for local IP {}: {}",
148                    local_ip,
149                    e
150                );
151            }
152        }
153
154        // Start background tasks
155        let listener_handles = tasks::start_listening(
156            packet_source,
157            scanner.scanner_outputs.clone(),
158            scanner.local_ips.clone(),
159            scanner.discovered_hosts.clone(),
160            Arc::clone(&scanner.arp_validator),
161            scanner.gateway_ip,
162            scanner.cancel_token.clone(),
163        );
164        scanner.task_handles.extend(listener_handles);
165
166        let tx_handle = tasks::start_tx_worker(
167            scanner_input_rx,
168            packet_sink,
169            scanner.interface_name.clone(),
170            scanner.scanner_outputs.clone(),
171            Arc::clone(&scanner.arp_validator),
172            scanner.cancel_token.clone(),
173        );
174        scanner.task_handles.push(tx_handle);
175
176        Ok(scanner)
177    }
178
179    /// Send ARP packets to discover hosts on the network.
180    ///
181    /// This triggers an ARP scan of the network range(s) configured
182    /// on the selected interface.
183    pub fn send_arp_packets(&self) {
184        if let Err(e) = self
185            .scanner_input_tx
186            .try_send(ScannerInputEvent::StartScanning)
187        {
188            tracing::error!("Failed to send StartScanning event: {}", e);
189        }
190    }
191
192    /// Get the detected default gateway IP address.
193    ///
194    /// Returns None if no gateway was detected or if detection failed.
195    pub fn gateway_ip(&self) -> Option<Ipv4Addr> {
196        self.gateway_ip
197    }
198
199    /// Get the set of detected local IP addresses.
200    ///
201    /// Returns the set of IPs on the local interfaces that were detected during startup.
202    pub fn local_ips(&self) -> &HashSet<Ipv4Addr> {
203        &self.local_ips
204    }
205
206    /// Remove hosts from the discovered set, allowing them to be re-discovered.
207    ///
208    /// This should be called when hosts are cleared/deleted from the UI.
209    ///
210    /// # Arguments
211    /// * `ips` - Slice of IP addresses to remove from the discovered set
212    pub fn remove_discovered_hosts(&self, ips: &[Ipv4Addr]) {
213        let mut discovered =
214            recover_or_log(self.discovered_hosts.lock(), "remove_discovered_hosts");
215        for ip in ips {
216            discovered.remove(ip);
217        }
218    }
219}
220
221impl Drop for Scanner {
222    fn drop(&mut self) {
223        tracing::debug!("Scanner::drop() called, cancelling background tasks");
224        self.cancel_token.cancel();
225        // Abort handles for immediate cleanup
226        for handle in self.task_handles.drain(..) {
227            handle.abort();
228        }
229    }
230}