Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ env_logger = "0.11.8"
anstream = "=0.6.20"
dirs = "6.0.0"
gcd = "2.0.1"
bit-set = "0.8"
rand = "0.9.2"
colorful = "0.3.2"
ansi_term = "0.12.1"
Expand Down
18 changes: 6 additions & 12 deletions benches/benchmark_portscan.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use async_std::task::block_on;
use criterion::{criterion_group, criterion_main, Criterion};
use rustscan::input::{Opts, PortRange, ScanOrder};
use rustscan::input::{Opts, PortRanges, ScanOrder};
use rustscan::port_strategy::PortStrategy;
use rustscan::scanner::Scanner;
use std::hint::black_box;
Expand All @@ -20,11 +20,8 @@ fn bench_address() {
}

fn bench_port_strategy() {
let range = PortRange {
start: 1,
end: 1_000,
};
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let range = PortRanges(vec![(1, 1_000)]);
let _strategy = PortStrategy::pick(Some(range.clone()), None, ScanOrder::Serial);
}

fn bench_address_parsing() {
Expand All @@ -47,12 +44,9 @@ fn bench_address_parsing() {

fn criterion_benchmark(c: &mut Criterion) {
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy_tcp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let strategy_udp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let range = PortRanges(vec![(1, 1_000)]);
let strategy_tcp = PortStrategy::pick(Some(range.clone()), None, ScanOrder::Serial);
let strategy_udp = PortStrategy::pick(Some(range.clone()), None, ScanOrder::Serial);

let scanner_tcp = Scanner::new(
&addrs,
Expand Down
85 changes: 48 additions & 37 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,52 @@ pub enum ScriptsRequired {
Custom,
}

/// Represents the range of ports to be scanned.
/// Represents the ranges of ports to be scanned Vec<(start:u16, end: u16)>
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct PortRange {
pub start: u16,
pub end: u16,
}
pub struct PortRanges(pub Vec<(u16, u16)>);

#[cfg(not(tarpaulin_include))]
fn parse_range(input: &str) -> Result<PortRange, String> {
let range = input
.split('-')
.map(str::parse)
.collect::<Result<Vec<u16>, std::num::ParseIntError>>();

if range.is_err() {
return Err(String::from(
"the range format must be 'start-end'. Example: 1-1000.",
));
/// Parse a single `start-end` token (e.g. "100-200") into `(start, end)`.
/// Returns `None` when the token is malformed, cannot be parsed as `u16`,
/// or `start > end`.
fn parse_range(input: &str) -> Option<(u16, u16)> {
let mut parts = input.trim().splitn(2, '-').map(str::trim);
let a = parts.next()?;
let b = parts.next()?;

let start = a.parse::<u16>().ok()?;
let end = b.parse::<u16>().ok()?;

if start <= end {
Some((start, end))
} else {
None
}

match range.unwrap().as_slice() {
[start, end] => Ok(PortRange {
start: *start,
end: *end,
}),
_ => Err(String::from(
"the range format must be 'start-end'. Example: 1-1000.",
)),
}
#[cfg(not(tarpaulin_include))]
/// Parse a comma-separated list of `start-end` ranges into `PortRanges`.
///
/// Errors with a helpful message identifying the bad token.
fn parse_ranges(input: &str) -> Result<PortRanges, String> {
let s = input.trim();
if s.is_empty() {
return Err("empty input: expected one or more comma-separated 'start-end' pairs".into());
}

let ranges_res: Result<Vec<(u16, u16)>, String> = s
.split(',')
.map(|token| {
let t = token.trim();
parse_range(t).ok_or_else(|| {
format!(
"invalid range token `{}` — expected `start-end` with 0 <= start <= end <= 65535",
t
)
})
})
.collect();

ranges_res.map(PortRanges)
}

#[derive(Parser, Debug, Clone)]
Expand All @@ -80,9 +97,9 @@ pub struct Opts {
#[arg(short, long, value_delimiter = ',')]
pub ports: Option<Vec<u16>>,

/// A range of ports with format start-end. Example: 1-1000.
#[arg(short, long, conflicts_with = "ports", value_parser = parse_range)]
pub range: Option<PortRange>,
/// Ranges of ports with comma seperated start-end pairs. Example: 1-500,1000-2500,4000-7000
#[arg(short, long, conflicts_with = "ports", value_parser = parse_ranges)]
pub range: Option<PortRanges>,

/// Whether to ignore the configuration file or not.
#[arg(short, long)]
Expand Down Expand Up @@ -169,10 +186,7 @@ impl Opts {
let mut opts = Opts::parse();

if opts.ports.is_none() && opts.range.is_none() {
opts.range = Some(PortRange {
start: LOWEST_PORT_NUMBER,
end: TOP_PORT_NUMBER,
});
opts.range = Some(PortRanges(vec![(LOWEST_PORT_NUMBER, TOP_PORT_NUMBER)]));
}

opts
Expand Down Expand Up @@ -259,7 +273,7 @@ impl Default for Opts {
pub struct Config {
addresses: Option<Vec<String>>,
ports: Option<Vec<u16>>,
range: Option<PortRange>,
range: Option<PortRanges>,
greppable: Option<bool>,
accessible: Option<bool>,
batch_size: Option<usize>,
Expand Down Expand Up @@ -344,7 +358,7 @@ mod tests {
use clap::{CommandFactory, Parser};
use parameterized::parameterized;

use super::{Config, Opts, PortRange, ScanOrder, ScriptsRequired};
use super::{Config, Opts, PortRanges, ScanOrder, ScriptsRequired};

impl Config {
fn default() -> Self {
Expand Down Expand Up @@ -430,10 +444,7 @@ mod tests {
fn opts_merge_optional_arguments() {
let mut opts = Opts::default();
let mut config = Config::default();
config.range = Some(PortRange {
start: 1,
end: 1_000,
});
config.range = Some(PortRanges(vec![(1, 1_000)]));
config.ulimit = Some(1_000);
config.resolver = Some("1.1.1.1".to_owned());

Expand Down
9 changes: 3 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@
//! use async_std::task::block_on;
//! use std::{net::IpAddr, time::Duration};
//!
//! use rustscan::input::{PortRange, ScanOrder};
//! use rustscan::input::{PortRanges, ScanOrder};
//! use rustscan::port_strategy::PortStrategy;
//! use rustscan::scanner::Scanner;
//!
//! fn main() {
//! let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
//! let range = PortRange {
//! start: 1,
//! end: 1_000,
//! };
//! let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random); // can be serial, random or manual https://github.com/RustScan/RustScan/blob/master/src/port_strategy/mod.rs
//! let range = PortRanges(vec![(1, 1_000)]);
//! let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random); // can be serial, random or manual https://github.com/RustScan/RustScan/blob/master/src/port_strategy/mod.rs
//! let scanner = Scanner::new(
//! &addrs, // the addresses to scan
//! 10, // batch_size is how many ports at a time should be scanned
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ 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.ports, opts.scan_order),
opts.accessible,
opts.exclude_ports.unwrap_or_default(),
opts.udp,
Expand Down
68 changes: 39 additions & 29 deletions src/port_strategy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Provides a means to hold configuration options specifically for port scanning.
mod range_iterator;
use crate::input::{PortRange, ScanOrder};
use crate::input::{PortRanges, ScanOrder};
use rand::rng;
use rand::seq::SliceRandom;
use range_iterator::RangeIterator;
Expand All @@ -17,20 +17,18 @@ pub enum PortStrategy {
}

impl PortStrategy {
pub fn pick(range: &Option<PortRange>, ports: Option<Vec<u16>>, order: ScanOrder) -> Self {
pub fn pick(range: Option<PortRanges>, ports: Option<Vec<u16>>, order: ScanOrder) -> Self {
match order {
ScanOrder::Serial if ports.is_none() => {
let range = range.as_ref().unwrap();
let port_ranges = range.unwrap();
PortStrategy::Serial(SerialRange {
start: range.start,
end: range.end,
range: port_ranges.0,
})
}
ScanOrder::Random if ports.is_none() => {
let range = range.as_ref().unwrap();
let port_ranges = range.unwrap();
PortStrategy::Random(RandomRange {
start: range.start,
end: range.end,
range: port_ranges.0,
})
}
ScanOrder::Serial => PortStrategy::Manual(ports.unwrap()),
Expand Down Expand Up @@ -62,22 +60,20 @@ trait RangeOrder {
/// ascending order.
#[derive(Debug)]
pub struct SerialRange {
start: u16,
end: u16,
range: Vec<(u16, u16)>,
}

impl RangeOrder for SerialRange {
fn generate(&self) -> Vec<u16> {
(self.start..=self.end).collect()
RangeIterator::new_serial(&self.range).collect()
}
}

/// As the name implies RandomRange will always generate a vector with
/// a random order. This vector is built following the LCG algorithm.
#[derive(Debug)]
pub struct RandomRange {
start: u16,
end: u16,
range: Vec<(u16, u16)>,
}

impl RangeOrder for RandomRange {
Expand All @@ -91,50 +87,64 @@ impl RangeOrder for RandomRange {
// port numbers close to each other are pretty slim due to the way the
// algorithm works.
fn generate(&self) -> Vec<u16> {
RangeIterator::new(self.start.into(), self.end.into()).collect()
RangeIterator::new_random(&self.range).collect()
}
}

#[cfg(test)]
mod tests {
use super::PortStrategy;
use crate::input::{PortRange, ScanOrder};
use crate::input::{PortRanges, ScanOrder};
use std::collections::HashSet;

fn expected_ports_from_ranges(input: &[(u16, u16)]) -> Vec<u16> {
let mut s = HashSet::new();
for &(start, end) in input {
for p in start..=end {
s.insert(p);
}
}
let mut v: Vec<u16> = s.into_iter().collect();
v.sort_unstable();
v
}
#[test]
fn serial_strategy_with_range() {
let range = PortRange { start: 1, end: 100 };
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Serial);
let ranges = PortRanges(vec![(1u16, 10u16), (20u16, 30u16), (100u16, 110u16)]);
let strategy = PortStrategy::pick(Some(ranges.clone()), None, ScanOrder::Serial);
let result = strategy.order();
let expected_range = (1..=100).collect::<Vec<u16>>();
assert_eq!(expected_range, result);
let expected = expected_ports_from_ranges(&ranges.0);

assert_eq!(expected, result);
}
#[test]
fn random_strategy_with_range() {
let range = PortRange { start: 1, end: 100 };
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let ranges = PortRanges(vec![(1u16, 10u16), (20u16, 30u16), (100u16, 110u16)]);
let strategy = PortStrategy::pick(Some(ranges.clone()), None, ScanOrder::Random);
let mut result = strategy.order();
let expected_range = (1..=100).collect::<Vec<u16>>();
assert_ne!(expected_range, result);
let expected = expected_ports_from_ranges(&ranges.0);

assert_ne!(expected, result);
result.sort_unstable();
assert_eq!(expected_range, result);

assert_eq!(expected, result);
}

#[test]
fn serial_strategy_with_ports() {
let strategy = PortStrategy::pick(&None, Some(vec![80, 443]), ScanOrder::Serial);
let strategy = PortStrategy::pick(None, Some(vec![80, 443]), ScanOrder::Serial);
let result = strategy.order();
assert_eq!(vec![80, 443], result);
}

#[test]
fn random_strategy_with_ports() {
let strategy = PortStrategy::pick(&None, Some((1..10).collect()), ScanOrder::Random);
let strategy = PortStrategy::pick(None, Some((1..10).collect()), ScanOrder::Random);
let mut result = strategy.order();
let expected_range = (1..10).collect::<Vec<u16>>();
assert_ne!(expected_range, result);
let expected = (1..10).collect::<Vec<u16>>();
assert_ne!(expected, result);

result.sort_unstable();
assert_eq!(expected_range, result);
assert_eq!(expected, result);
}
}
Loading