1use crate::common::{IpFamily, OutputFormat};
2use crate::output::write_as_ip_list_to_file;
3use ipnet::IpNet;
4use std::{collections::BTreeSet, error::Error, process::Stdio, sync::Arc};
5use tokio::{process::Command, sync::Semaphore};
6
7pub async fn get_ips_for_as_once(
9 as_number: &str,
10) -> Result<(BTreeSet<IpNet>, BTreeSet<IpNet>), Box<dyn Error + Send + Sync>> {
11 let output = Command::new("whois")
12 .arg("-h")
13 .arg("whois.radb.net")
14 .arg("--")
15 .arg(format!("-i origin {}", as_number))
16 .stderr(Stdio::inherit())
17 .output()
18 .await?;
19
20 if !output.status.success() {
21 return Err(format!("whois command failed for {}", as_number).into());
22 }
23
24 let stdout_str = String::from_utf8_lossy(&output.stdout);
25
26 let mut v4s = BTreeSet::new();
28 let mut v6s = BTreeSet::new();
29
30 for line in stdout_str.lines() {
31 if line.starts_with("route:") {
32 if let Some(ip_str) = line.split_whitespace().nth(1) {
34 if let Ok(ip) = ip_str.parse::<IpNet>() {
35 if let IpNet::V4(_) = ip {
37 v4s.insert(ip);
38 }
39 }
40 }
41 } else if line.starts_with("route6:") {
42 if let Some(ip_str) = line.split_whitespace().nth(1) {
44 if let Ok(ip) = ip_str.parse::<IpNet>() {
45 if let IpNet::V6(_) = ip {
46 v6s.insert(ip);
47 }
48 }
49 }
50 }
51 }
52
53 let aggregated_v4 = IpNet::aggregate(&v4s.iter().copied().collect::<Vec<_>>());
55 let aggregated_v6 = IpNet::aggregate(&v6s.iter().copied().collect::<Vec<_>>());
56
57 Ok((
58 aggregated_v4.into_iter().collect(),
59 aggregated_v6.into_iter().collect(),
60 ))
61}
62
63pub async fn process_as_numbers(
66 as_numbers: &[String],
67 mode: &str,
68 output_format: OutputFormat,
69) -> Result<(), Box<dyn Error + Send + Sync>> {
70 let max_concurrent = 5;
72 let semaphore = Arc::new(Semaphore::new(max_concurrent));
73
74 let mut handles = Vec::new();
75
76 for as_number in as_numbers {
77 let as_number = as_number.clone();
78 let mode = mode.to_string();
79 let format = output_format;
80 let sem_clone = semaphore.clone();
81
82 let handle = tokio::spawn(async move {
84 let _permit = sem_clone.acquire_owned().await?;
86
87 match get_ips_for_as_once(&as_number).await {
88 Ok((v4set, v6set)) => {
89 if v4set.is_empty() {
91 println!("[asn] No IPv4 routes found for {}", as_number);
92 } else {
93 write_as_ip_list_to_file(&as_number, IpFamily::V4, &v4set, &mode, format)?;
94 }
95
96 if v6set.is_empty() {
97 println!("[asn] No IPv6 routes found for {}", as_number);
98 } else {
99 write_as_ip_list_to_file(&as_number, IpFamily::V6, &v6set, &mode, format)?;
100 }
101 }
102 Err(e) => eprintln!("[asn] Error processing {}: {}", as_number, e),
103 };
104
105 Ok::<(), Box<dyn Error + Send + Sync>>(())
106 });
107 handles.push(handle);
108 }
109
110 for h in handles {
112 h.await??;
113 }
114
115 Ok(())
116}