Skip to main content

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    /// Iterate over all unique relationship entries.
186    ///
187    /// Each (asn1, asn2) pair appears once in canonical order (asn1 < asn2).
188    /// Returns both IPv4 and IPv6 entries, tagged by address family.
189    #[allow(dead_code)]
190    pub fn all_entries(&self) -> impl Iterator<Item = As2relExportEntry> + '_ {
191        let v4 = self
192            .v4_rels_map
193            .iter()
194            .filter(move |((a, b), _)| *a < *b)
195            .flat_map(|(_, set)| {
196                set.iter().map(|e| As2relExportEntry {
197                    asn1: e.asn1,
198                    asn2: e.asn2,
199                    rel: e.rel,
200                    paths_count: e.paths_count,
201                    peers_count: e.peers_count,
202                    address_family: 4,
203                })
204            });
205        let v6 = self
206            .v6_rels_map
207            .iter()
208            .filter(move |((a, b), _)| *a < *b)
209            .flat_map(|(_, set)| {
210                set.iter().map(|e| As2relExportEntry {
211                    asn1: e.asn1,
212                    asn2: e.asn2,
213                    rel: e.rel,
214                    paths_count: e.paths_count,
215                    peers_count: e.peers_count,
216                    address_family: 6,
217                })
218            });
219        v4.chain(v6)
220    }
221}
222
223/// A single AS relationship entry for export, with address family tag.
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct As2relExportEntry {
226    pub asn1: u32,
227    pub asn2: u32,
228    pub rel: AsRelationship,
229    pub paths_count: u32,
230    pub peers_count: u32,
231    pub address_family: u8,
232}
233
234impl LazyLoadable for As2relBgpkit {
235    fn reload(&mut self) -> Result<()> {
236        *self = As2relBgpkit::new().map_err(|e| {
237            BgpkitCommonsError::data_source_error(data_sources::BGPKIT, e.to_string())
238        })?;
239        Ok(())
240    }
241
242    fn is_loaded(&self) -> bool {
243        !self.v4_rels_map.is_empty() || !self.v6_rels_map.is_empty()
244    }
245
246    fn loading_status(&self) -> &'static str {
247        if self.is_loaded() {
248            "AS2Rel data loaded"
249        } else {
250            "AS2Rel data not loaded"
251        }
252    }
253}
254
255fn parse_as2rel_data(url: &str) -> Result<Vec<As2relEntry>> {
256    info!("loading AS2REL data from {}", url);
257    let data: Vec<As2relEntry> = oneio::read_json_struct(url)?;
258    Ok(data)
259}
260
261impl BgpkitCommons {
262    pub fn as2rel_lookup(
263        &self,
264        asn1: u32,
265        asn2: u32,
266    ) -> Result<(Vec<As2relBgpkitData>, Vec<As2relBgpkitData>)> {
267        if self.as2rel.is_none() {
268            return Err(BgpkitCommonsError::module_not_loaded(
269                modules::AS2REL,
270                load_methods::LOAD_AS2REL,
271            ));
272        }
273
274        Ok(self.as2rel.as_ref().unwrap().lookup_pair(asn1, asn2))
275    }
276
277    /// Returns an iterator over all unique AS relationship entries.
278    ///
279    /// Requires the `as2rel` module to be loaded via [`load_as2rel`](Self::load_as2rel).
280    #[cfg(feature = "export")]
281    pub fn as2rel_all_entries(&self) -> Result<impl Iterator<Item = As2relExportEntry> + '_> {
282        self.as2rel
283            .as_ref()
284            .ok_or_else(|| {
285                BgpkitCommonsError::module_not_loaded(modules::AS2REL, load_methods::LOAD_AS2REL)
286            })
287            .map(|data| data.all_entries())
288    }
289}