fire_scope/
asn.rs

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
7/// AS番号とIPバージョン(IPv4/IPv6)を指定してWHOISサーバからルート情報を取得し、
8/// IPアドレスの集合を返す。(重複除外+ソートのため BTreeSet)
9pub async fn get_ips_for_as(
10    as_number: &str,
11    family: IpFamily,
12) -> Result<BTreeSet<IpNet>, Box<dyn Error + Send + Sync>> {
13    // WHOISコマンド: whois -h whois.radb.net -- -i origin ASxxxx
14    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    // 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    output_format: OutputFormat,
58) -> Result<(), Box<dyn Error + Send + Sync>> {
59    // 同一AS番号に対して IPv4, IPv6 を順次処理
60    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                        // nft/txtの出力切り替え
72                        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}