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 .await?;
95 }
96
97 if v6set.is_empty() {
98 println!("[asn] No IPv6 routes found for {}", as_number);
99 } else {
100 write_as_ip_list_to_file(&as_number, IpFamily::V6, &v6set, &mode, format)
101 .await?;
102 }
103 }
104 Err(e) => eprintln!("[asn] Error processing {}: {}", as_number, e),
105 };
106
107 Ok::<(), Box<dyn Error + Send + Sync>>(())
108 });
109 handles.push(handle);
110 }
111
112 for h in handles {
114 h.await??;
115 }
116
117 Ok(())
118}