1use std::net::{Ipv4Addr, SocketAddr, TcpStream};
4use std::sync::Mutex;
5use std::time::Duration;
6
7pub fn scan_port(ips: &[Ipv4Addr], port: u16, timeout: Duration, workers: usize) -> Vec<Ipv4Addr> {
11 if ips.is_empty() {
12 return Vec::new();
13 }
14 let workers = workers.clamp(1, ips.len());
15 let chunk = ips.len().div_ceil(workers);
16 let found = Mutex::new(Vec::new());
17 std::thread::scope(|s| {
18 for slice in ips.chunks(chunk) {
19 let found = &found;
20 s.spawn(move || {
21 for &ip in slice {
22 let addr = SocketAddr::from((ip, port));
23 if TcpStream::connect_timeout(&addr, timeout).is_ok() {
24 found.lock().unwrap().push(ip);
25 }
26 }
27 });
28 }
29 });
30 let mut v = found.into_inner().unwrap();
31 v.sort();
32 v
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use std::net::{Ipv4Addr, TcpListener};
39 use std::time::Duration;
40
41 #[test]
42 fn finds_a_listening_port_and_skips_closed() {
43 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
44 let open = listener.local_addr().unwrap().port();
45 let ips = [Ipv4Addr::LOCALHOST];
46 let found = scan_port(&ips, open, Duration::from_millis(300), 4);
47 assert_eq!(found, vec![Ipv4Addr::LOCALHOST]);
48
49 let tmp = TcpListener::bind("127.0.0.1:0").unwrap();
51 let closed = tmp.local_addr().unwrap().port();
52 drop(tmp);
53 let none = scan_port(&ips, closed, Duration::from_millis(200), 4);
54 assert!(
55 none.is_empty(),
56 "closed port should not be reported: {none:?}"
57 );
58 }
59}