1
- use clap:: { App , Arg } ;
1
+ use clap:: { App , Arg , ArgGroup } ;
2
2
use futures:: stream:: FuturesUnordered ;
3
3
use futures:: stream:: StreamExt ;
4
4
use indicatif:: { ProgressBar , ProgressStyle } ;
@@ -21,6 +21,9 @@ use colored::*;
21
21
use scraper:: { Html , Selector } ;
22
22
use rquest:: tls:: Impersonate ;
23
23
use flate2:: read:: GzDecoder ;
24
+ use regex:: Regex ;
25
+ use tokio:: process:: Command as TokioCommand ;
26
+ use ipnet:: Ipv4Net ;
24
27
25
28
#[ tokio:: main]
26
29
async fn main ( ) {
@@ -52,18 +55,27 @@ async fn main() {
52
55
. long ( "ipfile" )
53
56
. short ( "i" )
54
57
. help ( "File containing list of IP addresses" )
55
- . conflicts_with ( "iprange" )
56
- . required_unless ( "iprange" )
57
58
. takes_value ( true ) ,
58
59
)
59
60
. arg (
60
61
Arg :: with_name ( "iprange" )
61
62
. long ( "iprange" )
62
63
. value_name ( "IP_RANGE" )
63
- . help ( "Specifies a single IP address or a range of IP addresses (e.g., 51.15.0.0-51.15.10.255)" )
64
- . conflicts_with ( "ipfile" )
64
+ . help ( "Specifies a single IP address, a range of IP addresses (e.g., 51.15.0.0-51.15.10.255), or a CIDR notation (e.g., 51.15.0.0/16)" )
65
65
. takes_value ( true ) ,
66
66
)
67
+ . arg (
68
+ Arg :: with_name ( "asn" )
69
+ . long ( "asn" )
70
+ . value_name ( "ASN_CODE" )
71
+ . help ( "Specify an ASN code (e.g., AS714) to scan all ranges associated with it" )
72
+ . takes_value ( true ) ,
73
+ )
74
+ . group ( ArgGroup :: with_name ( "ip_input" )
75
+ . args ( & [ "ipfile" , "iprange" , "asn" ] )
76
+ . required ( true )
77
+ . multiple ( false )
78
+ )
67
79
. arg (
68
80
Arg :: with_name ( "useragents" )
69
81
. long ( "useragents" )
@@ -110,6 +122,7 @@ async fn main() {
110
122
111
123
let ip_file = matches. value_of ( "ipfile" ) ;
112
124
let ip_range = matches. value_of ( "iprange" ) ;
125
+ let asn_code = matches. value_of ( "asn" ) ;
113
126
let ua_file = matches. value_of ( "useragents" ) ;
114
127
let timeout_secs: u64 = matches. value_of ( "timeout" ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
115
128
let timeout_duration = Duration :: from_secs ( timeout_secs) ;
@@ -138,8 +151,27 @@ async fn main() {
138
151
return ;
139
152
}
140
153
}
154
+ } else if let Some ( asn_code) = asn_code {
155
+ let ranges = fetch_ranges_for_asn ( asn_code) . await ;
156
+ if ranges. is_empty ( ) {
157
+ eprintln ! ( "No IP ranges found for ASN code {}" , asn_code) ;
158
+ return ;
159
+ } else {
160
+ println ! ( "Found the following ranges for ASN {}:" , asn_code) ;
161
+ for range in & ranges {
162
+ println ! ( "{}" , range) ;
163
+ }
164
+ let mut ips = Vec :: new ( ) ;
165
+ for range in ranges {
166
+ match parse_ip_range ( & range) {
167
+ Ok ( mut range_ips) => ips. append ( & mut range_ips) ,
168
+ Err ( e) => eprintln ! ( "Error parsing range {}: {}" , range, e) ,
169
+ }
170
+ }
171
+ ips
172
+ }
141
173
} else {
142
- eprintln ! ( "You must specify either an IP address file with --ipfile, or an IP address range with --iprange." ) ;
174
+ eprintln ! ( "You must specify either an IP address file with --ipfile, an IP address range with --iprange, or an ASN code with --asn ." ) ;
143
175
return ;
144
176
} ;
145
177
@@ -488,41 +520,78 @@ fn read_lines(filename: &str) -> Vec<String> {
488
520
}
489
521
490
522
fn parse_ip_range ( ip_range : & str ) -> Result < Vec < String > , String > {
491
- let parts: Vec < & str > = ip_range. split ( '-' ) . collect ( ) ;
492
- if parts. len ( ) == 1 {
493
- let ip = parts[ 0 ] ;
494
- let ip_addr = Ipv4Addr :: from_str ( ip) . map_err ( |_| "Adresse IP invalide." . to_string ( ) ) ?;
495
- Ok ( vec ! [ ip_addr. to_string( ) ] )
496
- } else if parts. len ( ) == 2 {
497
- let start_ip =
498
- Ipv4Addr :: from_str ( parts[ 0 ] ) . map_err ( |_| "Adresse IP de début invalide." . to_string ( ) ) ?;
499
- let end_ip =
500
- Ipv4Addr :: from_str ( parts[ 1 ] ) . map_err ( |_| "Adresse IP de fin invalide." . to_string ( ) ) ?;
523
+ if ip_range. contains ( '/' ) {
524
+ // Handle CIDR notation
525
+ let cidr = ip_range;
526
+ let ipnet: Ipv4Net = match cidr. parse ( ) {
527
+ Ok ( net) => net,
528
+ Err ( _) => return Err ( "Invalid CIDR notation." . to_string ( ) ) ,
529
+ } ;
530
+ let ips: Vec < String > = ipnet. hosts ( ) . map ( |ip| ip. to_string ( ) ) . collect ( ) ;
531
+ Ok ( ips)
532
+ } else if ip_range. contains ( '-' ) {
533
+ // Handle start-end format
534
+ let parts: Vec < & str > = ip_range. split ( '-' ) . collect ( ) ;
535
+ if parts. len ( ) == 2 {
536
+ let start_ip =
537
+ Ipv4Addr :: from_str ( parts[ 0 ] ) . map_err ( |_| "Invalid start IP address." . to_string ( ) ) ?;
538
+ let end_ip =
539
+ Ipv4Addr :: from_str ( parts[ 1 ] ) . map_err ( |_| "Invalid end IP address." . to_string ( ) ) ?;
540
+
541
+ let start: u32 = start_ip. into ( ) ;
542
+ let end: u32 = end_ip. into ( ) ;
543
+
544
+ if start > end {
545
+ return Err ( "Start IP address is greater than end IP address." . to_string ( ) ) ;
546
+ }
501
547
502
- let start: u32 = start_ip. into ( ) ;
503
- let end: u32 = end_ip. into ( ) ;
548
+ let ips: Vec < String > = ( start..=end)
549
+ . map ( |ip_num| Ipv4Addr :: from ( ip_num) . to_string ( ) )
550
+ . collect ( ) ;
504
551
505
- if start > end {
506
- return Err ( "L'adresse IP de début est supérieure à l'adresse IP de fin." . to_string ( ) ) ;
552
+ Ok ( ips)
553
+ } else {
554
+ Err ( "Invalid IP range format. Use 'start-end' format, CIDR notation, or specify a single IP address." . to_string ( ) )
507
555
}
556
+ } else {
557
+ // Handle single IP address
558
+ let ip = ip_range;
559
+ let ip_addr = Ipv4Addr :: from_str ( ip) . map_err ( |_| "Invalid IP address." . to_string ( ) ) ?;
560
+ Ok ( vec ! [ ip_addr. to_string( ) ] )
561
+ }
562
+ }
508
563
509
- let max_ips = 1_000_000 ;
510
- let total_ips = end - start + 1 ;
511
-
512
- if total_ips > max_ips {
513
- return Err ( format ! (
514
- "La plage d'adresses IP est trop grande ({} adresses). Veuillez spécifier une plage plus petite." ,
515
- total_ips
516
- ) ) ;
517
- }
564
+ async fn fetch_ranges_for_asn ( asn_code : & str ) -> Vec < String > {
565
+ let whois_arg = format ! ( "-i origin {}" , asn_code) ;
518
566
519
- let ips: Vec < String > = ( start..=end)
520
- . map ( |ip_num| Ipv4Addr :: from ( ip_num) . to_string ( ) )
521
- . collect ( ) ;
567
+ let output = TokioCommand :: new ( "whois" )
568
+ . arg ( "-h" )
569
+ . arg ( "whois.radb.net" )
570
+ . arg ( "--" )
571
+ . arg ( & whois_arg)
572
+ . output ( )
573
+ . await ;
522
574
523
- Ok ( ips)
524
- } else {
525
- Err ( "Format de plage IP invalide. Utilisez le format 'début-fin' ou spécifiez une seule adresse IP." . to_string ( ) )
575
+ match output {
576
+ Ok ( output) => {
577
+ if output. status . success ( ) {
578
+ let data = String :: from_utf8_lossy ( & output. stdout ) ;
579
+ let re = Regex :: new ( r"(\d{1,3}\.){3}\d{1,3}/\d+" ) . unwrap ( ) ;
580
+ let mut ranges = Vec :: new ( ) ;
581
+ for cap in re. captures_iter ( & data) {
582
+ let range = cap. get ( 0 ) . unwrap ( ) . as_str ( ) . to_string ( ) ;
583
+ ranges. push ( range) ;
584
+ }
585
+ ranges
586
+ } else {
587
+ eprintln ! ( "WHOIS command failed for ASN {}: {}" , asn_code, String :: from_utf8_lossy( & output. stderr) ) ;
588
+ Vec :: new ( )
589
+ }
590
+ }
591
+ Err ( e) => {
592
+ eprintln ! ( "Failed to execute WHOIS command for ASN {}: {}" , asn_code, e) ;
593
+ Vec :: new ( )
594
+ }
526
595
}
527
596
}
528
597
0 commit comments