bgpkit_commons/as2rel/
mod.rs

1//! AS-level relationship generated by BGPKIT
2//!
3//! Raw data files available at: <https://data.bgpkit.com/as2rel/>
4//! * [as2rel-latest.json.bz2](https://data.bgpkit.com/as2rel/as2rel-latest.json.bz2): latest combined
5//! * [as2rel-v4-latest.json.bz2](https://data.bgpkit.com/as2rel/as2rel-v4-latest.json.bz2): latest IPv4 relationship
6//! * [as2rel-v6-latest.json.bz2](https://data.bgpkit.com/as2rel/as2rel-v6-latest.json.bz2): latest IPv6 relationship
7
8use crate::errors::{data_sources, load_methods, modules};
9use crate::{BgpkitCommons, BgpkitCommonsError, LazyLoadable, Result};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet};
12use std::hash::Hash;
13use tracing::info;
14
15#[allow(dead_code)]
16const AS2REL_LATEST_COMBINED: &str = "https://data.bgpkit.com/as2rel/as2rel-latest.json.bz2";
17
18const AS2REL_LATEST_V4: &str = "https://data.bgpkit.com/as2rel/as2rel-v4-latest.json.bz2";
19const AS2REL_LATEST_V6: &str = "https://data.bgpkit.com/as2rel/as2rel-v6-latest.json.bz2";
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum AsRelationship {
23    ProviderCustomer,
24    CustomerProvider,
25    PeerPeer,
26}
27
28impl Serialize for AsRelationship {
29    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
30    where
31        S: serde::Serializer,
32    {
33        match self {
34            AsRelationship::ProviderCustomer => serializer.serialize_str("pc"),
35            AsRelationship::CustomerProvider => serializer.serialize_str("cp"),
36            AsRelationship::PeerPeer => serializer.serialize_str("pp"),
37        }
38    }
39}
40
41impl<'de> Deserialize<'de> for AsRelationship {
42    fn deserialize<D>(deserializer: D) -> std::result::Result<AsRelationship, D::Error>
43    where
44        D: serde::Deserializer<'de>,
45    {
46        let s = i8::deserialize(deserializer)?;
47        match s {
48            -1 | 1 => Ok(AsRelationship::ProviderCustomer),
49            0 => Ok(AsRelationship::PeerPeer),
50            _ => Err(serde::de::Error::custom("invalid relationship")),
51        }
52    }
53}
54
55#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
56struct As2relEntry {
57    asn1: u32,
58    asn2: u32,
59    paths_count: u32,
60    peers_count: u32,
61    rel: AsRelationship,
62}
63
64impl PartialEq for As2relEntry {
65    fn eq(&self, other: &Self) -> bool {
66        self.asn1 == other.asn1 && self.asn2 == other.asn2 && self.rel == other.rel
67    }
68}
69impl Eq for As2relEntry {}
70
71impl Hash for As2relEntry {
72    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
73        self.asn1.hash(state);
74        self.asn2.hash(state);
75        self.rel.hash(state);
76    }
77}
78
79impl As2relEntry {
80    fn reverse(&self) -> Self {
81        Self {
82            asn1: self.asn2,
83            asn2: self.asn1,
84            paths_count: self.paths_count,
85            peers_count: self.peers_count,
86            rel: match self.rel {
87                AsRelationship::ProviderCustomer => AsRelationship::CustomerProvider,
88                AsRelationship::CustomerProvider => AsRelationship::ProviderCustomer,
89                AsRelationship::PeerPeer => AsRelationship::PeerPeer,
90            },
91        }
92    }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct As2relBgpkit {
97    v4_rels_map: HashMap<(u32, u32), HashSet<As2relEntry>>,
98    v6_rels_map: HashMap<(u32, u32), HashSet<As2relEntry>>,
99    v4_max_peer_count: u32,
100    v6_max_peer_count: u32,
101}
102
103#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
104pub struct As2relBgpkitData {
105    pub rel: AsRelationship,
106    pub peers_count: u32,
107    pub max_peer_count: u32,
108}
109
110impl As2relBgpkit {
111    pub fn new() -> Result<Self> {
112        let v4_rels = parse_as2rel_data(AS2REL_LATEST_V4)?;
113        let v6_rels = parse_as2rel_data(AS2REL_LATEST_V6)?;
114        let mut v4_rels_map = HashMap::new();
115        let mut v6_rels_map = HashMap::new();
116        let mut v4_max_peer_count = 0;
117        let mut v6_max_peer_count = 0;
118        for entry in v4_rels {
119            v4_rels_map
120                .entry((entry.asn1, entry.asn2))
121                .or_insert_with(HashSet::new)
122                .insert(entry);
123            v4_rels_map
124                .entry((entry.asn2, entry.asn1))
125                .or_insert_with(HashSet::new)
126                .insert(entry.reverse());
127
128            v4_max_peer_count = v4_max_peer_count.max(entry.peers_count);
129        }
130        for entry in v6_rels {
131            v6_rels_map
132                .entry((entry.asn1, entry.asn2))
133                .or_insert_with(HashSet::new)
134                .insert(entry);
135            v6_rels_map
136                .entry((entry.asn2, entry.asn1))
137                .or_insert_with(HashSet::new)
138                .insert(entry.reverse());
139
140            v6_max_peer_count = v6_max_peer_count.max(entry.peers_count);
141        }
142        Ok(Self {
143            v4_rels_map,
144            v6_rels_map,
145            v4_max_peer_count,
146            v6_max_peer_count,
147        })
148    }
149
150    pub fn lookup_pair(
151        &self,
152        asn1: u32,
153        asn2: u32,
154    ) -> (Vec<As2relBgpkitData>, Vec<As2relBgpkitData>) {
155        let v4_entry_set = self.v4_rels_map.get(&(asn1, asn2));
156        let v6_entry_set = self.v6_rels_map.get(&(asn1, asn2));
157
158        let v4_entries = v4_entry_set
159            .map(|set| {
160                set.iter()
161                    .map(|entry| As2relBgpkitData {
162                        rel: entry.rel,
163                        peers_count: entry.peers_count,
164                        max_peer_count: self.v4_max_peer_count,
165                    })
166                    .collect()
167            })
168            .unwrap_or_default();
169
170        let v6_entries = v6_entry_set
171            .map(|set| {
172                set.iter()
173                    .map(|entry| As2relBgpkitData {
174                        rel: entry.rel,
175                        peers_count: entry.peers_count,
176                        max_peer_count: self.v6_max_peer_count,
177                    })
178                    .collect()
179            })
180            .unwrap_or_default();
181
182        (v4_entries, v6_entries)
183    }
184}
185
186impl LazyLoadable for As2relBgpkit {
187    fn reload(&mut self) -> Result<()> {
188        *self = As2relBgpkit::new().map_err(|e| {
189            BgpkitCommonsError::data_source_error(data_sources::BGPKIT, e.to_string())
190        })?;
191        Ok(())
192    }
193
194    fn is_loaded(&self) -> bool {
195        !self.v4_rels_map.is_empty() || !self.v6_rels_map.is_empty()
196    }
197
198    fn loading_status(&self) -> &'static str {
199        if self.is_loaded() {
200            "AS2Rel data loaded"
201        } else {
202            "AS2Rel data not loaded"
203        }
204    }
205}
206
207fn parse_as2rel_data(url: &str) -> Result<Vec<As2relEntry>> {
208    info!("loading AS2REL data from {}", url);
209    let data: Vec<As2relEntry> = oneio::read_json_struct(url)?;
210    Ok(data)
211}
212
213impl BgpkitCommons {
214    pub fn as2rel_lookup(
215        &self,
216        asn1: u32,
217        asn2: u32,
218    ) -> Result<(Vec<As2relBgpkitData>, Vec<As2relBgpkitData>)> {
219        if self.as2rel.is_none() {
220            return Err(BgpkitCommonsError::module_not_loaded(
221                modules::AS2REL,
222                load_methods::LOAD_AS2REL,
223            ));
224        }
225
226        Ok(self.as2rel.as_ref().unwrap().lookup_pair(asn1, asn2))
227    }
228}