11//! Provides functions to parse input IP addresses, CIDRs or files.
2+ use std:: cell:: LazyCell ;
23use std:: collections:: BTreeSet ;
34use std:: fs:: { self , File } ;
45use std:: io:: { prelude:: * , BufReader } ;
@@ -39,12 +40,23 @@ use crate::warning;
3940/// start your runtime (as the `rustscan` binary does) or from
4041/// `tokio::task::spawn_blocking`, not directly from async code.
4142pub fn parse_addresses ( input : & Opts ) -> Vec < IpAddr > {
43+ parse_addresses_with_resolver ( input, || get_resolver ( & input. resolver ) )
44+ }
45+
46+ fn parse_addresses_with_resolver (
47+ input : & Opts ,
48+ create_resolver : impl FnOnce ( ) -> Resolver ,
49+ ) -> Vec < IpAddr > {
4250 let mut ips: Vec < IpAddr > = Vec :: new ( ) ;
4351 let mut unresolved_addresses: Vec < & str > = Vec :: new ( ) ;
44- let backup_resolver = get_resolver ( & input. resolver ) ;
52+ // Most scans use literal IPs or CIDRs. Defer the resolver's runtime and
53+ // hosts-file loading until a DNS fallback actually needs them, then reuse
54+ // that resolver for the rest of the targets and exclusions.
55+ let backup_resolver = LazyCell :: new ( create_resolver) ;
56+ let resolver = || & * backup_resolver;
4557
4658 for address in & input. addresses {
47- let parsed_ips = parse_address ( address, & backup_resolver ) ;
59+ let parsed_ips = parse_address_with_resolver ( address, & resolver ) ;
4860 if !parsed_ips. is_empty ( ) {
4961 ips. extend ( parsed_ips) ;
5062 } else {
@@ -66,7 +78,7 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
6678 continue ;
6779 }
6880
69- if let Ok ( x) = read_ips_from_file ( file_path, & backup_resolver ) {
81+ if let Ok ( x) = read_ips_from_file ( file_path, & resolver ) {
7082 ips. extend ( x) ;
7183 } else {
7284 warning ! (
@@ -77,7 +89,7 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
7789 }
7890 }
7991
80- let excluded_cidrs = parse_excluded_networks ( & input. exclude_addresses , & backup_resolver ) ;
92+ let excluded_cidrs = parse_excluded_networks_with_resolver ( & input. exclude_addresses , & resolver ) ;
8193
8294 // Remove duplicated/excluded IPs.
8395 let mut seen = BTreeSet :: new ( ) ;
@@ -100,6 +112,13 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
100112/// let ips = parse_address("127.0.0.1", &Resolver::default().unwrap());
101113/// ```
102114pub fn parse_address ( address : & str , resolver : & Resolver ) -> Vec < IpAddr > {
115+ parse_address_with_resolver ( address, & || resolver)
116+ }
117+
118+ fn parse_address_with_resolver < ' a > (
119+ address : & str ,
120+ resolver : & impl Fn ( ) -> & ' a Resolver ,
121+ ) -> Vec < IpAddr > {
103122 if let Ok ( addr) = IpAddr :: from_str ( address) {
104123 // `address` is an IP string
105124 vec ! [ addr]
@@ -118,14 +137,17 @@ pub fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
118137}
119138
120139/// Uses DNS to get the IPS associated with host
121- fn resolve_ips_from_host ( source : & str , backup_resolver : & Resolver ) -> Vec < IpAddr > {
140+ fn resolve_ips_from_host < ' a > (
141+ source : & str ,
142+ backup_resolver : & impl Fn ( ) -> & ' a Resolver ,
143+ ) -> Vec < IpAddr > {
122144 let mut ips: Vec < IpAddr > = Vec :: new ( ) ;
123145
124146 if let Ok ( addrs) = source. to_socket_addrs ( ) {
125147 for ip in addrs {
126148 ips. push ( ip. ip ( ) ) ;
127149 }
128- } else if let Ok ( addrs) = backup_resolver. lookup_ip ( source) {
150+ } else if let Ok ( addrs) = backup_resolver ( ) . lookup_ip ( source) {
129151 ips. extend ( addrs. iter ( ) ) ;
130152 }
131153
@@ -148,6 +170,13 @@ fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr
148170pub fn parse_excluded_networks (
149171 exclude_addresses : & Option < Vec < String > > ,
150172 resolver : & Resolver ,
173+ ) -> Vec < IpCidr > {
174+ parse_excluded_networks_with_resolver ( exclude_addresses, & || resolver)
175+ }
176+
177+ fn parse_excluded_networks_with_resolver < ' a > (
178+ exclude_addresses : & Option < Vec < String > > ,
179+ resolver : & impl Fn ( ) -> & ' a Resolver ,
151180) -> Vec < IpCidr > {
152181 exclude_addresses
153182 . iter ( )
@@ -157,7 +186,10 @@ pub fn parse_excluded_networks(
157186}
158187
159188/// Parses a single address into an IpCidr, handling CIDR notation, IP addresses, and hostnames.
160- fn parse_single_excluded_address ( addr : & str , resolver : & Resolver ) -> Vec < IpCidr > {
189+ fn parse_single_excluded_address < ' a > (
190+ addr : & str ,
191+ resolver : & impl Fn ( ) -> & ' a Resolver ,
192+ ) -> Vec < IpCidr > {
161193 if let Ok ( cidr) = IpCidr :: from_str ( addr) {
162194 return vec ! [ cidr] ;
163195 }
@@ -261,9 +293,9 @@ fn read_resolver_from_file(path: &str) -> Result<Vec<IpAddr>, std::io::Error> {
261293
262294#[ cfg( not( tarpaulin_include) ) ]
263295/// Parses an input file of IPs and uses those
264- fn read_ips_from_file (
296+ fn read_ips_from_file < ' a > (
265297 ips : & std:: path:: Path ,
266- backup_resolver : & Resolver ,
298+ backup_resolver : & impl Fn ( ) -> & ' a Resolver ,
267299) -> Result < Vec < IpAddr > , std:: io:: Error > {
268300 let file = File :: open ( ips) ?;
269301 let reader = BufReader :: new ( file) ;
@@ -272,7 +304,7 @@ fn read_ips_from_file(
272304
273305 for address_line in reader. lines ( ) {
274306 if let Ok ( address) = address_line {
275- ips. extend ( parse_address ( & address, backup_resolver) ) ;
307+ ips. extend ( parse_address_with_resolver ( & address, backup_resolver) ) ;
276308 } else {
277309 debug ! ( "Line in file is not valid" ) ;
278310 }
@@ -283,9 +315,48 @@ fn read_ips_from_file(
283315
284316#[ cfg( test) ]
285317mod tests {
286- use super :: { parse_addresses, Opts } ;
318+ use super :: { parse_addresses, parse_addresses_with_resolver , Opts } ;
287319 use std:: net:: { IpAddr , Ipv4Addr } ;
288320
321+ #[ test]
322+ fn literal_targets_and_exclusions_do_not_initialize_a_resolver ( ) {
323+ let opts = Opts {
324+ addresses : vec ! [
325+ "192.0.2.0/30" . to_owned( ) ,
326+ "192.0.2.2" . to_owned( ) ,
327+ "2001:db8::/126" . to_owned( ) ,
328+ "2001:db8::3" . to_owned( ) ,
329+ ] ,
330+ exclude_addresses : Some ( vec ! [
331+ "192.0.2.0/31" . to_owned( ) ,
332+ "192.0.2.3" . to_owned( ) ,
333+ "2001:db8::/127" . to_owned( ) ,
334+ "2001:db8::3" . to_owned( ) ,
335+ ] ) ,
336+ ..Default :: default ( )
337+ } ;
338+
339+ let ips = parse_addresses_with_resolver ( & opts, || {
340+ panic ! ( "literal targets and exclusions must not initialize DNS" )
341+ } ) ;
342+
343+ assert_eq ! (
344+ ips,
345+ [
346+ "192.0.2.2" . parse:: <IpAddr >( ) . unwrap( ) ,
347+ "2001:db8::2" . parse:: <IpAddr >( ) . unwrap( ) ,
348+ ]
349+ ) ;
350+ }
351+
352+ #[ test]
353+ fn empty_targets_do_not_initialize_a_resolver ( ) {
354+ assert ! ( parse_addresses_with_resolver( & Opts :: default ( ) , || {
355+ panic!( "empty targets must not initialize DNS" )
356+ } )
357+ . is_empty( ) ) ;
358+ }
359+
289360 #[ test]
290361 fn parse_correct_addresses ( ) {
291362 let opts = Opts {
0 commit comments