r_lanlib/scanners/
syn_scanner.rs

1//! Provides Scanner implementation for SYN scanning
2
3use log::*;
4use pnet::packet::{Packet, ethernet, ip, ipv4, tcp};
5use std::{
6    collections::HashMap,
7    net::Ipv4Addr,
8    sync::{self, Arc, LazyLock, Mutex, mpsc},
9    thread::{self, JoinHandle},
10    time::Duration,
11};
12
13use crate::{
14    error::{RLanLibError, Result},
15    network::NetworkInterface,
16    packet::{self, Reader, Sender, rst_packet::RstPacketBuilder, syn_packet::SynPacketBuilder},
17    scanners::{PortSet, Scanning, heartbeat::HeartBeat},
18    targets::ports::PortTargets,
19};
20
21use super::{Device, Port, ScanMessage, Scanner};
22
23static SERVICES: LazyLock<HashMap<u16, &str>> = LazyLock::new(|| {
24    HashMap::from([
25        (20, "ftp-data"),
26        (21, "ftp"),
27        (22, "ssh"),
28        (23, "telnet"),
29        (25, "smtp"),
30        (53, "dns"),
31        (80, "http"),
32        (110, "pop3"),
33        (143, "imap"),
34        (443, "https"),
35        (445, "microsoft-ds"),
36        (587, "submission"),
37        (993, "imaps"),
38        (995, "pop3s"),
39        (1433, "mssql"),
40        (3306, "mysql"),
41        (3389, "rdp"),
42        (5432, "postgresql"),
43        (5900, "vnc"),
44        (6379, "redis"),
45        (8080, "http-alt"),
46        (8443, "https-alt"),
47        (27017, "mongodb"),
48    ])
49});
50
51/// Data structure representing an ARP scanner
52pub struct SYNScanner<'net> {
53    interface: &'net NetworkInterface,
54    packet_reader: Arc<Mutex<dyn Reader>>,
55    packet_sender: Arc<Mutex<dyn Sender>>,
56    targets: Vec<Device>,
57    ports: Arc<PortTargets>,
58    source_port: u16,
59    idle_timeout: Duration,
60    notifier: mpsc::Sender<ScanMessage>,
61}
62
63/// Data structure holding parameters needed to create instance of SYNScanner
64pub struct SYNScannerArgs<'net> {
65    /// The network interface to use when scanning
66    pub interface: &'net NetworkInterface,
67    /// A packet Reader implementation (can use default provided in packet
68    /// crate)
69    pub packet_reader: Arc<Mutex<dyn Reader>>,
70    /// A packet Sender implementation (can use default provided in packet
71    /// crate)
72    pub packet_sender: Arc<Mutex<dyn Sender>>,
73    /// [`Device`] list to scan
74    pub targets: Vec<Device>,
75    /// [`PortTargets`] to scan for each detected device
76    pub ports: Arc<PortTargets>,
77    /// An open source port to listen for incoming packets (can use network
78    /// packet to find open port)
79    pub source_port: u16,
80    /// The amount of time to wait for incoming packets after scanning all
81    /// targets
82    pub idle_timeout: Duration,
83    /// Channel to send messages regarding devices being scanned, and detected
84    /// devices
85    pub notifier: mpsc::Sender<ScanMessage>,
86}
87
88impl<'net> SYNScanner<'net> {
89    /// Returns a new instance of SYNScanner using provided info
90    pub fn new(args: SYNScannerArgs<'net>) -> Self {
91        Self {
92            interface: args.interface,
93            packet_reader: args.packet_reader,
94            packet_sender: args.packet_sender,
95            targets: args.targets,
96            ports: args.ports,
97            source_port: args.source_port,
98            idle_timeout: args.idle_timeout,
99            notifier: args.notifier,
100        }
101    }
102}
103
104impl SYNScanner<'_> {
105    // Implements packet reading in a separate thread so we can send and
106    // receive packets simultaneously
107    fn read_packets(&self, done_rx: mpsc::Receiver<()>) -> JoinHandle<Result<()>> {
108        let packet_reader = Arc::clone(&self.packet_reader);
109        let heartbeat_packet_sender = Arc::clone(&self.packet_sender);
110        let rst_packet_sender = Arc::clone(&self.packet_sender);
111        // Build a HashMap for O(1) device lookups instead of O(n) linear search
112        let device_map: HashMap<Ipv4Addr, Device> =
113            self.targets.iter().map(|d| (d.ip, d.clone())).collect();
114        let notifier = self.notifier.clone();
115        let source_ipv4 = self.interface.ipv4;
116        let source_mac = self.interface.mac;
117        let source_port = self.source_port;
118        let (heartbeat_tx, heartbeat_rx) = sync::mpsc::channel::<()>();
119
120        // since reading packets off the wire is a blocking operation, we
121        // won't be able to detect a "done" signal if no packets are being
122        // received as we'll be blocked on waiting for one to come it. To fix
123        // this we send periodic "heartbeat" packets so we can continue to
124        // check for "done" signals
125        thread::spawn(move || {
126            debug!("starting syn heartbeat thread");
127            let heartbeat = HeartBeat::new(
128                source_mac,
129                source_ipv4,
130                source_port,
131                heartbeat_packet_sender,
132            );
133            let interval = Duration::from_secs(1);
134            loop {
135                if heartbeat_rx.try_recv().is_ok() {
136                    debug!("stopping syn heartbeat");
137                    break;
138                }
139                debug!("sending syn heartbeat");
140                heartbeat.beat();
141                thread::sleep(interval);
142            }
143        });
144
145        thread::spawn(move || -> Result<()> {
146            let mut reader = packet_reader.lock()?;
147
148            loop {
149                if done_rx.try_recv().is_ok() {
150                    debug!("exiting syn packet reader");
151                    if let Err(e) = heartbeat_tx.send(()) {
152                        error!("failed to stop heartbeat: {}", e);
153                    }
154
155                    break;
156                }
157
158                let pkt = reader.next_packet()?;
159
160                let Some(eth) = ethernet::EthernetPacket::new(pkt) else {
161                    continue;
162                };
163
164                let Some(header) = ipv4::Ipv4Packet::new(eth.payload()) else {
165                    continue;
166                };
167
168                let device_ip = header.get_source();
169                let protocol = header.get_next_level_protocol();
170                let payload = header.payload();
171
172                if protocol != ip::IpNextHeaderProtocols::Tcp {
173                    continue;
174                }
175
176                let Some(tcp_packet) = tcp::TcpPacket::new(payload) else {
177                    continue;
178                };
179
180                let destination_port = tcp_packet.get_destination();
181                let matches_destination = destination_port == source_port;
182                let flags: u8 = tcp_packet.get_flags();
183                let sequence = tcp_packet.get_sequence();
184                let is_syn_ack = flags == tcp::TcpFlags::SYN + tcp::TcpFlags::ACK;
185
186                if !matches_destination || !is_syn_ack {
187                    continue;
188                }
189
190                let Some(device) = device_map.get(&device_ip) else {
191                    continue;
192                };
193
194                let port = tcp_packet.get_source();
195
196                // send rst packet to prevent SYN Flooding
197                // https://en.wikipedia.org/wiki/SYN_flood
198                // https://security.stackexchange.com/questions/128196/whats-the-advantage-of-sending-an-rst-packet-after-getting-a-response-in-a-syn
199                let dest_ipv4 = device.ip;
200                let dest_mac = device.mac;
201
202                let rst_packet = RstPacketBuilder::default()
203                    .source_ip(source_ipv4)
204                    .source_mac(source_mac)
205                    .source_port(source_port)
206                    .dest_ip(dest_ipv4)
207                    .dest_mac(dest_mac)
208                    .dest_port(port)
209                    .sequence_number(sequence + 1)
210                    .build()?;
211
212                let rst_packet = rst_packet.to_raw();
213
214                let mut rst_sender = rst_packet_sender.lock()?;
215
216                debug!("sending RST packet to {}:{}", device.ip, port);
217
218                rst_sender.send(&rst_packet)?;
219
220                let service = SERVICES
221                    .get(&port)
222                    .map(|s| s.to_string())
223                    .unwrap_or_default();
224
225                let mut ports = PortSet::new();
226                ports.0.insert(Port { id: port, service });
227
228                notifier
229                    .send(ScanMessage::SYNScanDevice(Device {
230                        open_ports: ports,
231                        ..device.clone()
232                    }))
233                    .map_err(RLanLibError::from_channel_send_error)?;
234            }
235
236            Ok(())
237        })
238    }
239}
240
241// Implements the Scanner trait for SYNScanner
242impl Scanner for SYNScanner<'_> {
243    fn scan(&self) -> JoinHandle<Result<()>> {
244        debug!("performing SYN scan on targets: {:?}", self.targets);
245
246        debug!("starting syn packet reader");
247
248        let (done_tx, done_rx) = mpsc::channel::<()>();
249        let notifier = self.notifier.clone();
250        let packet_sender = Arc::clone(&self.packet_sender);
251        let targets = self.targets.clone();
252        let interface = self.interface;
253        let source_ipv4 = interface.ipv4;
254        let source_mac = self.interface.mac;
255        let ports = Arc::clone(&self.ports);
256        let idle_timeout = self.idle_timeout;
257        let source_port = self.source_port;
258
259        let read_handle = self.read_packets(done_rx);
260
261        // prevent blocking thread so messages can be freely sent to consumer
262        thread::spawn(move || -> Result<()> {
263            let mut scan_error: Option<RLanLibError> = None;
264
265            let process_port = |port: u16| -> Result<()> {
266                for device in targets.iter() {
267                    // throttle packet sending to prevent packet loss
268                    thread::sleep(packet::DEFAULT_PACKET_SEND_TIMING);
269
270                    debug!("scanning SYN target: {}:{}", device.ip, port);
271
272                    let dest_ipv4 = device.ip;
273                    let dest_mac = device.mac;
274
275                    let syn_packet = SynPacketBuilder::default()
276                        .source_ip(source_ipv4)
277                        .source_mac(source_mac)
278                        .source_port(source_port)
279                        .dest_ip(dest_ipv4)
280                        .dest_mac(dest_mac)
281                        .dest_port(port)
282                        .build()?;
283
284                    let pkt_buf = syn_packet.to_raw();
285
286                    // send info message to consumer
287                    notifier
288                        .send(ScanMessage::Info(Scanning {
289                            ip: device.ip,
290                            port: Some(port),
291                        }))
292                        .map_err(RLanLibError::from_channel_send_error)?;
293
294                    let mut sender = packet_sender.lock()?;
295
296                    // scan device @ port
297                    sender.send(&pkt_buf).map_err(|e| RLanLibError::Scan {
298                        ip: Some(device.ip.to_string()),
299                        port: Some(port.to_string()),
300                        error: e.to_string(),
301                    })?;
302                }
303
304                Ok(())
305            };
306
307            if let Err(err) = ports.lazy_loop(process_port) {
308                scan_error = Some(err);
309            }
310
311            thread::sleep(idle_timeout);
312
313            notifier
314                .send(ScanMessage::Done)
315                .map_err(RLanLibError::from_channel_send_error)?;
316
317            // ignore errors here as the thread may already be dead due to error
318            // we'll catch any errors from that thread below and report
319            let _ = done_tx.send(());
320
321            let read_result = read_handle.join()?;
322
323            if let Some(err) = scan_error {
324                return Err(err);
325            }
326
327            read_result
328        })
329    }
330}
331
332#[cfg(test)]
333#[path = "./syn_scanner_tests.rs"]
334mod tests;