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, sync::Arc};
5use tokio::{process::Command, sync::Semaphore};
6
7/// 1つのAS番号に対し、whoisを1回だけ実行し、IPv4/IPv6ルートを同時取得して返す。
8pub async fn get_ips_for_as_once(
9    as_number: &str,
10) -> Result<(BTreeSet<IpNet>, BTreeSet<IpNet>), Box<dyn Error + Send + Sync>> {
11    let output = Command::new("whois")
12        .arg("-h")
13        .arg("whois.radb.net")
14        .arg("--")
15        .arg(format!("-i origin {}", as_number))
16        .stderr(Stdio::inherit())
17        .output()
18        .await?;
19
20    if !output.status.success() {
21        return Err(format!("whois command failed for {}", as_number).into());
22    }
23
24    let stdout_str = String::from_utf8_lossy(&output.stdout);
25
26    // ここでIPv4, IPv6に仕分け
27    let mut v4s = BTreeSet::new();
28    let mut v6s = BTreeSet::new();
29
30    for line in stdout_str.lines() {
31        if line.starts_with("route:") {
32            // 例: "route: 192.0.2.0/24"
33            if let Some(ip_str) = line.split_whitespace().nth(1) {
34                if let Ok(ip) = ip_str.parse::<IpNet>() {
35                    // ip.is_ipv4() の代わりにパターンマッチ
36                    if let IpNet::V4(_) = ip {
37                        v4s.insert(ip);
38                    }
39                }
40            }
41        } else if line.starts_with("route6:") {
42            // 例: "route6: 2001:db8::/32"
43            if let Some(ip_str) = line.split_whitespace().nth(1) {
44                if let Ok(ip) = ip_str.parse::<IpNet>() {
45                    if let IpNet::V6(_) = ip {
46                        v6s.insert(ip);
47                    }
48                }
49            }
50        }
51    }
52
53    // 必要に応じてサブネットをまとめる
54    let aggregated_v4 = IpNet::aggregate(&v4s.iter().copied().collect::<Vec<_>>());
55    let aggregated_v6 = IpNet::aggregate(&v6s.iter().copied().collect::<Vec<_>>());
56
57    Ok((
58        aggregated_v4.into_iter().collect(),
59        aggregated_v6.into_iter().collect(),
60    ))
61}
62
63/// 複数の AS番号を並列で処理し、IPv4/IPv6リストをファイル出力する。
64/// 同時実行数はSemaphoreで制限。
65pub async fn process_as_numbers(
66    as_numbers: &[String],
67    mode: &str,
68    output_format: OutputFormat,
69) -> Result<(), Box<dyn Error + Send + Sync>> {
70    // 同時に叩くwhoisコマンドの上限。必要に応じて調整
71    let max_concurrent = 5;
72    let semaphore = Arc::new(Semaphore::new(max_concurrent));
73
74    let mut handles = Vec::new();
75
76    for as_number in as_numbers {
77        let as_number = as_number.clone();
78        let mode = mode.to_string();
79        let format = output_format;
80        let sem_clone = semaphore.clone();
81
82        // 各ASを並行処理
83        let handle = tokio::spawn(async move {
84            // セマフォで同時実行数を制限
85            let _permit = sem_clone.acquire_owned().await?;
86
87            match get_ips_for_as_once(&as_number).await {
88                Ok((v4set, v6set)) => {
89                    // IPv4/IPv6をファイルに書き出す
90                    if v4set.is_empty() {
91                        println!("[asn] No IPv4 routes found for {}", as_number);
92                    } else {
93                        write_as_ip_list_to_file(&as_number, IpFamily::V4, &v4set, &mode, format)
94                            .await?;
95                    }
96
97                    if v6set.is_empty() {
98                        println!("[asn] No IPv6 routes found for {}", as_number);
99                    } else {
100                        write_as_ip_list_to_file(&as_number, IpFamily::V6, &v6set, &mode, format)
101                            .await?;
102                    }
103                }
104                Err(e) => eprintln!("[asn] Error processing {}: {}", as_number, e),
105            };
106
107            Ok::<(), Box<dyn Error + Send + Sync>>(())
108        });
109        handles.push(handle);
110    }
111
112    // 全タスクが完了するのを待機
113    for h in handles {
114        h.await??;
115    }
116
117    Ok(())
118}