use chrono::Local;
use ipnet::IpNet;
use std::collections::BTreeSet;
use std::error::Error;
use std::process::Stdio;
use tokio::process::Command;
async fn get_ips_for_as(
as_number: &str,
version: &str,
) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
let route_key = match version {
"IPv4" => "route:",
"IPv6" => "route6:",
_ => return Err("Invalid IP version. Must be 'IPv4' or 'IPv6'.".into()),
};
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 mut ipnets = Vec::new();
for line in stdout_str.lines() {
if line.contains(route_key) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
continue;
}
let cidr_str = parts[1];
if let Ok(net) = cidr_str.parse::<IpNet>() {
ipnets.push(net);
}
}
}
let aggregated = IpNet::aggregate(&ipnets);
Ok(aggregated.into_iter().collect())
}
fn write_as_ip_list_to_file(
as_number: &str,
version: &str,
ipnets: &BTreeSet<IpNet>,
mode: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
use std::fs::{self, OpenOptions};
use std::io::Write;
let file_name = format!("AS_{}_{}.txt", as_number, version);
let now = Local::now().format("%Y-%m-%d %H:%M").to_string();
let mut content = format!("# Execution Date and Time: {}\n", now);
for net in ipnets {
content.push_str(&format!("{}\n", net));
}
match mode {
"append" => {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&file_name)?;
file.write_all(content.as_bytes())?;
println!("[asn] Appended IP list to: {}", file_name);
}
_ => {
fs::write(&file_name, &content)?;
println!("[asn] Wrote (overwrite) IP list to: {}", file_name);
}
}
Ok(())
}
pub async fn process_as_numbers(
as_numbers: &[String],
mode: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
for as_number in as_numbers {
for version in ["IPv4", "IPv6"] {
match get_ips_for_as(as_number, version).await {
Ok(set) => {
if set.is_empty() {
println!("[asn] No {} routes found for {}", version, as_number);
} else {
write_as_ip_list_to_file(as_number, version, &set, mode)?;
}
}
Err(e) => eprintln!("[asn] Error processing {} ({}): {}", as_number, version, e),
}
}
}
Ok(())
}