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}