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