Skip to content

Commit 1085a72

Browse files
authored
Merge pull request #787 from mikhail5555/improve-excluded_nets-filter-performance
Improve excluded nets filter performance dractically
2 parents 05e5c50 + e1c8c25 commit 1085a72

File tree

6 files changed

+92
-33
lines changed

6 files changed

+92
-33
lines changed

benches/benchmark_portscan.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use async_std::task::block_on;
2-
use criterion::{black_box, criterion_group, criterion_main, Criterion};
3-
use rustscan::input::{PortRange, ScanOrder};
2+
use criterion::{criterion_group, criterion_main, Criterion};
3+
use rustscan::input::{Opts, PortRange, ScanOrder};
44
use rustscan::port_strategy::PortStrategy;
55
use rustscan::scanner::Scanner;
6+
use std::hint::black_box;
67
use std::net::IpAddr;
78
use std::time::Duration;
89

@@ -26,6 +27,24 @@ fn bench_port_strategy() {
2627
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
2728
}
2829

30+
fn bench_address_parsing() {
31+
let opts = Opts {
32+
addresses: vec![
33+
"127.0.0.1".to_owned(),
34+
"10.2.0.1".to_owned(),
35+
"192.168.0.0/24".to_owned(),
36+
],
37+
exclude_addresses: Some(vec![
38+
"10.0.0.0/8".to_owned(),
39+
"172.16.0.0/12".to_owned(),
40+
"192.168.0.0/16".to_owned(),
41+
"172.16.0.1".to_owned(),
42+
]),
43+
..Default::default()
44+
};
45+
let _ips = rustscan::address::parse_addresses(&opts);
46+
}
47+
2948
fn criterion_benchmark(c: &mut Criterion) {
3049
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
3150
let range = PortRange {
@@ -74,6 +93,13 @@ fn criterion_benchmark(c: &mut Criterion) {
7493
c.bench_function("parse address", |b| b.iter(bench_address));
7594

7695
c.bench_function("port strategy", |b| b.iter(bench_port_strategy));
96+
97+
let mut address_group = c.benchmark_group("address parsing");
98+
address_group.measurement_time(Duration::from_secs(10));
99+
address_group.bench_function("parse addresses with exclusions", |b| {
100+
b.iter(|| bench_address_parsing())
101+
});
102+
address_group.finish();
77103
}
78104

79105
criterion_group!(benches, criterion_benchmark);

build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ fn ports_v(fp_map: &BTreeMap<i32, String>) -> BTreeMap<i32, Vec<u16>> {
141141
} else if !segment.is_empty() {
142142
match segment.parse::<u16>() {
143143
Ok(port) => port_list.push(port),
144-
Err(_) => println!("Error parsing port: {}", segment),
144+
Err(_) => println!("Error parsing port: {segment}"),
145145
}
146146
}
147147
}

src/address.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,11 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
6969
}
7070
}
7171

72-
// Finally, craft a list of addresses to be excluded from the scan.
73-
let mut excluded_ips: BTreeSet<IpAddr> = BTreeSet::new();
74-
if let Some(exclude_addresses) = &input.exclude_addresses {
75-
for addr in exclude_addresses {
76-
excluded_ips.extend(parse_address(addr, &backup_resolver));
77-
}
78-
}
72+
let excluded_cidrs = parse_excluded_networks(&input.exclude_addresses, &backup_resolver);
7973

8074
// Remove duplicated/excluded IPs.
8175
let mut seen = BTreeSet::new();
82-
ips.retain(|ip| seen.insert(*ip) && !excluded_ips.contains(ip));
76+
ips.retain(|ip| seen.insert(*ip) && !excluded_cidrs.iter().any(|cidr| cidr.contains(ip)));
8377

8478
ips
8579
}
@@ -125,6 +119,46 @@ fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr
125119
ips
126120
}
127121

