fire_scope/
asn.rs

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
6/// AS番号とIPバージョン(IPv4/IPv6)を指定してWHOISサーバからルート情報を取得し、
7/// IPアドレスの集合を返す。(重複除外+ソートのため BTreeSet)
8pub async fn get_ips_for_as(
9    as_number: &str,
10    family: IpFamily,
11) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
12    // WHOISコマンド: whois -h whois.radb.net -- -i origin ASxxxx
13    let output = Command::new("whois")
14        .arg("-h")
15        .arg("whois.radb.net")
16        .arg("--")
17        .arg(format!("-i origin {}", as_number))
18        // エラー出力を継承
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    // route_key = "route:" or "route6:" を列挙型から取得
30    let route_key = family.route_key();
31
32    // イテレータを使った抽出
33    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    // 集約してBTreeSetへ格納
48    let aggregated = IpNet::aggregate(&ipnets);
49    Ok(aggregated.into_iter().collect())
50}
51
52/// 複数のAS番号を受け取り、それぞれIPv4/IPv6のWHOISルート情報を取得して出力ファイルに書き込む。
53/// main.rsから呼び出す想定のエントリポイント。
54pub async fn process_as_numbers(
55    as_numbers: &[String],
56    mode: &str,
57) -> Result<(), Box<dyn Error + Send + Sync>> {
58    // 同一AS番号に対して IPv4, IPv6 を順次処理
59    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}