shark_scan/
scanner.rs

1use crate::parser::{parse_ports, Args};
2use log::{error, info};
3use serde::{Deserialize, Serialize};
4use std::io;
5use std::net::{SocketAddr, ToSocketAddrs};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use threadpool::ThreadPool;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10use tokio::net::TcpStream;
11use tokio::sync::Mutex as AsyncMutex;
12use tokio::time::timeout;
13
14/// A struct to contain scan results for a given port:
15///
16/// `status` will be set to open if a connection succeeds
17///
18/// If the --probe flag is used, `banner` will contain the first 1024 bytes
19/// returned by the service on that port, if it supports HTTP
20#[derive(Serialize, Deserialize)]
21pub struct ScanResult {
22    port: u16,
23    status: String,
24    banner: Option<String>,
25}
26
27async fn probe(target: &str, port: u16, timeout_ms: u64) -> Option<String> {
28    let address = format!("{}:{}", target, port);
29    let socket_addr: SocketAddr = address.to_socket_addrs().ok()?.next()?;
30
31    info!("Attempting to connect to {}", address);
32
33    match timeout(
34        Duration::from_millis(timeout_ms),
35        TcpStream::connect(&socket_addr),
36    )
37    .await
38    {
39        Ok(Ok(mut stream)) => {
40            info!("Connected to {}", address);
41
42            let http_request = format!(
43                "GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
44                target
45            );
46            match stream.write_all(http_request.as_bytes()).await {
47                Ok(_) => info!("Sent HTTP GET request to {}", address),
48                Err(e) => {
49                    error!("Failed to send HTTP GET request to {}: {:?}", address, e);
50                    return None;
51                }
52            }
53
54            let mut banner = vec![0; 1024];
55
56            // Wait one full second to read response from server
57            match timeout(Duration::from_secs(1), stream.read(&mut banner)).await {
58                Ok(Ok(n)) if n > 0 => {
59                    info!("Read {} bytes from {}", n, address);
60                    return Some(String::from_utf8_lossy(&banner[..n]).to_string());
61                }
62                Ok(Ok(_)) => {
63                    error!("No data read from {}", address);
64                }
65                Ok(Err(e)) => {
66                    error!("Failed to read from {}: {:?}", address, e);
67                }
68                Err(_) => {
69                    error!("Read operation timed out for {}", address);
70                }
71            }
72        }
73        Ok(Err(e)) => {
74            error!("Failed to connect to {}: {:?}", address, e);
75        }
76        Err(_) => {
77            error!("Connection attempt timed out for {}", address);
78        }
79    }
80
81    None
82}
83
84async fn check_port(
85    target: Arc<String>,
86    port: u16,
87    timeout_ms: u64,
88    do_probe: bool,
89    results: Arc<AsyncMutex<Vec<ScanResult>>>,
90) {
91    let address = format!("{}:{}", target, port);
92    let socket_addr: SocketAddr = match address.to_socket_addrs() {
93        Ok(mut addrs) => match addrs.next() {
94            Some(addr) => addr,
95            None => {
96                error!("Could not resolve address: {}", address);
97                return;
98            }
99        },
100        Err(e) => {
101            error!("Failed to resolve address {}: {:?}", address, e);
102            return;
103        }
104    };
105
106    match timeout(
107        Duration::from_secs(timeout_ms),
108        TcpStream::connect(&socket_addr),
109    )
110    .await
111    {
112        Ok(Ok(_)) => {
113            if do_probe {
114                let banner = probe(&target, port, timeout_ms).await;
115                let mut results = results.lock().await;
116                results.push(ScanResult {
117                    port,
118                    status: "open".to_string(),
119                    banner,
120                });
121            } else {
122                let mut results = results.lock().await;
123                results.push(ScanResult {
124                    port,
125                    status: "open".to_string(),
126                    banner: None,
127                });
128            }
129        }
130        Ok(Err(e)) => {
131            let status = match e.kind() {
132                io::ErrorKind::ConnectionRefused => "refused",
133                _ => "failed",
134            };
135            info!("Port {} {}", port, status);
136        }
137        Err(_) => {
138            info!("Port {} timed out", port);
139        }
140    }
141}
142
143pub async fn scan(args: Args) {
144    let ports = parse_ports(&args.port_range);
145    let target = Arc::new(args.target.trim().to_string());
146
147    println!("{}", "*".repeat(40));
148    println!("* Scanning: {} *", target);
149    println!("{}", "*".repeat(40));
150
151    let start = Instant::now();
152
153    let pool = ThreadPool::new(args.threads);
154    let results = Arc::new(AsyncMutex::new(Vec::new()));
155
156    for port in ports {
157        let results = Arc::clone(&results);
158        let target = Arc::clone(&target);
159        let timeout = args.timeout;
160        let probe = args.probe;
161        pool.execute(move || {
162            let rt = tokio::runtime::Runtime::new().unwrap();
163            rt.block_on(async move {
164                check_port(target, port, timeout, probe, results).await;
165            });
166        });
167    }
168
169    pool.join();
170
171    let end = Instant::now();
172    let duration = end.duration_since(start);
173
174    let results = results.lock();
175
176    println!();
177    for result in results.await.iter() {
178        println!(
179            "Port {} {}{}",
180            result.port,
181            result.status,
182            result
183                .banner
184                .as_ref()
185                .map(|b| format!(" - {}", b))
186                .unwrap_or_default()
187        );
188    }
189
190    println!(
191        "\nScanning completed in {:.2} seconds",
192        duration.as_secs_f64()
193    );
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[tokio::test]
201    async fn test_open_port() {
202        // Assuming the local network has port 80 open
203        let target = Arc::new("192.168.1.1".to_string());
204        let port = 80;
205        let results = Arc::new(AsyncMutex::new(Vec::new()));
206        let results_clone = Arc::clone(&results);
207
208        check_port(target, port, 100, false, results_clone).await;
209
210        let results = results.lock().await;
211        assert_eq!(results.len(), 1);
212        assert_eq!(results[0].port, port);
213        assert_eq!(results[0].status, "open");
214    }
215
216    #[tokio::test]
217    async fn test_closed_port() {
218        // Assuming the local network has port 90 closed
219        let target = Arc::new("192.168.1.1".to_string());
220        let port = 90;
221        let results = Arc::new(AsyncMutex::new(Vec::new()));
222        let results_clone = Arc::clone(&results);
223
224        check_port(target, port, 100, false, results_clone).await;
225
226        let results = results.lock().await;
227        assert_eq!(results.len(), 0);
228    }
229}