122+
/// Parses excluded networks from a list of addresses.
123+
///
124+
/// This function handles three types of inputs:
125+
/// 1. CIDR notation (e.g. "192.168.0.0/24")
126+
/// 2. Single IP addresses (e.g. "192.168.0.1")
127+
/// 3. Hostnames that need to be resolved (e.g. "example.com")
128+
///
129+
/// ```rust
130+
/// # use rustscan::address::parse_excluded_networks;
131+
/// # use hickory_resolver::Resolver;
132+
/// let resolver = Resolver::default().unwrap();
133+
/// let excluded = parse_excluded_networks(&Some(vec!["192.168.0.0/24".to_owned()]), &resolver);
134+
/// ```
135+
pub fn parse_excluded_networks(
136+
exclude_addresses: &Option<Vec<String>>,
137+
resolver: &Resolver,
138+
) -> Vec<IpCidr> {
139+
exclude_addresses
140+
.iter()
141+
.flatten()
142+
.flat_map(|addr| parse_single_excluded_address(addr, resolver))
143+
.collect()
144+
}
145+
146+
/// Parses a single address into an IpCidr, handling CIDR notation, IP addresses, and hostnames.
147+
fn parse_single_excluded_address(addr: &str, resolver: &Resolver) -> Vec<IpCidr> {
148+
if let Ok(cidr) = IpCidr::from_str(addr) {
149+
return vec![cidr];
150+
}
151+
152+
if let Ok(ip) = IpAddr::from_str(addr) {
153+
return vec![IpCidr::new_host(ip)];
154+
}
155+
156+
resolve_ips_from_host(addr, resolver)
157+
.into_iter()
158+
.map(IpCidr::new_host)
159+
.collect()
160+
}
161+
128162
/// Derive a DNS resolver.
129163
///
130164
/// 1. if the `resolver` parameter has been set:

src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fn main() {
4646
let config = Config::read(opts.config_path.clone());
4747
opts.merge(&config);
4848

49-
debug!("Main() `opts` arguments are {:?}", opts);
49+
debug!("Main() `opts` arguments are {opts:?}");
5050

5151
let scripts_to_run: Vec<ScriptFile> = match init_scripts(&opts.scripts) {
5252
Ok(scripts_to_run) => scripts_to_run,
@@ -94,7 +94,7 @@ fn main() {
9494
opts.exclude_ports.unwrap_or_default(),
9595
opts.udp,
9696
);
97-
debug!("Scanner finished building: {:?}", scanner);
97+
debug!("Scanner finished building: {scanner:?}");
9898

9999
let mut portscan_bench = NamedTimer::start("Portscan");
100100
let scan_result = block_on(scanner.run());
@@ -146,7 +146,7 @@ fn main() {
146146
// This part allows us to add commandline arguments to the Script call_format, appending them to the end of the command.
147147
if !opts.command.is_empty() {
148148
let user_extra_args = &opts.command.join(" ");
149-
debug!("Extra args vec {:?}", user_extra_args);
149+
debug!("Extra args vec {user_extra_args:?}");
150150
if script_f.call_format.is_some() {
151151
let mut call_f = script_f.call_format.unwrap();
152152
call_f.push(' ');
@@ -156,7 +156,7 @@ fn main() {
156156
opts.greppable,
157157
opts.accessible
158158
);
159-
debug!("Call format {}", call_f);
159+
debug!("Call format {call_f}");
160160
script_f.call_format = Some(call_f);
161161
}
162162
}
@@ -187,7 +187,7 @@ fn main() {
187187
benchmarks.push(script_bench);
188188
rustscan_bench.end();
189189
benchmarks.push(rustscan_bench);
190-
debug!("Benchmarks raw {:?}", benchmarks);
190+
debug!("Benchmarks raw {benchmarks:?}");
191191
info!("{}", benchmarks.summary());
192192
}
193193

src/scanner/mod.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl Scanner {
112112
}
113113
}
114114
}
115-
debug!("Typical socket connection errors {:?}", errors);
115+
debug!("Typical socket connection errors {errors:?}");
116116
debug!("Open Sockets found: {:?}", &open_sockets);
117117
open_sockets
118118
}
@@ -153,7 +153,7 @@ impl Scanner {
153153
}
154154
self.fmt_ports(socket);
155155

