diff --git a/benches/benchmark_portscan.rs b/benches/benchmark_portscan.rs index 1206fa08e..89f082683 100644 --- a/benches/benchmark_portscan.rs +++ b/benches/benchmark_portscan.rs @@ -45,6 +45,7 @@ fn criterion_benchmark(c: &mut Criterion) { true, vec![], false, + false, ); c.bench_function("portscan tcp", |b| { @@ -61,6 +62,7 @@ fn criterion_benchmark(c: &mut Criterion) { true, vec![], true, + false, ); let mut udp_group = c.benchmark_group("portscan udp"); diff --git a/src/input.rs b/src/input.rs index 2536e8b36..77868423b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -162,6 +162,10 @@ pub struct Opts { /// UDP scanning mode, finds UDP ports that send back responses #[arg(long)] pub udp: bool, + + /// Also show closed Ports + #[arg(long)] + pub closed: bool, } #[cfg(not(tarpaulin_include))] @@ -201,7 +205,7 @@ impl Opts { merge_required!( addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts, - command, udp + command, udp, closed ); } @@ -252,6 +256,7 @@ impl Default for Opts { exclude_ports: None, exclude_addresses: None, udp: false, + closed: false, } } } @@ -278,6 +283,7 @@ pub struct Config { exclude_ports: Option>, exclude_addresses: Option>, udp: Option, + closed: Option, } #[cfg(not(tarpaulin_include))] @@ -295,6 +301,7 @@ impl Config { /// scan_order = "Serial" /// exclude_ports = [8080, 9090, 80] /// udp = false + /// closed = false /// pub fn read(custom_config_path: Option) -> Self { let mut content = String::new(); @@ -353,6 +360,7 @@ mod tests { exclude_ports: None, exclude_addresses: None, udp: Some(false), + closed: Some(false), } } } diff --git a/src/lib.rs b/src/lib.rs index e335de58a..b01c35a7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ //! true, // accessible, should the output be A11Y compliant? //! vec![9000], // What ports should RustScan exclude? //! false, // is this a UDP scan? +//! false, // Output closed ports? //! ); //! //! let scan_result = block_on(scanner.run()); diff --git a/src/main.rs b/src/main.rs index 52a998d74..6c52d3d35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use std::string::ToString; use std::time::Duration; use rustscan::address::parse_addresses; +use rustscan::scanner::PortStatus; extern crate colorful; extern crate dirs; @@ -89,10 +90,11 @@ fn main() { Duration::from_millis(opts.timeout.into()), opts.tries, opts.greppable, - PortStrategy::pick(&opts.range, opts.ports, opts.scan_order), + PortStrategy::pick(&opts.range, opts.clone().ports, opts.scan_order), opts.accessible, - opts.exclude_ports.unwrap_or_default(), + opts.exclude_ports.clone().unwrap_or_default(), opts.udp, + opts.closed, ); debug!("Scanner finished building: {:?}", scanner); @@ -101,17 +103,28 @@ fn main() { portscan_bench.end(); benchmarks.push(portscan_bench); - let mut ports_per_ip = HashMap::new(); + let mut open_ports_per_ip = HashMap::new(); + let mut closed_ports_per_ip = HashMap::new(); for socket in scan_result { - ports_per_ip - .entry(socket.ip()) - .or_insert_with(Vec::new) - .push(socket.port()); + match socket { + PortStatus::Open(socket) => { + open_ports_per_ip + .entry(socket.ip()) + .or_insert_with(Vec::new) + .push(socket.port()); + } + PortStatus::Closed(socket) => { + closed_ports_per_ip + .entry(socket.ip()) + .or_insert_with(Vec::new) + .push(socket.port()); + } + } } for ip in ips { - if ports_per_ip.contains_key(&ip) { + if open_ports_per_ip.contains_key(&ip) || closed_ports_per_ip.contains_key(&ip) { continue; } @@ -128,7 +141,29 @@ fn main() { } let mut script_bench = NamedTimer::start("Scripts"); - for (ip, ports) in &ports_per_ip { + + print_summary(&open_ports_per_ip, &scripts_to_run, &opts); + // We only print closed ports if the user requested it. + if opts.closed { + println!("closed ports:"); + print_summary(&closed_ports_per_ip, &scripts_to_run, &opts); + } + + // To use the runtime benchmark, run the process as: RUST_LOG=info ./rustscan + script_bench.end(); + benchmarks.push(script_bench); + rustscan_bench.end(); + benchmarks.push(rustscan_bench); + debug!("Benchmarks raw {:?}", benchmarks); + info!("{}", benchmarks.summary()); +} + +fn print_summary( + ports_per_ip: &HashMap>, + scripts_to_run: &[ScriptFile], + opts: &Opts, +) { + for (ip, ports) in ports_per_ip { let vec_str_ports: Vec = ports.iter().map(ToString::to_string).collect(); // nmap port style is 80,443. Comma separated with no spaces. @@ -142,7 +177,7 @@ fn main() { detail!("Starting Script(s)", opts.greppable, opts.accessible); // Run all the scripts we found and parsed based on the script config file tags field. - for mut script_f in scripts_to_run.clone() { + for mut script_f in scripts_to_run.iter().cloned() { // This part allows us to add commandline arguments to the Script call_format, appending them to the end of the command. if !opts.command.is_empty() { let user_extra_args = &opts.command.join(" "); @@ -181,14 +216,6 @@ fn main() { } } } - - // To use the runtime benchmark, run the process as: RUST_LOG=info ./rustscan - script_bench.end(); - benchmarks.push(script_bench); - rustscan_bench.end(); - benchmarks.push(rustscan_bench); - debug!("Benchmarks raw {:?}", benchmarks); - info!("{}", benchmarks.summary()); } /// Prints the opening title of RustScan diff --git a/src/scanner/mod.rs b/src/scanner/mod.rs index 8d52fde7f..aff03d8f8 100644 --- a/src/scanner/mod.rs +++ b/src/scanner/mod.rs @@ -37,6 +37,13 @@ pub struct Scanner { accessible: bool, exclude_ports: Vec, udp: bool, + closed: bool, +} + +#[derive(Debug)] +pub enum PortStatus { + Open(SocketAddr), + Closed(SocketAddr), } // Allowing too many arguments for clippy. @@ -52,6 +59,7 @@ impl Scanner { accessible: bool, exclude_ports: Vec, udp: bool, + closed: bool, ) -> Self { Self { batch_size, @@ -63,13 +71,14 @@ impl Scanner { accessible, exclude_ports, udp, + closed, } } /// Runs scan_range with chunk sizes /// If you want to run RustScan normally, this is the entry point used /// Returns all open ports as `Vec` - pub async fn run(&self) -> Vec { + pub async fn run(&self) -> Vec { let ports: Vec = self .port_strategy .order() @@ -78,7 +87,7 @@ impl Scanner { .copied() .collect(); let mut socket_iterator: SocketIterator = SocketIterator::new(&self.ips, &ports); - let mut open_sockets: Vec = Vec::new(); + let mut found_sockets: Vec = Vec::new(); let mut ftrs = FuturesUnordered::new(); let mut errors: HashSet = HashSet::new(); let udp_map = get_parsed_data(); @@ -103,7 +112,7 @@ impl Scanner { } match result { - Ok(socket) => open_sockets.push(socket), + Ok(socket) => found_sockets.push(socket), Err(e) => { let error_string = e.to_string(); if errors.len() < self.ips.len() * 1000 { @@ -113,8 +122,8 @@ impl Scanner { } } debug!("Typical socket connection errors {:?}", errors); - debug!("Open Sockets found: {:?}", &open_sockets); - open_sockets + debug!("Open Sockets found: {:?}", &found_sockets); + found_sockets } /// Given a socket, scan it self.tries times. @@ -135,7 +144,7 @@ impl Scanner { &self, socket: SocketAddr, udp_map: BTreeMap, Vec>, - ) -> io::Result { + ) -> io::Result { if self.udp { return self.scan_udp_socket(socket, udp_map).await; } @@ -154,11 +163,23 @@ impl Scanner { self.fmt_ports(socket); debug!("Return Ok after {} tries", nr_try); - return Ok(socket); + return Ok(PortStatus::Open(socket)); } Err(e) => { let mut error_string = e.to_string(); + if e.kind() == io::ErrorKind::ConnectionRefused { + if !self.greppable && self.closed { + if self.accessible { + println!("Closed {socket}"); + } else { + println!("Closed {}", socket.to_string().red()); + } + } + + return Ok(PortStatus::Closed(socket)); + } + assert!(!error_string.to_lowercase().contains("too many open files"), "Too many open files. Please reduce batch size. The default is 5000. Try -b 2500."); if nr_try == tries { @@ -176,7 +197,7 @@ impl Scanner { &self, socket: SocketAddr, udp_map: BTreeMap, Vec>, - ) -> io::Result { + ) -> io::Result { let mut payload: Vec = Vec::new(); for (key, value) in udp_map { if key.contains(&socket.port()) { @@ -187,7 +208,7 @@ impl Scanner { let tries = self.tries.get(); for _ in 1..=tries { match self.udp_scan(socket, &payload, self.timeout).await { - Ok(true) => return Ok(socket), + Ok(true) => return Ok(PortStatus::Open(socket)), Ok(false) => continue, Err(e) => return Err(e), } @@ -333,6 +354,7 @@ mod tests { true, vec![9000], false, + false, ); block_on(scanner.run()); // if the scan fails, it wouldn't be able to assert_eq! as it panicked! @@ -357,6 +379,7 @@ mod tests { true, vec![9000], false, + false, ); block_on(scanner.run()); // if the scan fails, it wouldn't be able to assert_eq! as it panicked! @@ -380,6 +403,7 @@ mod tests { true, vec![9000], false, + false, ); block_on(scanner.run()); assert_eq!(1, 1); @@ -402,6 +426,7 @@ mod tests { true, vec![9000], false, + false, ); block_on(scanner.run()); assert_eq!(1, 1); @@ -427,6 +452,7 @@ mod tests { true, vec![9000], false, + false, ); block_on(scanner.run()); assert_eq!(1, 1); @@ -451,6 +477,7 @@ mod tests { true, vec![9000], true, + false, ); block_on(scanner.run()); // if the scan fails, it wouldn't be able to assert_eq! as it panicked! @@ -475,6 +502,7 @@ mod tests { true, vec![9000], true, + false, ); block_on(scanner.run()); // if the scan fails, it wouldn't be able to assert_eq! as it panicked! @@ -498,6 +526,7 @@ mod tests { true, vec![9000], true, + false, ); block_on(scanner.run()); assert_eq!(1, 1); @@ -520,6 +549,7 @@ mod tests { true, vec![9000], true, + false, ); block_on(scanner.run()); assert_eq!(1, 1);