use std::collections::HashSet;
use std::net::Ipv4Addr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::mpsc::{self, channel, Sender};
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use crate::backend::{BackendConfig, BackendFactory, BackendType};
use crate::error::AppResult;
use crate::event::Event;
use crate::event::ScannerEvent;
use crate::interface_utils;
use crate::utils::recover_or_log;
#[cfg(feature = "ebpf-backend")]
use crate::backend::EbpfBackendFactory;
use crate::backend::PnetBackendFactory;
mod arp_scanner;
pub mod arp_validator;
mod dns_resolver;
mod gateway;
pub mod packet_processor;
pub mod tasks;
pub enum ScannerInputEvent {
StartScanning,
}
pub struct Scanner {
scanner_input_tx: Sender<ScannerInputEvent>,
scanner_outputs: Sender<Event>,
interface_name: String,
cancel_token: CancellationToken,
task_handles: Vec<JoinHandle<()>>,
local_ips: HashSet<Ipv4Addr>,
discovered_hosts: Arc<Mutex<HashSet<Ipv4Addr>>>,
arp_validator: Arc<Mutex<arp_validator::ArpValidator>>,
gateway_ip: Option<Ipv4Addr>,
}
impl Scanner {
pub fn new(
scanner_outputs: mpsc::Sender<Event>,
interface_name: String,
backend_type: BackendType,
) -> AppResult<Self> {
let backend_factory: Box<dyn BackendFactory> = match backend_type {
BackendType::Pnet => Box::new(PnetBackendFactory),
#[cfg(feature = "ebpf-backend")]
BackendType::Ebpf => Box::new(EbpfBackendFactory),
#[cfg(not(feature = "ebpf-backend"))]
BackendType::Ebpf => {
return Err("eBPF backend not compiled in. Enable 'ebpf-backend' feature.".into())
}
};
let backend_config = BackendConfig::new(interface_name.clone());
let (packet_source, packet_sink) = backend_factory
.create(backend_config)
.map_err(|e| format!("Failed to create backend: {}", e))?;
if let Err(e) = scanner_outputs.try_send(Event::Scanner(
crate::event::ScannerEvent::InterfaceName(interface_name.clone()),
)) {
return Err(format!("Failed to send interface name event: {}", e).into());
}
const SCANNER_INPUT_CAPACITY: usize = 100;
let (scanner_input_tx, scanner_input_rx) =
channel::<ScannerInputEvent>(SCANNER_INPUT_CAPACITY);
let cancel_token = CancellationToken::new();
let local_ips: HashSet<Ipv4Addr> = pnet_datalink::interfaces()
.iter()
.filter(|nif| {
interface_utils::is_interface_active(nif)
&& interface_utils::interface_matches(nif, &interface_name)
})
.flat_map(interface_utils::get_interface_ipv4_addrs)
.collect();
let mut scanner = Self {
scanner_outputs,
scanner_input_tx,
interface_name,
cancel_token,
task_handles: Vec::new(),
local_ips,
discovered_hosts: Arc::new(Mutex::new(HashSet::new())),
arp_validator: Arc::new(Mutex::new(arp_validator::ArpValidator::new(
Duration::from_secs(15),
))),
gateway_ip: gateway::detect_default_gateway(),
};
if let Some(gw) = scanner.gateway_ip {
tracing::info!("Detected default gateway: {}", gw);
} else {
tracing::info!("No default gateway detected (may be expected in some environments)");
}
for local_ip in &scanner.local_ips {
let local_host = crate::host::Host {
ipv4: *local_ip,
mac: crate::types::MacAddr::default(),
hostname: None,
time: chrono::Local::now(),
speed: None,
};
if let Err(e) = scanner
.scanner_outputs
.try_send(Event::Scanner(ScannerEvent::HostFound(local_host)))
{
tracing::warn!(
"Failed to send HostFound event for local IP {}: {}",
local_ip,
e
);
}
}
let listener_handles = tasks::start_listening(
packet_source,
scanner.scanner_outputs.clone(),
scanner.local_ips.clone(),
scanner.discovered_hosts.clone(),
Arc::clone(&scanner.arp_validator),
scanner.gateway_ip,
scanner.cancel_token.clone(),
);
scanner.task_handles.extend(listener_handles);
let tx_handle = tasks::start_tx_worker(
scanner_input_rx,
packet_sink,
scanner.interface_name.clone(),
scanner.scanner_outputs.clone(),
Arc::clone(&scanner.arp_validator),
scanner.cancel_token.clone(),
);
scanner.task_handles.push(tx_handle);
Ok(scanner)
}
pub fn send_arp_packets(&self) {
if let Err(e) = self
.scanner_input_tx
.try_send(ScannerInputEvent::StartScanning)
{
tracing::error!("Failed to send StartScanning event: {}", e);
}
}
pub fn gateway_ip(&self) -> Option<Ipv4Addr> {
self.gateway_ip
}
pub fn local_ips(&self) -> &HashSet<Ipv4Addr> {
&self.local_ips
}
pub fn remove_discovered_hosts(&self, ips: &[Ipv4Addr]) {
let mut discovered =
recover_or_log(self.discovered_hosts.lock(), "remove_discovered_hosts");
for ip in ips {
discovered.remove(ip);
}
}
}
impl Drop for Scanner {
fn drop(&mut self) {
tracing::debug!("Scanner::drop() called, cancelling background tasks");
self.cancel_token.cancel();
for handle in self.task_handles.drain(..) {
handle.abort();
}
}
}