156-
debug!("Return Ok after {} tries", nr_try);
156+
debug!("Return Ok after {nr_try} tries");
157157
return Ok(socket);
158158
}
159159
Err(e) => {
@@ -164,7 +164,7 @@ impl Scanner {
164164
if nr_try == tries {
165165
error_string.push(' ');
166166
error_string.push_str(&socket.ip().to_string());
167-
return Err(io::Error::new(io::ErrorKind::Other, error_string));
167+
return Err(io::Error::other(error_string));
168168
}
169169
}
170170
};
@@ -193,10 +193,9 @@ impl Scanner {
193193
}
194194
}
195195

196-
Err(io::Error::new(
197-
io::ErrorKind::Other,
198-
format!("UDP scan timed-out for all tries on socket {}", socket),
199-
))
196+
Err(io::Error::other(format!(
197+
"UDP scan timed-out for all tries on socket {socket}"
198+
)))
200199
}
201200

202201
/// Performs the connection to the socket with timeout
@@ -275,7 +274,7 @@ impl Scanner {
275274

276275
match io::timeout(wait, udp_socket.recv(&mut buf)).await {
277276
Ok(size) => {
278-
debug!("Received {} bytes", size);
277+
debug!("Received {size} bytes");
279278
self.fmt_ports(socket);
280279
Ok(true)
281280
}
@@ -289,7 +288,7 @@ impl Scanner {
289288
}
290289
}
291290
Err(e) => {
292-
println!("Err E binding sock {:?}", e);
291+
println!("Err E binding sock {e:?}");
293292
Err(e)
294293
}
295294
}

src/scripts/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
109109
}
110110
ScriptsRequired::Custom => {
111111
let script_config = ScriptConfig::read_config()?;
112-
debug!("Script config \n{:?}", script_config);
112+
debug!("Script config \n{script_config:?}");
113113

114114
let script_dir_base = if let Some(config_directory) = &script_config.directory {
115115
PathBuf::from(config_directory)
@@ -118,10 +118,10 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
118118
};
119119

120120
let script_paths = find_scripts(script_dir_base)?;
121-
debug!("Scripts paths \n{:?}", script_paths);
121+
debug!("Scripts paths \n{script_paths:?}");
122122

123123
let parsed_scripts = parse_scripts(script_paths);
124-
debug!("Scripts parsed \n{:?}", parsed_scripts);
124+
debug!("Scripts parsed \n{parsed_scripts:?}");
125125

126126
// Only Scripts that contain all the tags found in ScriptConfig will be selected.
127127
if let Some(config_hashset) = script_config.tags {
@@ -142,7 +142,7 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
142142
}
143143
}
144144
}
145-
debug!("\nScript(s) to run {:?}", scripts_to_run);
145+
debug!("\nScript(s) to run {scripts_to_run:?}");
146146
}
147147
}
148148

@@ -269,14 +269,14 @@ impl Script {
269269
};
270270
to_run = default_template.fill_with_struct(&exec_parts)?;
271271
}
272-
debug!("\nScript format to run {}", to_run);
272+
debug!("\nScript format to run {to_run}");
273273
execute_script(&to_run)
274274
}
275275
}
276276

277277
#[cfg(not(tarpaulin_include))]
278278
fn execute_script(script: &str) -> Result<String> {
279-
debug!("\nScript arguments {}", script);
279+
debug!("\nScript arguments {script}");
280280

281281
let (cmd, arg) = if cfg!(unix) {
282282
("sh", "-c")
@@ -314,7 +314,7 @@ fn execute_script(script: &str) -> Result<String> {
314314
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
315315
}
316316
Err(error) => {
317-
debug!("Command error {}", error.to_string());
317+
debug!("Command error {error}",);
318318
Err(anyhow!(error.to_string()))
319319
}
320320
}
@@ -373,7 +373,7 @@ impl ScriptFile {
373373
Some(parsed)
374374
}
375375
Err(e) => {
376-
debug!("Failed to parse ScriptFile headers {}", e.to_string());
376+
debug!("Failed to parse ScriptFile headers {e}");
377377
None
378378
}
379379
}

0 commit comments

Comments
 (0)