Skip to main content

bgpkit_commons/
lib.rs

1//! # Overview
2//!
3//! `bgpkit-commons` is a library for common BGP-related data and functions with a lazy-loading
4//! architecture. Each module can be independently enabled via feature flags, allowing for minimal builds.
5//!
6//! ## Quick Start
7//!
8//! Add `bgpkit-commons` to your `Cargo.toml`:
9//!
10//! ```toml
11//! [dependencies]
12//! bgpkit-commons = "0.10"
13//! ```
14//!
15//! All modules follow the same pattern: create a [`BgpkitCommons`] instance, call a `load_xxx()`
16//! method to fetch data, then use `xxx_yyy()` methods to access it.
17//!
18//! ```rust
19//! # #[cfg(feature = "bogons")]
20//! # fn main() {
21//! use bgpkit_commons::BgpkitCommons;
22//!
23//! let mut commons = BgpkitCommons::new();
24//! commons.load_bogons().unwrap();
25//!
26//! if let Ok(is_bogon) = commons.bogons_match("23456") {
27//!     println!("ASN 23456 is a bogon: {}", is_bogon);
28//! }
29//! # }
30//! # #[cfg(not(feature = "bogons"))]
31//! # fn main() {}
32//! ```
33//!
34//! ## Modules
35//!
36//! ### [`asinfo`] — Autonomous System Information
37//!
38//! Feature: `asinfo` | Sources: RIPE NCC, CAIDA as2org, APNIC population, IIJ IHR hegemony, PeeringDB
39//!
40//! - Load: `load_asinfo(as2org, population, hegemony, peeringdb)`, `load_asinfo_cached()`, `load_asinfo_with(builder)`
41//! - Access: `asinfo_get(asn)`, `asinfo_all()`, `asinfo_are_siblings(asn1, asn2)`
42//! - AS name resolution, country mapping, organization data, population statistics, hegemony scores
43//!
44//! ### [`as2rel`] — AS Relationship Data
45//!
46//! Feature: `as2rel` | Source: BGPKIT AS relationship inference
47//!
48//! - Load: `load_as2rel()`
49//! - Access: `as2rel_lookup(asn1, asn2)`
50//! - Provider-customer, peer-to-peer, and sibling relationships between ASes
51//!
52//! ### [`bogons`] — Bogon Detection
53//!
54//! Feature: `bogons` | Source: IANA special registries (IPv4, IPv6, ASN)
55//!
56//! - Load: `load_bogons()`
57//! - Access: `bogons_match(input)`, `bogons_match_prefix(prefix)`, `bogons_match_asn(asn)`, `get_bogon_prefixes()`, `get_bogon_asns()`
58//! - Detect invalid/reserved IP prefixes and ASNs that shouldn't appear in routing
59//!
60//! ### [`countries`] — Country Information
61//!
62//! Feature: `countries` | Source: GeoNames geographical database
63//!
64//! - Load: `load_countries()`
65//! - Access: `country_by_code(code)`, `country_by_code3(code)`, `country_by_name(name)`, `country_all()`
66//! - ISO country code to name mapping and geographical information
67//!
68//! ### [`mrt_collectors`] — MRT Collector Metadata
69//!
70//! Feature: `mrt_collectors` | Sources: RouteViews and RIPE RIS official APIs
71//!
72//! - Load: `load_mrt_collectors()`, `load_mrt_collector_peers()`
73//! - Access: `mrt_collectors_all()`, `mrt_collectors_by_name(name)`, `mrt_collectors_by_country(country)`, `mrt_collector_peers_all()`, `mrt_collector_peers_full_feed()`
74//! - BGP collector information, peer details, full-feed vs partial-feed classification
75//!
76//! ### [`rpki`] — RPKI Validation
77//!
78//! Feature: `rpki` | Sources: Cloudflare (real-time), RIPE NCC historical, RPKIviews historical, RPKISPOOL historical
79//!
80//! - Load: `load_rpki(optional_date)`, `load_rpki_historical(date, source)`, `load_rpki_from_files(urls, source, date)`
81//! - Access: `rpki_validate(asn, prefix)`, `rpki_validate_check_expiry(asn, prefix, timestamp)`, `rpki_lookup_by_prefix(prefix)`, `rpki_lookup_aspa(customer_asn)`
82//! - Route Origin Authorization (ROA) and ASPA validation, supports real-time and historical sources
83//!
84//! ## Examples
85//!
86//! ### Loading multiple modules
87//!
88//! ```rust
89//! # #[cfg(all(feature = "asinfo", feature = "countries"))]
90//! # fn main() {
91//! use bgpkit_commons::BgpkitCommons;
92//!
93//! let mut commons = BgpkitCommons::new();
94//! commons.load_asinfo(false, false, false, false).unwrap();
95//! commons.load_countries().unwrap();
96//!
97//! if let Ok(Some(asinfo)) = commons.asinfo_get(13335) {
98//!     println!("AS13335: {} ({})", asinfo.name, asinfo.country);
99//! }
100//! # }
101//! # #[cfg(not(all(feature = "asinfo", feature = "countries")))]
102//! # fn main() {}
103//! ```
104//!
105//! ### Using AsInfoBuilder
106//!
107//! ```rust
108//! # #[cfg(feature = "asinfo")]
109//! # fn main() {
110//! use bgpkit_commons::BgpkitCommons;
111//!
112//! let mut commons = BgpkitCommons::new();
113//! let builder = commons.asinfo_builder()
114//!     .with_as2org()
115//!     .with_peeringdb();
116//! commons.load_asinfo_with(builder).unwrap();
117//!
118//! if let Ok(are_siblings) = commons.asinfo_are_siblings(13335, 132892) {
119//!     println!("AS13335 and AS132892 are siblings: {}", are_siblings);
120//! }
121//! # }
122//! # #[cfg(not(feature = "asinfo"))]
123//! # fn main() {}
124//! ```
125//!
126//! ### Loading historical RPKI data
127//!
128//! ```rust,no_run
129//! # #[cfg(feature = "rpki")]
130//! # fn main() {
131//! use bgpkit_commons::BgpkitCommons;
132//! use bgpkit_commons::rpki::{HistoricalRpkiSource, RpkiViewsCollector};
133//! use chrono::NaiveDate;
134//!
135//! let mut commons = BgpkitCommons::new();
136//! let date = NaiveDate::from_ymd_opt(2024, 1, 4).unwrap();
137//!
138//! // Load from RIPE NCC historical archives
139//! commons.load_rpki_historical(date, HistoricalRpkiSource::Ripe).unwrap();
140//!
141//! // Or load from RPKIviews collectors
142//! let source = HistoricalRpkiSource::RpkiViews(RpkiViewsCollector::KerfuffleNet);
143//! commons.load_rpki_historical(date, source).unwrap();
144//!
145//! // List available files for a date
146//! let files = commons.list_rpki_files(date, HistoricalRpkiSource::Ripe).unwrap();
147//! # }
148//! # #[cfg(not(feature = "rpki"))]
149//! # fn main() {}
150//! ```
151//!
152//! ### Direct module access
153//!
154//! Modules can also be used directly without `BgpkitCommons`:
155//!
156//! ```rust
157//! # #[cfg(feature = "bogons")]
158//! # fn main() {
159//! use bgpkit_commons::bogons::Bogons;
160//! let bogons = Bogons::new().unwrap();
161//! # }
162//! # #[cfg(not(feature = "bogons"))]
163//! # fn main() {}
164//! ```
165//!
166//! ## Feature Flags
167//!
168//! | Feature | Description |
169//! |---------|-------------|
170//! | `asinfo` | AS information: names, countries, organizations, population, hegemony |
171//! | `as2rel` | AS relationship data |
172//! | `bogons` | Bogon prefix and ASN detection |
173//! | `countries` | Country information lookup |
174//! | `mrt_collectors` | MRT collector metadata |
175//! | `rpki` | RPKI validation (ROA and ASPA) |
176//! | `all` *(default)* | Enables all modules |
177//!
178//! For a minimal build:
179//!
180//! ```toml
181//! [dependencies]
182//! bgpkit-commons = { version = "0.10", default-features = false, features = ["bogons", "countries"] }
183//! ```
184
185#![doc(
186    html_logo_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/icon-transparent.png",
187    html_favicon_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/favicon.ico"
188)]
189
190#[cfg(feature = "as2rel")]
191pub mod as2rel;
192#[cfg(feature = "asinfo")]
193pub mod asinfo;
194#[cfg(feature = "bogons")]
195pub mod bogons;
196#[cfg(feature = "countries")]
197pub mod countries;
198#[cfg(feature = "mrt_collectors")]
199pub mod mrt_collectors;
200#[cfg(feature = "rpki")]
201pub mod rpki;
202
203pub mod errors;
204
205// Re-export error types for convenience
206pub use errors::{BgpkitCommonsError, Result};
207
208/// Trait for modules that support lazy loading and reloading of data
209pub trait LazyLoadable {
210    /// Reload the module's data from its external sources
211    fn reload(&mut self) -> Result<()>;
212
213    /// Check if the module's data is currently loaded
214    fn is_loaded(&self) -> bool;
215
216    /// Get a description of the module's current loading status
217    fn loading_status(&self) -> &'static str;
218}
219
220#[derive(Default)]
221pub struct BgpkitCommons {
222    #[cfg(feature = "countries")]
223    countries: Option<crate::countries::Countries>,
224    #[cfg(feature = "rpki")]
225    rpki_trie: Option<crate::rpki::RpkiTrie>,
226    #[cfg(feature = "mrt_collectors")]
227    mrt_collectors: Option<Vec<crate::mrt_collectors::MrtCollector>>,
228    #[cfg(feature = "mrt_collectors")]
229    mrt_collector_peers: Option<Vec<crate::mrt_collectors::MrtCollectorPeer>>,
230    #[cfg(feature = "bogons")]
231    bogons: Option<crate::bogons::Bogons>,
232    #[cfg(feature = "asinfo")]
233    asinfo: Option<crate::asinfo::AsInfoUtils>,
234    #[cfg(feature = "as2rel")]
235    as2rel: Option<crate::as2rel::As2relBgpkit>,
236}
237
238impl BgpkitCommons {
239    pub fn new() -> Self {
240        Self::default()
241    }
242
243    /// Reload all data sources that are already loaded
244    pub fn reload(&mut self) -> Result<()> {
245        #[cfg(feature = "countries")]
246        if self.countries.is_some() {
247            self.load_countries()?;
248        }
249        #[cfg(feature = "rpki")]
250        if let Some(rpki) = self.rpki_trie.as_mut() {
251            rpki.reload()?;
252        }
253        #[cfg(feature = "mrt_collectors")]
254        if self.mrt_collectors.is_some() {
255            self.load_mrt_collectors()?;
256        }
257        #[cfg(feature = "mrt_collectors")]
258        if self.mrt_collector_peers.is_some() {
259            self.load_mrt_collector_peers()?;
260        }
261        #[cfg(feature = "bogons")]
262        if self.bogons.is_some() {
263            self.load_bogons()?;
264        }
265        #[cfg(feature = "asinfo")]
266        if let Some(asinfo) = self.asinfo.as_mut() {
267            asinfo.reload()?;
268        }
269        #[cfg(feature = "as2rel")]
270        if self.as2rel.is_some() {
271            self.load_as2rel()?;
272        }
273
274        Ok(())
275    }
276
277    /// Get loading status for all available modules
278    pub fn loading_status(&self) -> Vec<(&'static str, &'static str)> {
279        #[allow(unused_mut)] // mut needed when any features are enabled
280        let mut status = Vec::new();
281
282        #[cfg(feature = "countries")]
283        if let Some(ref countries) = self.countries {
284            status.push(("countries", countries.loading_status()));
285        } else {
286            status.push(("countries", "Countries data not loaded"));
287        }
288
289        #[cfg(feature = "bogons")]
290        if let Some(ref bogons) = self.bogons {
291            status.push(("bogons", bogons.loading_status()));
292        } else {
293            status.push(("bogons", "Bogons data not loaded"));
294        }
295
296        #[cfg(feature = "rpki")]
297        if let Some(ref rpki) = self.rpki_trie {
298            status.push(("rpki", rpki.loading_status()));
299        } else {
300            status.push(("rpki", "RPKI data not loaded"));
301        }
302
303        #[cfg(feature = "asinfo")]
304        if let Some(ref asinfo) = self.asinfo {
305            status.push(("asinfo", asinfo.loading_status()));
306        } else {
307            status.push(("asinfo", "ASInfo data not loaded"));
308        }
309
310        #[cfg(feature = "as2rel")]
311        if let Some(ref as2rel) = self.as2rel {
312            status.push(("as2rel", as2rel.loading_status()));
313        } else {
314            status.push(("as2rel", "AS2Rel data not loaded"));
315        }
316
317        #[cfg(feature = "mrt_collectors")]
318        {
319            if self.mrt_collectors.is_some() {
320                status.push(("mrt_collectors", "MRT collectors data loaded"));
321            } else {
322                status.push(("mrt_collectors", "MRT collectors data not loaded"));
323            }
324
325            if self.mrt_collector_peers.is_some() {
326                status.push(("mrt_collector_peers", "MRT collector peers data loaded"));
327            } else {
328                status.push(("mrt_collector_peers", "MRT collector peers data not loaded"));
329            }
330        }
331
332        status
333    }
334
335    /// Load countries data
336    #[cfg(feature = "countries")]
337    pub fn load_countries(&mut self) -> Result<()> {
338        self.countries = Some(crate::countries::Countries::new()?);
339        Ok(())
340    }
341
342    /// Load RPKI data from Cloudflare (real-time) or historical archives
343    ///
344    /// - If `date_opt` is `None`, loads real-time data from Cloudflare
345    /// - If `date_opt` is `Some(date)`, loads historical data from RIPE NCC by default
346    ///
347    /// For more control over the data source, use `load_rpki_historical()` instead.
348    #[cfg(feature = "rpki")]
349    pub fn load_rpki(&mut self, date_opt: Option<chrono::NaiveDate>) -> Result<()> {
350        if let Some(date) = date_opt {
351            self.rpki_trie = Some(rpki::RpkiTrie::from_ripe_historical(date)?);
352        } else {
353            self.rpki_trie = Some(rpki::RpkiTrie::from_cloudflare()?);
354        }
355        Ok(())
356    }
357
358    /// Load RPKI data from a specific historical data source
359    ///
360    /// This allows you to choose between RIPE NCC and RPKIviews for historical data.
361    ///
362    /// # Example
363    ///
364    /// ```rust,no_run
365    /// use bgpkit_commons::BgpkitCommons;
366    /// use bgpkit_commons::rpki::{HistoricalRpkiSource, RpkiViewsCollector};
367    /// use chrono::NaiveDate;
368    ///
369    /// let mut commons = BgpkitCommons::new();
370    /// let date = NaiveDate::from_ymd_opt(2024, 1, 4).unwrap();
371    ///
372    /// // Load from RIPE NCC
373    /// commons.load_rpki_historical(date, HistoricalRpkiSource::Ripe).unwrap();
374    ///
375    /// // Or load from RPKIviews
376    /// let source = HistoricalRpkiSource::RpkiViews(RpkiViewsCollector::KerfuffleNet);
377    /// commons.load_rpki_historical(date, source).unwrap();
378    /// ```
379    #[cfg(feature = "rpki")]
380    pub fn load_rpki_historical(
381        &mut self,
382        date: chrono::NaiveDate,
383        source: rpki::HistoricalRpkiSource,
384    ) -> Result<()> {
385        match source {
386            rpki::HistoricalRpkiSource::Ripe => {
387                self.rpki_trie = Some(rpki::RpkiTrie::from_ripe_historical(date)?);
388            }
389            rpki::HistoricalRpkiSource::RpkiViews(collector) => {
390                self.rpki_trie = Some(rpki::RpkiTrie::from_rpkiviews(collector, date)?);
391            }
392            rpki::HistoricalRpkiSource::RpkiSpools(collector) => {
393                self.rpki_trie = Some(rpki::RpkiTrie::from_rpkispools(collector, date)?);
394            }
395        }
396        Ok(())
397    }
398
399    /// Load RPKI data from specific file URLs
400    ///
401    /// This allows loading from specific archive files, which is useful when you want
402    /// to process multiple files or use specific timestamps.
403    ///
404    /// # Arguments
405    ///
406    /// * `urls` - A slice of URLs pointing to RPKI data files
407    /// * `source` - The type of data source (RIPE or RPKIviews) - determines how files are parsed
408    /// * `date` - Optional date to associate with the loaded data
409    ///
410    /// # Example
411    ///
412    /// ```rust,no_run
413    /// use bgpkit_commons::BgpkitCommons;
414    /// use bgpkit_commons::rpki::HistoricalRpkiSource;
415    ///
416    /// let mut commons = BgpkitCommons::new();
417    /// let urls = vec![
418    ///     "https://example.com/rpki-20240104T144128Z.tgz".to_string(),
419    /// ];
420    /// commons.load_rpki_from_files(&urls, HistoricalRpkiSource::RpkiViews(
421    ///     bgpkit_commons::rpki::RpkiViewsCollector::KerfuffleNet
422    /// ), None).unwrap();
423    /// ```
424    #[cfg(feature = "rpki")]
425    pub fn load_rpki_from_files(
426        &mut self,
427        urls: &[String],
428        source: rpki::HistoricalRpkiSource,
429        date: Option<chrono::NaiveDate>,
430    ) -> Result<()> {
431        match source {
432            rpki::HistoricalRpkiSource::Ripe => {
433                self.rpki_trie = Some(rpki::RpkiTrie::from_ripe_files(urls, date)?);
434            }
435            rpki::HistoricalRpkiSource::RpkiViews(_) => {
436                self.rpki_trie = Some(rpki::RpkiTrie::from_rpkiviews_files(urls, date)?);
437            }
438            rpki::HistoricalRpkiSource::RpkiSpools(_) => {
439                // For RPKISPOOL, each URL is a tar.zst archive; load the first one
440                if let Some(url) = urls.first() {
441                    self.rpki_trie = Some(rpki::RpkiTrie::from_rpkispools_url(url, date)?);
442                }
443            }
444        }
445        Ok(())
446    }
447
448    /// List available RPKI files for a given date from a specific source
449    ///
450    /// # Example
451    ///
452    /// ```rust,no_run
453    /// use bgpkit_commons::BgpkitCommons;
454    /// use bgpkit_commons::rpki::{HistoricalRpkiSource, RpkiViewsCollector};
455    /// use chrono::NaiveDate;
456    ///
457    /// let commons = BgpkitCommons::new();
458    /// let date = NaiveDate::from_ymd_opt(2024, 1, 4).unwrap();
459    ///
460    /// // List files from RIPE NCC
461    /// let ripe_files = commons.list_rpki_files(date, HistoricalRpkiSource::Ripe).unwrap();
462    ///
463    /// // List files from RPKIviews
464    /// let source = HistoricalRpkiSource::RpkiViews(RpkiViewsCollector::KerfuffleNet);
465    /// let rpkiviews_files = commons.list_rpki_files(date, source).unwrap();
466    /// ```
467    #[cfg(feature = "rpki")]
468    pub fn list_rpki_files(
469        &self,
470        date: chrono::NaiveDate,
471        source: rpki::HistoricalRpkiSource,
472    ) -> Result<Vec<rpki::RpkiFile>> {
473        match source {
474            rpki::HistoricalRpkiSource::Ripe => rpki::list_ripe_files(date),
475            rpki::HistoricalRpkiSource::RpkiViews(collector) => {
476                rpki::list_rpkiviews_files(collector, date)
477            }
478            rpki::HistoricalRpkiSource::RpkiSpools(collector) => {
479                rpki::list_rpkispools_files(collector, date)
480            }
481        }
482    }
483
484    /// Load MRT mrt_collectors data
485    #[cfg(feature = "mrt_collectors")]
486    pub fn load_mrt_collectors(&mut self) -> Result<()> {
487        self.mrt_collectors = Some(crate::mrt_collectors::get_all_collectors()?);
488        Ok(())
489    }
490
491    /// Load MRT mrt_collectors data
492    #[cfg(feature = "mrt_collectors")]
493    pub fn load_mrt_collector_peers(&mut self) -> Result<()> {
494        self.mrt_collector_peers = Some(crate::mrt_collectors::get_mrt_collector_peers()?);
495        Ok(())
496    }
497
498    /// Load bogons data
499    #[cfg(feature = "bogons")]
500    pub fn load_bogons(&mut self) -> Result<()> {
501        self.bogons = Some(crate::bogons::Bogons::new()?);
502        Ok(())
503    }
504
505    /// Load AS name and country data
506    #[cfg(feature = "asinfo")]
507    pub fn load_asinfo(
508        &mut self,
509        load_as2org: bool,
510        load_population: bool,
511        load_hegemony: bool,
512        load_peeringdb: bool,
513    ) -> Result<()> {
514        self.asinfo = Some(crate::asinfo::AsInfoUtils::new(
515            load_as2org,
516            load_population,
517            load_hegemony,
518            load_peeringdb,
519        )?);
520        Ok(())
521    }
522
523    #[cfg(feature = "asinfo")]
524    pub fn load_asinfo_cached(&mut self) -> Result<()> {
525        self.asinfo = Some(crate::asinfo::AsInfoUtils::new_from_cached()?);
526        Ok(())
527    }
528
529    /// Returns a builder for loading AS information with specific data sources.
530    ///
531    /// This provides a more ergonomic way to configure which data sources to load
532    /// compared to the `load_asinfo()` method with boolean parameters.
533    ///
534    /// # Example
535    ///
536    /// ```rust,no_run
537    /// use bgpkit_commons::BgpkitCommons;
538    ///
539    /// let mut commons = BgpkitCommons::new();
540    /// let builder = commons.asinfo_builder()
541    ///     .with_as2org()
542    ///     .with_peeringdb();
543    /// commons.load_asinfo_with(builder).unwrap();
544    /// ```
545    #[cfg(feature = "asinfo")]
546    pub fn asinfo_builder(&self) -> crate::asinfo::AsInfoBuilder {
547        crate::asinfo::AsInfoBuilder::new()
548    }
549
550    /// Load AS information using a pre-configured builder.
551    ///
552    /// # Example
553    ///
554    /// ```rust,no_run
555    /// use bgpkit_commons::BgpkitCommons;
556    ///
557    /// let mut commons = BgpkitCommons::new();
558    /// let builder = commons.asinfo_builder()
559    ///     .with_as2org()
560    ///     .with_hegemony();
561    /// commons.load_asinfo_with(builder).unwrap();
562    /// ```
563    #[cfg(feature = "asinfo")]
564    pub fn load_asinfo_with(&mut self, builder: crate::asinfo::AsInfoBuilder) -> Result<()> {
565        self.asinfo = Some(builder.build()?);
566        Ok(())
567    }
568
569    /// Load AS-level relationship data
570    #[cfg(feature = "as2rel")]
571    pub fn load_as2rel(&mut self) -> Result<()> {
572        self.as2rel = Some(crate::as2rel::As2relBgpkit::new()?);
573        Ok(())
574    }
575}