r_lanlib/scanners/
arp_scanner.rs

1//! Provides Scanner implementation for ARP scanning
2
3use log::*;
4use pnet::packet::{Packet, arp, ethernet};
5use std::{
6    io::Error as IOError,
7    net,
8    sync::{self, Arc, Mutex},
9    thread::{self, JoinHandle},
10    time::Duration,
11};
12
13use crate::{
14    network::NetworkInterface,
15    packet::{self, Reader, Sender, arp_packet},
16    scanners::{Device, ScanError, Scanning},
17    targets::ips::IPTargets,
18};
19
20use super::{ScanMessage, Scanner, heartbeat::HeartBeat};
21
22/// Data structure representing an ARP scanner
23pub struct ARPScanner<'net> {
24    interface: &'net NetworkInterface,
25    packet_reader: Arc<Mutex<dyn Reader>>,
26    packet_sender: Arc<Mutex<dyn Sender>>,
27    targets: Arc<IPTargets>,
28    source_port: u16,
29    include_vendor: bool,
30    include_host_names: bool,
31    idle_timeout: Duration,
32    notifier: sync::mpsc::Sender<ScanMessage>,
33}
34
35/// Data structure holding parameters needed to create instance of ARPScanner
36pub struct ARPScannerArgs<'net> {
37    /// The network interface to use when scanning
38    pub interface: &'net NetworkInterface,
39    /// A packet Reader implementation (can use default provided in packet
40    /// crate)
41    pub packet_reader: Arc<Mutex<dyn Reader>>,
42    /// A packet Sender implementation (can use default provided in packet
43    /// crate)
44    pub packet_sender: Arc<Mutex<dyn Sender>>,
45    /// [`IPTargets`] to scan
46    pub targets: Arc<IPTargets>,
47    /// An open source port to listen for incoming packets (can use network
48    /// packet to find open port)
49    pub source_port: u16,
50    /// Whether or not to include vendor look-ups for detected devices
51    pub include_vendor: bool,
52    /// Whether or not to include hostname look-ups for detected devices
53    pub include_host_names: bool,
54    /// The amount of time to wait for incoming packets after scanning all
55    /// targets
56    pub idle_timeout: Duration,
57    /// Channel to send messages regarding devices being scanned, and detected
58    /// devices
59    pub notifier: sync::mpsc::Sender<ScanMessage>,
60}
61
62impl<'net> ARPScanner<'net> {
63    /// Returns an instance of ARPScanner
64    pub fn new(args: ARPScannerArgs<'net>) -> Self {
65        Self {
66            interface: args.interface,
67            packet_reader: args.packet_reader,
68            packet_sender: args.packet_sender,
69            targets: args.targets,
70            source_port: args.source_port,
71            include_vendor: args.include_vendor,
72            include_host_names: args.include_host_names,
73            idle_timeout: args.idle_timeout,
74            notifier: args.notifier,
75        }
76    }
77}
78
79impl ARPScanner<'_> {
80    // Implements packet reading in a separate thread so we can send and
81    // receive packets simultaneously
82    fn read_packets(&self, done: sync::mpsc::Receiver<()>) -> JoinHandle<Result<(), ScanError>> {
83        let packet_reader = Arc::clone(&self.packet_reader);
84        let packet_sender = Arc::clone(&self.packet_sender);
85        let include_host_names = self.include_host_names;
86        let include_vendor = self.include_vendor;
87        let source_ipv4 = self.interface.ipv4;
88        let source_mac = self.interface.mac;
89        let source_port = self.source_port.to_owned();
90        let notifier = self.notifier.clone();
91        let (heartbeat_tx, heartbeat_rx) = sync::mpsc::channel::<()>();
92
93        // since reading packets off the wire is a blocking operation, we
94        // won't be able to detect a "done" signal if no packets are being
95        // received as we'll be blocked on waiting for one to come it. To fix
96        // this we send periodic "heartbeat" packets so we can continue to
97        // check for "done" signals
98        thread::spawn(move || {
99            debug!("starting arp heartbeat thread");
100            let heartbeat = HeartBeat::new(source_mac, source_ipv4, source_port, packet_sender);
101            let interval = Duration::from_secs(1);
102            loop {
103                if heartbeat_rx.try_recv().is_ok() {
104                    debug!("stopping arp heartbeat");
105                    break;
106                }
107                debug!("sending arp heartbeat");
108                heartbeat.beat();
109                thread::sleep(interval);
110            }
111        });
112
113        thread::spawn(move || -> Result<(), ScanError> {
114            let mut reader = packet_reader.lock().map_err(|e| ScanError {
115                ip: None,
116                port: None,
117                error: Box::from(e.to_string()),
118            })?;
119
120            loop {
121                if done.try_recv().is_ok() {
122                    debug!("exiting arp packet reader");
123                    if let Err(e) = heartbeat_tx.send(()) {
124                        error!("failed to stop heartbeat: {}", e);
125                    }
126                    break;
127                }
128
129                let pkt = reader.next_packet().map_err(|e| ScanError {
130                    ip: None,
131                    port: None,
132                    error: e,
133                })?;
134
135                let eth = ethernet::EthernetPacket::new(pkt);
136
137                if eth.is_none() {
138                    continue;
139                }
140
141                let eth = eth.unwrap();
142
143                let header = arp::ArpPacket::new(eth.payload());
144
145                if header.is_none() {
146                    continue;
147                }
148
149                let header = header.unwrap();
150
151                let op = header.get_operation();
152
153                // Capture ANY ARP reply as it's an indiction that there's a
154                // device on the network
155                let is_expected_arp_packet = op == arp::ArpOperations::Reply;
156
157                if !is_expected_arp_packet {
158                    continue;
159                }
160
161                let ip4 = header.get_sender_proto_addr();
162                let mac = eth.get_source().to_string();
163
164                let notification_sender = notifier.clone();
165
166                // use a separate thread here so we don't slow down packet
167                // processing
168                thread::spawn(move || {
169                    let mut hostname: String = String::from("");
170                    if include_host_names {
171                        debug!("looking up hostname for {}", ip4);
172                        if let Ok(host) = dns_lookup::lookup_addr(&ip4.into()) {
173                            hostname = host;
174                        }
175                    }
176
177                    let mut vendor = String::from("");
178                    if include_vendor && let Some(vendor_data) = oui_data::lookup(&mac) {
179                        vendor = vendor_data.organization().to_owned();
180                    }
181
182                    let _ = notification_sender.send(ScanMessage::ARPScanResult(Device {
183                        hostname,
184                        ip: ip4.to_string(),
185                        mac,
186                        vendor,
187                        is_current_host: ip4 == source_ipv4,
188                    }));
189                });
190            }
191
192            Ok(())
193        })
194    }
195}
196
197// Implements the Scanner trait for ARPScanner
198impl Scanner for ARPScanner<'_> {
199    fn scan(&self) -> JoinHandle<Result<(), ScanError>> {
200        debug!("performing ARP scan on targets: {:?}", self.targets);
201        debug!("include_vendor: {}", self.include_vendor);
202        debug!("include_host_names: {}", self.include_host_names);
203        debug!("starting arp packet reader");
204        let (done_tx, done_rx) = sync::mpsc::channel::<()>();
205        let notifier = self.notifier.clone();
206        let packet_sender = Arc::clone(&self.packet_sender);
207        let idle_timeout = self.idle_timeout;
208        let source_ipv4 = self.interface.ipv4;
209        let source_mac = self.interface.mac;
210        let targets = Arc::clone(&self.targets);
211
212        let read_handle = self.read_packets(done_rx);
213
214        // prevent blocking thread so messages can be freely sent to consumer
215        thread::spawn(move || -> Result<(), ScanError> {
216            let process_target = |target_ipv4: net::Ipv4Addr| {
217                // throttle packet sending to prevent packet loss
218                thread::sleep(packet::DEFAULT_PACKET_SEND_TIMING);
219
220                debug!("scanning ARP target: {}", target_ipv4);
221
222                let pkt_buf = arp_packet::build(source_ipv4, source_mac, target_ipv4);
223
224                // inform consumer we are scanning this target (ignore error on failure to notify)
225                notifier
226                    .send(ScanMessage::Info(Scanning {
227                        ip: target_ipv4.to_string(),
228                        port: None,
229                    }))
230                    .map_err(|e| ScanError {
231                        ip: Some(target_ipv4.to_string()),
232                        port: None,
233                        error: Box::from(e),
234                    })?;
235
236                let mut pkt_sender = packet_sender.lock().map_err(|e| ScanError {
237                    ip: Some(target_ipv4.to_string()),
238                    port: None,
239                    error: Box::from(IOError::other(e.to_string())),
240                })?;
241
242                // Send to the broadcast address
243                pkt_sender.send(&pkt_buf).map_err(|e| ScanError {
244                    ip: Some(target_ipv4.to_string()),
245                    port: None,
246                    error: e,
247                })?;
248
249                Ok(())
250            };
251
252            let mut scan_error: Option<ScanError> = None;
253
254            if let Err(err) = targets.lazy_loop(process_target) {
255                scan_error = Some(err);
256            }
257
258            thread::sleep(idle_timeout);
259
260            notifier.send(ScanMessage::Done).map_err(|e| ScanError {
261                ip: None,
262                port: None,
263                error: Box::from(e),
264            })?;
265
266            // ignore errors here as the thread may already be dead due to error
267            // we'll catch any errors from that thread below and report
268            let _ = done_tx.send(());
269
270            let read_result = read_handle.join().map_err(|_| ScanError {
271                ip: None,
272                port: None,
273                error: Box::from("error encountered in arp packet reading thread"),
274            })?;
275
276            if let Some(err) = scan_error {
277                return Err(err);
278            }
279
280            read_result
281        })
282    }
283}
284
285#[cfg(test)]
286#[path = "./arp_scanner_tests.rs"]
287mod tests;