Skip to main content

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