use crate::{common::IpFamily, output::write_as_ip_list_to_file};
use ipnet::IpNet;
use std::{collections::BTreeSet, error::Error, process::Stdio};
use tokio::process::Command;
pub async fn get_ips_for_as(
as_number: &str,
family: IpFamily,
) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
let output = Command::new("whois")
.arg("-h")
.arg("whois.radb.net")
.arg("--")
.arg(format!("-i origin {}", as_number))
.stderr(Stdio::inherit())
.output()
.await?;
if !output.status.success() {
return Err(format!("whois command failed for {}", as_number).into());
}
let stdout_str = String::from_utf8_lossy(&output.stdout);
let route_key = family.route_key();
let ipnets: Vec<IpNet> = stdout_str
.lines()
.filter_map(|line| {
if line.contains(route_key) {
let parts: Vec<&str> = line.split_whitespace().collect();
parts
.get(1)
.and_then(|cidr_str| cidr_str.parse::<IpNet>().ok())
} else {
None
}
})
.collect();
let aggregated = IpNet::aggregate(&ipnets);
Ok(aggregated.into_iter().collect())
}
pub async fn process_as_numbers(
as_numbers: &[String],
mode: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
for as_number in as_numbers {
for &family in &[IpFamily::V4, IpFamily::V6] {
match get_ips_for_as(as_number, family).await {
Ok(set) => {
if set.is_empty() {
println!(
"[asn] No {} routes found for {}",
family.as_str(),
as_number
);
} else {
write_as_ip_list_to_file(as_number, family, &set, mode)?;
}
}
Err(e) => eprintln!(
"[asn] Error processing {} ({}): {}",
as_number,
family.as_str(),
e
),
}
}
}
Ok(())
}