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