bgpkit_commons/
lib.rs

1//! # Overview
2//!
3//! BGPKIT-Commons is a library for common BGP-related data and functions with a lazy-loading architecture.
4//! Each module can be independently enabled via feature flags, allowing for minimal builds.
5//!
6//! ## Available Modules
7//!
8//! ### [`asinfo`] - Autonomous System Information (requires `asinfo` feature)
9//! **Load Method**: `load_asinfo(as2org, population, hegemony, peeringdb)` or `load_asinfo_cached()`  
10//! **Access Methods**: `asinfo_get(asn)`, `asinfo_all()`  
11//! **Data Sources**: RIPE NCC, CAIDA as2org, APNIC population, IIJ IHR hegemony, PeeringDB  
12//! **Functionality**: AS name resolution, country mapping, organization data, population statistics, hegemony scores
13//!
14//! ### [`as2rel`] - AS Relationship Data (requires `as2rel` feature)
15//! **Load Method**: `load_as2rel()`  
16//! **Access Methods**: `as2rel_lookup(asn1, asn2)`  
17//! **Data Sources**: BGPKIT AS relationship inference  
18//! **Functionality**: Provider-customer, peer-to-peer, and sibling relationships between ASes
19//!
20//! ### [`bogons`] - Bogon Detection (requires `bogons` feature)
21//! **Load Method**: `load_bogons()`  
22//! **Access Methods**: `bogons_match(input)`, `bogons_match_prefix(prefix)`, `bogons_match_asn(asn)`, `get_bogon_prefixes()`, `get_bogon_asns()`  
23//! **Data Sources**: IANA special registries (IPv4, IPv6, ASN)  
24//! **Functionality**: Detect invalid/reserved IP prefixes and ASNs that shouldn't appear in routing
25//!
26//! ### [`countries`] - Country Information (requires `countries` feature)
27//! **Load Method**: `load_countries()`  
28//! **Access Methods**: `country_by_code(code)`, country lookup by name  
29//! **Data Sources**: GeoNames geographical database  
30//! **Functionality**: ISO country code to name mapping and geographical information
31//!
32//! ### [`mrt_collectors`] - MRT Collector Metadata (requires `mrt_collectors` feature)
33//! **Load Methods**: `load_mrt_collectors()`, `load_mrt_collector_peers()`  
34//! **Access Methods**: `mrt_collectors_all()`, `mrt_collector_peers()`, `mrt_collector_peers_full_feed()`  
35//! **Data Sources**: RouteViews and RIPE RIS official APIs  
36//! **Functionality**: BGP collector information, peer details, full-feed vs partial-feed classification
37//!
38//! ### [`rpki`] - RPKI Validation (requires `rpki` feature)
39//! **Load Method**: `load_rpki(optional_date)`  
40//! **Access Methods**: `rpki_validate(prefix, asn)`  
41//! **Data Sources**: RIPE NCC historical data, Cloudflare real-time data  
42//! **Functionality**: Route Origin Authorization (ROA) validation, supports multiple ROAs per prefix
43//!
44//! ## Quick Start
45//!
46//! Add `bgpkit-commons` to your `Cargo.toml`:
47//! ```toml
48//! [dependencies]
49//! bgpkit-commons = "0.8"
50//! ```
51//!
52//! ### Basic Usage Pattern
53//!
54//! All modules follow the same lazy-loading pattern:
55//! 1. Create a mutable `BgpkitCommons` instance
56//! 2. Load the data you need by calling `load_xxx()` methods
57//! 3. Access the data using the corresponding `xxx_yyy()` methods
58//!
59//! ```rust
60//! use bgpkit_commons::BgpkitCommons;
61//!
62//! let mut commons = BgpkitCommons::new();
63//!
64//! // Load bogon data
65//! commons.load_bogons().unwrap();
66//!
67//! // Use the data
68//! if let Ok(is_bogon) = commons.bogons_match("23456") {
69//!     println!("ASN 23456 is a bogon: {}", is_bogon);
70//! }
71//! ```
72//!
73//! ### Working with Multiple Modules
74//!
75//! ```rust
76//! use bgpkit_commons::BgpkitCommons;
77//!
78//! let mut commons = BgpkitCommons::new();
79//!
80//! // Load multiple data sources
81//! commons.load_asinfo(false, false, false, false).unwrap();
82//! commons.load_countries().unwrap();
83//!
84//! // Use the data together
85//! if let Ok(Some(asinfo)) = commons.asinfo_get(13335) {
86//!     println!("AS13335: {} ({})", asinfo.name, asinfo.country);
87//! }
88//! ```
89//!
90//! ## Feature Flags
91//!
92//! ### Module Features
93//! - `asinfo` - AS information with organization and population data
94//! - `as2rel` - AS relationship data
95//! - `bogons` - Bogon prefix and ASN detection  
96//! - `countries` - Country information lookup
97//! - `mrt_collectors` - MRT collector metadata
98//! - `rpki` - RPKI validation functionality
99//!
100//! ### Convenience Features  
101//! - `all` (default) - Enables all modules for backwards compatibility
102//!
103//! ### Minimal Build Example
104//! ```toml
105//! [dependencies]
106//! bgpkit-commons = { version = "0.8", default-features = false, features = ["bogons", "countries"] }
107//! ```
108//!
109//! ## Error Handling
110//!
111//! All access methods return `Result<T>` and will return an error if the corresponding module
112//! hasn't been loaded yet or if there are data validation issues. Error messages include guidance
113//! on which `load_xxx()` method to call. Always call the appropriate `load_xxx()` method before accessing data.
114//!
115//! ## Data Persistence and Reloading
116//!
117//! All loaded data is kept in memory for fast access. Use the `reload()` method to refresh
118//! all currently loaded data sources:
119//!
120//! ```rust
121//! # use bgpkit_commons::BgpkitCommons;
122//! let mut commons = BgpkitCommons::new();
123//! commons.load_bogons().unwrap();
124//!
125//! // Later, reload all loaded data
126//! commons.reload().unwrap();
127//! ```
128
129#![doc(
130    html_logo_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/icon-transparent.png",
131    html_favicon_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/favicon.ico"
132)]
133
134#[cfg(feature = "as2rel")]
135pub mod as2rel;
136#[cfg(feature = "asinfo")]
137pub mod asinfo;
138#[cfg(feature = "bogons")]
139pub mod bogons;
140#[cfg(feature = "countries")]
141pub mod countries;
142#[cfg(feature = "mrt_collectors")]
143pub mod mrt_collectors;
144#[cfg(feature = "rpki")]
145pub mod rpki;
146
147pub mod errors;
148
149// Re-export error types for convenience
150pub use errors::{BgpkitCommonsError, Result};
151
152/// Trait for modules that support lazy loading and reloading of data
153pub trait LazyLoadable {
154    /// Reload the module's data from its external sources
155    fn reload(&mut self) -> Result<()>;
156
157    /// Check if the module's data is currently loaded
158    fn is_loaded(&self) -> bool;
159
160    /// Get a description of the module's current loading status
161    fn loading_status(&self) -> &'static str;
162}
163
164#[derive(Default)]
165pub struct BgpkitCommons {
166    #[cfg(feature = "countries")]
167    countries: Option<crate::countries::Countries>,
168    #[cfg(feature = "rpki")]
169    rpki_trie: Option<crate::rpki::RpkiTrie>,
170    #[cfg(feature = "mrt_collectors")]
171    mrt_collectors: Option<Vec<crate::mrt_collectors::MrtCollector>>,
172    #[cfg(feature = "mrt_collectors")]
173    mrt_collector_peers: Option<Vec<crate::mrt_collectors::MrtCollectorPeer>>,
174    #[cfg(feature = "bogons")]
175    bogons: Option<crate::bogons::Bogons>,
176    #[cfg(feature = "asinfo")]
177    asinfo: Option<crate::asinfo::AsInfoUtils>,
178    #[cfg(feature = "as2rel")]
179    as2rel: Option<crate::as2rel::As2relBgpkit>,
180}
181
182impl BgpkitCommons {
183    pub fn new() -> Self {
184        Self::default()
185    }
186
187    /// Reload all data sources that are already loaded
188    pub fn reload(&mut self) -> Result<()> {
189        #[cfg(feature = "countries")]
190        if self.countries.is_some() {
191            self.load_countries()?;
192        }
193        #[cfg(feature = "rpki")]
194        if let Some(rpki) = self.rpki_trie.as_mut() {
195            rpki.reload()?;
196        }
197        #[cfg(feature = "mrt_collectors")]
198        if self.mrt_collectors.is_some() {
199            self.load_mrt_collectors()?;
200        }
201        #[cfg(feature = "mrt_collectors")]
202        if self.mrt_collector_peers.is_some() {
203            self.load_mrt_collector_peers()?;
204        }
205        #[cfg(feature = "bogons")]
206        if self.bogons.is_some() {
207            self.load_bogons()?;
208        }
209        #[cfg(feature = "asinfo")]
210        if let Some(asinfo) = self.asinfo.as_mut() {
211            asinfo.reload()?;
212        }
213        #[cfg(feature = "as2rel")]
214        if self.as2rel.is_some() {
215            self.load_as2rel()?;
216        }
217
218        Ok(())
219    }
220
221    /// Get loading status for all available modules
222    pub fn loading_status(&self) -> Vec<(&'static str, &'static str)> {
223        #[allow(unused_mut)] // mut needed when any features are enabled
224        let mut status = Vec::new();
225
226        #[cfg(feature = "countries")]
227        if let Some(ref countries) = self.countries {
228            status.push(("countries", countries.loading_status()));
229        } else {
230            status.push(("countries", "Countries data not loaded"));
231        }
232
233        #[cfg(feature = "bogons")]
234        if let Some(ref bogons) = self.bogons {
235            status.push(("bogons", bogons.loading_status()));
236        } else {
237            status.push(("bogons", "Bogons data not loaded"));
238        }
239
240        #[cfg(feature = "rpki")]
241        if let Some(ref rpki) = self.rpki_trie {
242            status.push(("rpki", rpki.loading_status()));
243        } else {
244            status.push(("rpki", "RPKI data not loaded"));
245        }
246
247        #[cfg(feature = "asinfo")]
248        if let Some(ref asinfo) = self.asinfo {
249            status.push(("asinfo", asinfo.loading_status()));
250        } else {
251            status.push(("asinfo", "ASInfo data not loaded"));
252        }
253
254        #[cfg(feature = "as2rel")]
255        if let Some(ref as2rel) = self.as2rel {
256            status.push(("as2rel", as2rel.loading_status()));
257        } else {
258            status.push(("as2rel", "AS2Rel data not loaded"));
259        }
260
261        #[cfg(feature = "mrt_collectors")]
262        {
263            if self.mrt_collectors.is_some() {
264                status.push(("mrt_collectors", "MRT collectors data loaded"));
265            } else {
266                status.push(("mrt_collectors", "MRT collectors data not loaded"));
267            }
268
269            if self.mrt_collector_peers.is_some() {
270                status.push(("mrt_collector_peers", "MRT collector peers data loaded"));
271            } else {
272                status.push(("mrt_collector_peers", "MRT collector peers data not loaded"));
273            }
274        }
275
276        status
277    }
278
279    /// Load countries data
280    #[cfg(feature = "countries")]
281    pub fn load_countries(&mut self) -> Result<()> {
282        self.countries = Some(crate::countries::Countries::new()?);
283        Ok(())
284    }
285
286    /// Load RPKI data
287    #[cfg(feature = "rpki")]
288    pub fn load_rpki(&mut self, date_opt: Option<chrono::NaiveDate>) -> Result<()> {
289        if let Some(date) = date_opt {
290            self.rpki_trie = Some(rpki::RpkiTrie::from_ripe_historical(date)?);
291        } else {
292            self.rpki_trie = Some(rpki::RpkiTrie::from_cloudflare()?);
293        }
294        Ok(())
295    }
296
297    /// Load MRT mrt_collectors data
298    #[cfg(feature = "mrt_collectors")]
299    pub fn load_mrt_collectors(&mut self) -> Result<()> {
300        self.mrt_collectors = Some(crate::mrt_collectors::get_all_collectors()?);
301        Ok(())
302    }
303
304    /// Load MRT mrt_collectors data
305    #[cfg(feature = "mrt_collectors")]
306    pub fn load_mrt_collector_peers(&mut self) -> Result<()> {
307        self.mrt_collector_peers = Some(crate::mrt_collectors::get_mrt_collector_peers()?);
308        Ok(())
309    }
310
311    /// Load bogons data
312    #[cfg(feature = "bogons")]
313    pub fn load_bogons(&mut self) -> Result<()> {
314        self.bogons = Some(crate::bogons::Bogons::new()?);
315        Ok(())
316    }
317
318    /// Load AS name and country data
319    #[cfg(feature = "asinfo")]
320    pub fn load_asinfo(
321        &mut self,
322        load_as2org: bool,
323        load_population: bool,
324        load_hegemony: bool,
325        load_peeringdb: bool,
326    ) -> Result<()> {
327        self.asinfo = Some(crate::asinfo::AsInfoUtils::new(
328            load_as2org,
329            load_population,
330            load_hegemony,
331            load_peeringdb,
332        )?);
333        Ok(())
334    }
335
336    #[cfg(feature = "asinfo")]
337    pub fn load_asinfo_cached(&mut self) -> Result<()> {
338        self.asinfo = Some(crate::asinfo::AsInfoUtils::new_from_cached()?);
339        Ok(())
340    }
341
342    /// Load AS-level relationship data
343    #[cfg(feature = "as2rel")]
344    pub fn load_as2rel(&mut self) -> Result<()> {
345        self.as2rel = Some(crate::as2rel::As2relBgpkit::new()?);
346        Ok(())
347    }
348}