use indicatif::{ProgressBar, ProgressStyle};
use ipnetwork::IpNetwork;
use rayon::prelude::*;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub fn scan_address(ip: Ipv4Addr, timeout: Option<Duration>) -> Option<Ipv4Addr> {
match TcpStream::connect_timeout(
&SocketAddr::new(IpAddr::V4(ip), 80),
timeout.unwrap_or(crate::scanner::port::CONNECT_TIMEOUT),
) {
Ok(_) => Some(ip),
Err(_) => None,
}
}
pub fn scan_subnet(subnet: IpNetwork) -> Vec<Ipv4Addr> {
let network = subnet.network();
let broadcast = subnet.broadcast();
let mut available = Vec::new();
if let (IpAddr::V4(network), IpAddr::V4(broadcast)) = (network, broadcast) {
let total_hosts = u32::from(broadcast) - u32::from(network) + 1;
let pb = ProgressBar::new(total_hosts as u64);
pb.set_style(
ProgressStyle::with_template(
"[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len} addresses scanned",
)
.unwrap()
.progress_chars("=> "),
);
let available_ips = Arc::new(Mutex::new(Vec::new()));
(u32::from(network)..=u32::from(broadcast))
.into_par_iter()
.for_each(|ip| {
let ipv4 = Ipv4Addr::from(ip);
if let Some(available_ip) = scan_address(ipv4, None)
&& let Ok(mut guard) = available_ips.lock()
{
guard.push(available_ip);
}
pb.inc(1);
});
pb.finish_with_message("Subnet scan completed");
let mut result = available_ips.lock().unwrap();
result.sort();
available = result.clone();
}
available
}
pub fn scan_ip_range(start: Ipv4Addr, end: Ipv4Addr) -> Vec<Ipv4Addr> {
let start_num = u32::from(start);
let end_num = u32::from(end);
if start_num > end_num {
return Vec::new();
}
let total_hosts = end_num - start_num + 1;
let pb = ProgressBar::new(total_hosts as u64);
pb.set_style(
ProgressStyle::with_template(
"[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len} addresses scanned",
)
.unwrap()
.progress_chars("=> "),
);
let available_ips = Arc::new(Mutex::new(Vec::new()));
(start_num..=end_num).into_par_iter().for_each(|ip| {
let ipv4 = Ipv4Addr::from(ip);
if let Some(available_ip) = scan_address(ipv4, None)
&& let Ok(mut guard) = available_ips.lock()
{
guard.push(available_ip);
}
pb.inc(1);
});
pb.finish_with_message("Range scan completed");
let mut result = available_ips.lock().unwrap();
result.sort();
result.clone()
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn is_localhost_available() -> bool {
scan_address(
"127.0.0.1".parse().unwrap(),
Some(Duration::from_millis(100)),
)
.is_some()
}
#[test]
fn test_scan_address_localhost() {
if !is_localhost_available() {
println!("Skipping test_scan_address_localhost: localhost is not available");
return;
}
let ip = "127.0.0.1".parse::<Ipv4Addr>().unwrap();
assert!(scan_address(ip, Some(Duration::from_millis(100))).is_some());
}
#[test]
fn test_scan_address_unavailable() {
let ip = "192.168.255.255".parse::<Ipv4Addr>().unwrap();
assert!(scan_address(ip, Some(Duration::from_millis(100))).is_none());
}
#[test]
fn test_scan_subnet() {
if !is_localhost_available() {
println!("Skipping test_scan_subnet: localhost is not available");
return;
}
let subnet = "127.0.0.0/24".parse::<IpNetwork>().unwrap();
let results = scan_subnet(subnet);
assert!(results.contains(&"127.0.0.1".parse::<Ipv4Addr>().unwrap()));
assert!(results.windows(2).all(|w| w[0] <= w[1]));
}
#[test]
fn test_scan_ip_range() {
if !is_localhost_available() {
println!("Skipping test_scan_ip_range: localhost is not available");
return;
}
let start = "127.0.0.1".parse::<Ipv4Addr>().unwrap();
let end = "127.0.0.3".parse::<Ipv4Addr>().unwrap();
let results = scan_ip_range(start, end);
assert!(results.contains(&"127.0.0.1".parse::<Ipv4Addr>().unwrap()));
assert!(results.windows(2).all(|w| w[0] <= w[1]));
}
#[test]
fn test_scan_empty_range() {
let start = "127.0.0.10".parse::<Ipv4Addr>().unwrap();
let end = "127.0.0.1".parse::<Ipv4Addr>().unwrap();
let results = scan_ip_range(start, end);
assert!(results.is_empty());
}
}