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