Skip to main content

r_lanlib/scanners/
syn_scanner.rs

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