Skip to main content

fire_scope/
asn.rs

1use crate::common::{IpFamily, OutputFormat};
2use crate::error::AppError;
3use crate::output::write_as_ip_list_to_file;
4use ipnet::IpNet;
5use reqwest::Client;
6use serde_json::Value;
7use std::{collections::BTreeSet, str::FromStr, sync::Arc};
8use tokio::sync::Semaphore;
9use crate::constants::MAX_JSON_DOWNLOAD_BYTES;
10use crate::fetch::fetch_json_with_limit;
11use crate::common::debug_log;
12
13/// AS の発表プレフィックスを複数ソースから取得する(RIPEstat 優先、ARIN RDAP をフォールバック)
14/// RPKI検証なし
15pub async fn get_prefixes_via_rdap(
16    client: &Client,
17    as_number: &str,
18) -> Result<(BTreeSet<IpNet>, BTreeSet<IpNet>), AppError> {
19    // 1) RIPEstat announced-prefixes API
20    match fetch_ripe_stat_prefixes(client, as_number).await {
21        Ok(mut nets) => {
22            // フォールバックとして ARIN も併合(失敗は無視)
23            if let Ok(mut arin) = fetch_arin_originas_prefixes(client, as_number).await {
24                nets.append(&mut arin);
25            }
26            let (v4set, v6set) = dedup_and_partition(&nets);
27            return Ok((v4set, v6set));
28        }
29        Err(e) => {
30            debug_log(format!("RIPEstat fetch failed for AS{}: {}", as_number, e));
31            // 2) ARIN OriginAS RDAP(米地域中心、非網羅)
32            let nets = fetch_arin_originas_prefixes(client, as_number).await?;
33            let (v4set, v6set) = dedup_and_partition(&nets);
34            return Ok((v4set, v6set));
35        }
36    }
37}
38
39/// ARIN OriginAS RDAP 応答から CIDR を抽出
40fn extract_prefixes_from_arin(v: &Value) -> Result<Vec<IpNet>, AppError> {
41    let mut nets = Vec::new();
42    if let Some(arr) = v
43        .get("arin_originas0_networkSearchResults")
44        .and_then(|v| v.as_array())
45    {
46        for obj in arr {
47            let (prefix_key, len_key) = match obj.get("ipVersion").and_then(|v| v.as_str()) {
48                Some("v4") => ("v4prefix", "length"),
49                Some("v6") => ("v6prefix", "length"),
50                _ => continue,
51            };
52            if let (Some(prefix), Some(len)) = (
53                obj.get(prefix_key).and_then(|v| v.as_str()),
54                obj.get(len_key),
55            ) {
56                let cidr = format!("{}/{}", prefix, len);
57                if let Ok(net) = IpNet::from_str(&cidr) {
58                    nets.push(net);
59                }
60            }
61        }
62    }
63    Ok(nets)
64}
65
66/// RIPEstat: Announced Prefixes API から CIDR を抽出
67async fn fetch_ripe_stat_prefixes(client: &Client, as_number: &str) -> Result<Vec<IpNet>, AppError> {
68    // https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS{asn}
69    let url = format!(
70        "https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS{}",
71        as_number
72    );
73    let json: Value = fetch_json_with_limit(client, &url, MAX_JSON_DOWNLOAD_BYTES).await?;
74    let mut nets = Vec::new();
75    if let Some(prefixes) = json.get("data").and_then(|d| d.get("prefixes")).and_then(|p| p.as_array()) {
76        for obj in prefixes {
77            if let Some(pfx) = obj.get("prefix").and_then(|v| v.as_str()) {
78                if let Ok(net) = IpNet::from_str(pfx) {
79                    nets.push(net);
80                }
81            }
82        }
83    }
84    Ok(nets)
85}
86
87/// ARIN 独自 RDAP OriginAS ネットワーク API
88async fn fetch_arin_originas_prefixes(client: &Client, as_number: &str) -> Result<Vec<IpNet>, AppError> {
89    let base = "https://rdap.arin.net/registry";
90    let url = format!("{base}/arin_originas0_networksbyoriginas/{as_number}");
91    let json: Value = fetch_json_with_limit(client, &url, MAX_JSON_DOWNLOAD_BYTES).await?;
92    extract_prefixes_from_arin(&json)
93}
94
95/// Vec<IpNet> → (IPv4, IPv6) 集合に分割し aggregate
96fn dedup_and_partition(nets: &[IpNet]) -> (BTreeSet<IpNet>, BTreeSet<IpNet>) {
97    let agg = IpNet::aggregate(&nets.to_vec());
98    let mut v4 = BTreeSet::new();
99    let mut v6 = BTreeSet::new();
100    for net in agg {
101        match net {
102            IpNet::V4(_) => {
103                v4.insert(net);
104            }
105            IpNet::V6(_) => {
106                v6.insert(net);
107            }
108        }
109    }
110    (v4, v6)
111}
112
113/// 複数 AS を並列取得してファイル出力
114pub async fn process_as_numbers(
115    client: &Client,
116    as_numbers: &[String],
117    output_format: OutputFormat,
118    concurrency: usize,
119) -> Result<(), AppError> {
120    let max_concurrent = if concurrency == 0 { 1 } else { concurrency };
121    let semaphore = Arc::new(Semaphore::new(max_concurrent));
122
123    let handles = as_numbers
124        .iter()
125        .map(|asn| {
126            let asn_cloned = asn.clone();
127            let fmt_c = output_format;
128            let client_c = client.clone();
129            let sem_c = semaphore.clone();
130            tokio::spawn(async move {
131                let _permit = sem_c.acquire_owned().await?;
132                match get_prefixes_via_rdap(&client_c, &asn_cloned).await {
133                    Ok((v4, v6)) => {
134                        write_ip_list(&asn_cloned, IpFamily::V4, &v4, fmt_c).await?;
135                        write_ip_list(&asn_cloned, IpFamily::V6, &v6, fmt_c).await?;
136                    }
137                    Err(e) => debug_log(format!("Error processing {}: {}", asn_cloned, e)),
138                };
139                Ok::<(), AppError>(())
140            })
141        })
142        .collect::<Vec<_>>();
143
144    for h in handles {
145        h.await??;
146    }
147    Ok(())
148}
149
150/// ファイル書き出しヘルパ
151async fn write_ip_list(
152    as_number: &str,
153    ip_family: IpFamily,
154    ip_set: &BTreeSet<IpNet>,
155    output_format: OutputFormat,
156) -> Result<(), AppError> {
157    if ip_set.is_empty() {
158        debug_log(format!("No {} routes for {}", ip_family.as_str(), as_number));
159    } else {
160        write_as_ip_list_to_file(as_number, ip_family, ip_set, output_format).await?;
161    }
162    Ok(())
163}