r_lanlib/
scanners.rs

1//! Provides data structure an implementations for performing network scanning
2//!
3//! This includes:
4//! - ARP Scanning
5//! - SYN Scanning
6//! - Full Scanning (ARP + SYN)
7
8use itertools::Itertools;
9#[cfg(test)]
10use mockall::{automock, predicate::*};
11
12use pnet::util::MacAddr;
13use serde;
14use serde::{Deserialize, Serialize};
15use std::collections::HashSet;
16use std::fmt::Display;
17use std::hash::Hash;
18use std::net::Ipv4Addr;
19use std::thread::JoinHandle;
20
21use crate::error::Result;
22
23/// The default idle timeout for a scanner
24pub const IDLE_TIMEOUT: u16 = 10000;
25
26#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
27/// Data structure representing a port
28pub struct Port {
29    /// The ID of the port i.e. 22, 80, 443 etc.
30    pub id: u16,
31    /// The associated service name for the port if known
32    pub service: String,
33}
34
35impl Display for Port {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        if self.service.is_empty() {
38            write!(f, "{}", self.id)
39        } else {
40            write!(f, "{}:{}", self.id, self.service)
41        }
42    }
43}
44
45impl PartialEq for Port {
46    fn eq(&self, other: &Self) -> bool {
47        self.id == other.id
48    }
49}
50
51impl Hash for Port {
52    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53        self.id.hash(state);
54    }
55}
56
57impl Ord for Port {
58    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
59        self.id.cmp(&other.id)
60    }
61}
62
63impl PartialOrd for Port {
64    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
65        Some(self.cmp(other))
66    }
67}
68
69/// Wrapper around HashSet<Port> providing a convenience method for
70/// converting to a Vec of sorted ports
71#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
72pub struct PortSet(pub HashSet<Port>);
73
74impl PortSet {
75    /// Returns a new instance of PortSet
76    pub fn new() -> Self {
77        Self(HashSet::new())
78    }
79
80    /// Returns a sorted Vec of [`Port`]
81    pub fn to_sorted_vec(&self) -> Vec<Port> {
82        self.0.iter().cloned().sorted().collect()
83    }
84}
85
86impl From<HashSet<Port>> for PortSet {
87    fn from(value: HashSet<Port>) -> Self {
88        Self(value)
89    }
90}
91
92fn serialize_to_string<S, T>(val: &T, s: S) -> std::result::Result<S::Ok, S::Error>
93where
94    S: serde::Serializer,
95    T: std::fmt::Display,
96{
97    s.serialize_str(&val.to_string())
98}
99
100fn deserialize_from_str<'de, D, T>(d: D) -> std::result::Result<T, D::Error>
101where
102    D: serde::Deserializer<'de>,
103    T: std::str::FromStr,
104    T::Err: std::fmt::Display,
105{
106    let s = String::deserialize(d)?;
107    s.parse::<T>().map_err(serde::de::Error::custom)
108}
109
110// ARP Result from a single device
111#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
112/// Data structure representing a device on the network
113pub struct Device {
114    /// Hostname of the device
115    pub hostname: String,
116    /// IPv4 of the device
117    pub ip: Ipv4Addr,
118    /// MAC address of the device
119    #[serde(
120        serialize_with = "serialize_to_string",
121        deserialize_with = "deserialize_from_str"
122    )]
123    pub mac: MacAddr,
124    /// Vendor of the device if known
125    pub vendor: String,
126    /// Whether or not the device is the current host running the scan
127    pub is_current_host: bool,
128    /// A HashSet of open ports for this device
129    pub open_ports: PortSet,
130}
131
132impl PartialEq for Device {
133    fn eq(&self, other: &Self) -> bool {
134        self.ip == other.ip && self.mac == other.mac
135    }
136}
137
138impl Hash for Device {
139    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
140        self.ip.hash(state);
141        self.mac.hash(state);
142    }
143}
144
145impl Ord for Device {
146    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
147        self.ip.cmp(&other.ip)
148    }
149}
150
151impl PartialOrd for Device {
152    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
153        Some(self.cmp(other))
154    }
155}
156
157#[derive(Debug)]
158/// Data structure representing a message that a device is being scanned
159pub struct Scanning {
160    /// IPv4 of the device
161    pub ip: Ipv4Addr,
162    /// Port being scanned
163    pub port: Option<u16>,
164}
165
166#[derive(Debug)]
167/// Generic enum representing the various kinds of scanning messages over the
168/// mcsp channel
169pub enum ScanMessage {
170    /// Indicates that scanning has completed
171    Done,
172    /// Send to inform that a device is about to be scanned
173    Info(Scanning),
174    /// Sent whenever an ARP response is received from a device
175    ARPScanDevice(Device),
176    /// Sent whenever a SYN response is received from a device
177    SYNScanDevice(Device),
178}
179
180#[cfg_attr(test, automock)]
181/// Trait used by all scanners
182pub trait Scanner: Sync + Send {
183    /// Performs network scanning
184    fn scan(&self) -> JoinHandle<Result<()>>;
185}
186
187pub mod arp_scanner;
188pub mod full_scanner;
189mod heartbeat;
190pub mod syn_scanner;