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};
5use tokio::process::Command;
6
7pub async fn get_ips_for_as(
10 as_number: &str,
11 family: IpFamily,
12) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
13 let output = Command::new("whois")
15 .arg("-h")
16 .arg("whois.radb.net")
17 .arg("--")
18 .arg(format!("-i origin {}", as_number))
19 .stderr(Stdio::inherit())
20 .output()
21 .await?;
22
23 if !output.status.success() {
24 return Err(format!("whois command failed for {}", as_number).into());
25 }
26
27 let stdout_str = String::from_utf8_lossy(&output.stdout);
28
29 let route_key = family.route_key();
31
32 let ipnets: Vec<IpNet> = stdout_str
34 .lines()
35 .filter_map(|line| {
36 if line.contains(route_key) {
37 let parts: Vec<&str> = line.split_whitespace().collect();
38 parts
39 .get(1)
40 .and_then(|cidr_str| cidr_str.parse::<IpNet>().ok())
41 } else {
42 None
43 }
44 })
45 .collect();
46
47 let aggregated = IpNet::aggregate(&ipnets);
49 Ok(aggregated.into_iter().collect())
50}
51
52pub async fn process_as_numbers(
55 as_numbers: &[String],
56 mode: &str,
57 output_format: OutputFormat,
58) -> Result<(), Box<dyn Error + Send + Sync>> {
59 for as_number in as_numbers {
61 for &family in &[IpFamily::V4, IpFamily::V6] {
62 match get_ips_for_as(as_number, family).await {
63 Ok(set) => {
64 if set.is_empty() {
65 println!(
66 "[asn] No {} routes found for {}",
67 family.as_str(),
68 as_number
69 );
70 } else {
71 write_as_ip_list_to_file(as_number, family, &set, mode, output_format)?;
73 }
74 }
75 Err(e) => eprintln!(
76 "[asn] Error processing {} ({}): {}",
77 as_number,
78 family.as_str(),
79 e
80 ),
81 }
82 }
83 }
84
85 Ok(())
86}