1use crate::{common::IpFamily, output::write_as_ip_list_to_file};
2use ipnet::IpNet;
3use std::{collections::BTreeSet, error::Error, process::Stdio};
4use tokio::process::Command;
5
6pub async fn get_ips_for_as(
9 as_number: &str,
10 family: IpFamily,
11) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
12 let output = Command::new("whois")
14 .arg("-h")
15 .arg("whois.radb.net")
16 .arg("--")
17 .arg(format!("-i origin {}", as_number))
18 .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) -> Result<(), Box<dyn Error + Send + Sync>> {
58 for as_number in as_numbers {
60 for &family in &[IpFamily::V4, IpFamily::V6] {
61 match get_ips_for_as(as_number, family).await {
62 Ok(set) => {
63 if set.is_empty() {
64 println!(
65 "[asn] No {} routes found for {}",
66 family.as_str(),
67 as_number
68 );
69 } else {
70 write_as_ip_list_to_file(as_number, family, &set, mode)?;
71 }
72 }
73 Err(e) => eprintln!(
74 "[asn] Error processing {} ({}): {}",
75 as_number,
76 family.as_str(),
77 e
78 ),
79 }
80 }
81 }
82
83 Ok(())
84}