1use crate::models::{BgpElem, MrtRecord};
5use log::{debug, error, warn};
6use std::io::{BufReader, Cursor, Read};
7pub use text_dump::{detect_text_dump, infer_timestamp_from_path, TextDumpElemIterator};
8
9#[macro_use]
10pub mod utils;
11pub mod bgp;
12pub mod bmp;
13pub mod filter;
14pub mod iters;
15pub mod mrt;
16pub mod rpki;
17pub mod text_dump;
18
19#[cfg(feature = "rislive")]
20pub mod rislive;
21
22pub(crate) use self::utils::*;
23
24pub use mrt::mrt_elem::{BgpUpdateElemIter, ElemError, Elementor, RecordElemIter};
25#[cfg(feature = "oneio")]
26use oneio::{get_cache_reader, get_reader, get_resumable_http_reader};
27
28pub use crate::error::{ParserError, ParserErrorWithBytes};
29pub use bmp::{parse_bmp_msg, parse_openbmp_header, parse_openbmp_msg};
30pub use filter::*;
31pub use iters::*;
32pub use mrt::*;
33
34#[cfg(feature = "rislive")]
35pub use rislive::messages::{
36 RisLiveClientMessage, RisLiveMeta, RisLiveRawFull, RisSubscribe, RisSubscribeSocketOptions,
37 RisSubscribeType,
38};
39#[cfg(feature = "rislive")]
40pub use rislive::{
41 parse_ris_live_message, parse_ris_live_message_iter, parse_ris_live_message_json,
42 parse_ris_live_message_raw, parse_ris_live_message_raw_full, RisLiveElemIter,
43};
44
45pub struct BgpkitParser<R> {
46 reader: R,
47 core_dump: bool,
48 filters: Vec<Filter>,
49 options: ParserOptions,
50 text_dump_iter: Option<Box<dyn Iterator<Item = BgpElem> + Send>>,
53}
54
55pub(crate) struct ParserOptions {
56 show_warnings: bool,
57 warned_zebra_compat: bool,
58 logged_parse_error: bool,
59}
60impl Default for ParserOptions {
61 fn default() -> Self {
62 ParserOptions {
63 show_warnings: true,
64 warned_zebra_compat: false,
65 logged_parse_error: false,
66 }
67 }
68}
69
70impl ParserOptions {
71 pub(crate) fn warn_zebra_compat_once(&mut self) {
72 if self.show_warnings && !self.warned_zebra_compat {
73 warn!(
74 "recovered shortened Zebra BGP4MP records with missing envelope fields; substituting IPv4 zero addresses and interface index 0 (further occurrences for this parser will not be logged)"
75 );
76 self.warned_zebra_compat = true;
77 }
78 }
79
80 fn log_parse_error_once(&mut self, error: &dyn std::fmt::Display, stops_iteration: bool) {
87 if self.logged_parse_error {
88 debug!("parser error: {}", error);
89 } else if stops_iteration {
90 error!("parser error: {}", error);
91 self.logged_parse_error = true;
92 } else {
93 error!(
94 "parser error: {} (further parse errors for this parser will be logged at debug level)",
95 error
96 );
97 self.logged_parse_error = true;
98 }
99 }
100}
101
102#[cfg(feature = "oneio")]
103impl BgpkitParser<Box<dyn Read + Send>> {
104 pub fn new(path: &str) -> Result<Self, ParserErrorWithBytes> {
106 let reader = get_reader(path)?;
107 Ok(BgpkitParser {
108 reader,
109 core_dump: false,
110 filters: vec![],
111 options: ParserOptions::default(),
112 text_dump_iter: None,
113 })
114 }
115
116 pub fn new_resumable_http(path: &str) -> Result<Self, ParserErrorWithBytes> {
138 let reader = get_resumable_http_reader(path)?;
139 Ok(BgpkitParser {
140 reader,
141 core_dump: false,
142 filters: vec![],
143 options: ParserOptions::default(),
144 text_dump_iter: None,
145 })
146 }
147
148 pub fn new_cached(path: &str, cache_dir: &str) -> Result<Self, ParserErrorWithBytes> {
154 let file_name = path.rsplit('/').next().unwrap().to_string();
155 let new_file_name = format!(
156 "cache-{}",
157 add_suffix_to_filename(file_name.as_str(), crc32(path).as_str())
158 );
159 let reader = get_cache_reader(path, cache_dir, Some(new_file_name), false)?;
160 Ok(BgpkitParser {
161 reader,
162 core_dump: false,
163 filters: vec![],
164 options: ParserOptions::default(),
165 text_dump_iter: None,
166 })
167 }
168
169 pub fn new_text(path: &str) -> Result<Self, ParserErrorWithBytes> {
192 let timestamp = infer_timestamp_from_path(path).unwrap_or(0.0);
193 let reader = get_reader(path)?;
194 Self::from_text_reader_with_timestamp(reader, timestamp)
195 }
196
197 pub fn new_auto(path: &str) -> Result<Self, ParserErrorWithBytes> {
216 let reader = get_reader(path)?;
217 Self::from_auto_reader_with_timestamp(reader, infer_timestamp_from_path(path))
218 }
219}
220
221#[cfg(feature = "oneio")]
222fn add_suffix_to_filename(filename: &str, suffix: &str) -> String {
223 let mut parts: Vec<&str> = filename.split('.').collect(); if parts.len() > 1 {
225 let last_part = parts.pop().unwrap(); let new_last_part = format!("{suffix}.{last_part}"); parts.push(&new_last_part); parts.join(".") } else {
230 format!("{filename}.{suffix}")
232 }
233}
234
235impl<R: Read> BgpkitParser<R> {
236 pub fn from_reader(reader: R) -> Self {
238 BgpkitParser {
239 reader,
240 core_dump: false,
241 filters: vec![],
242 options: ParserOptions::default(),
243 text_dump_iter: None,
244 }
245 }
246
247 pub fn next_record(&mut self) -> Result<MrtRecord, ParserErrorWithBytes> {
249 if self.text_dump_iter.is_some() {
250 return Err(ParserError::Unsupported(
251 "text-dump parsers have no MRT record representation; iterate elements instead"
252 .to_string(),
253 )
254 .into());
255 }
256 let (record, used_zebra_compat) =
257 mrt::mrt_record::parse_mrt_record_with_zebra_compat(&mut self.reader)?;
258 if used_zebra_compat {
259 self.warn_zebra_compat_once();
260 }
261 Ok(record)
262 }
263}
264
265impl BgpkitParser<Box<dyn Read + Send>> {
266 pub fn from_text_reader(
270 reader: impl Read + Send + 'static,
271 ) -> Result<Self, ParserErrorWithBytes> {
272 Self::from_text_reader_with_timestamp(reader, 0.0)
273 }
274
275 pub fn from_text_reader_with_timestamp(
279 reader: impl Read + Send + 'static,
280 timestamp: f64,
281 ) -> Result<Self, ParserErrorWithBytes> {
282 let buf_reader = BufReader::new(reader);
283 let iter = TextDumpElemIterator::new(buf_reader, timestamp).map_err(ParserError::from)?;
284 Ok(BgpkitParser {
285 reader: Box::new(std::io::empty()),
286 core_dump: false,
287 filters: vec![],
288 options: ParserOptions::default(),
289 text_dump_iter: Some(Box::new(iter)),
290 })
291 }
292
293 pub fn from_auto_reader(
296 reader: impl Read + Send + 'static,
297 ) -> Result<Self, ParserErrorWithBytes> {
298 Self::from_auto_reader_with_timestamp(reader, None)
299 }
300
301 pub fn from_auto_reader_with_timestamp(
305 reader: impl Read + Send + 'static,
306 timestamp: Option<f64>,
307 ) -> Result<Self, ParserErrorWithBytes> {
308 let mut buf_reader = BufReader::new(reader);
309 let (is_text, head) = detect_text_dump(&mut buf_reader).map_err(ParserError::from)?;
310 if is_text {
311 let chained = BufReader::new(Cursor::new(head).chain(buf_reader));
312 let ts = timestamp.unwrap_or(0.0);
313 let iter = TextDumpElemIterator::new(chained, ts).map_err(ParserError::from)?;
314 Ok(BgpkitParser {
315 reader: Box::new(std::io::empty()),
316 core_dump: false,
317 filters: vec![],
318 options: ParserOptions::default(),
319 text_dump_iter: Some(Box::new(iter)),
320 })
321 } else {
322 Ok(BgpkitParser {
323 reader: Box::new(Cursor::new(head).chain(buf_reader)),
324 core_dump: false,
325 filters: vec![],
326 options: ParserOptions::default(),
327 text_dump_iter: None,
328 })
329 }
330 }
331}
332
333impl<R> BgpkitParser<R> {
334 pub(crate) fn warn_zebra_compat_once(&mut self) {
335 self.options.warn_zebra_compat_once();
336 }
337
338 pub(crate) fn log_parse_error_once(&mut self, error: &dyn std::fmt::Display) {
341 self.options.log_parse_error_once(error, self.core_dump);
342 }
343
344 pub fn enable_core_dump(self) -> Self {
345 BgpkitParser {
346 reader: self.reader,
347 core_dump: true,
348 filters: self.filters,
349 options: self.options,
350 text_dump_iter: self.text_dump_iter,
351 }
352 }
353
354 pub fn disable_warnings(self) -> Self {
355 let mut options = self.options;
356 options.show_warnings = false;
357 BgpkitParser {
358 reader: self.reader,
359 core_dump: self.core_dump,
360 filters: self.filters,
361 options,
362 text_dump_iter: self.text_dump_iter,
363 }
364 }
365
366 pub fn add_filter(
430 self,
431 filter_type: &str,
432 filter_value: &str,
433 ) -> Result<Self, ParserErrorWithBytes> {
434 let mut filters = self.filters;
435 filters.push(Filter::new(filter_type, filter_value)?);
436 Ok(BgpkitParser {
437 reader: self.reader,
438 core_dump: self.core_dump,
439 filters,
440 options: self.options,
441 text_dump_iter: self.text_dump_iter,
442 })
443 }
444
445 pub fn add_filters(mut self, filters: &[Filter]) -> Self {
465 self.filters.extend(filters.iter().cloned());
466 self
467 }
468
469 pub fn with_filters(mut self, filters: &[Filter]) -> Self {
497 self.filters = filters.to_vec();
498 self
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::models::Asn;
506
507 #[test]
508 fn test_new_with_reader() {
509 let reader = oneio::get_reader("http://archive.routeviews.org/route-views.ny/bgpdata/2023.02/UPDATES/updates.20230215.0630.bz2").unwrap();
511 assert_eq!(
512 12683,
513 BgpkitParser::from_reader(reader).into_elem_iter().count()
514 );
515
516 let reader = oneio::get_reader("https://spaces.bgpkit.org/parser/update-example").unwrap();
518 assert_eq!(
519 8160,
520 BgpkitParser::from_reader(reader).into_elem_iter().count()
521 );
522 }
523
524 #[test]
525 fn test_new_resumable_http() {
526 let parser =
527 BgpkitParser::new_resumable_http("https://spaces.bgpkit.org/parser/update-example.gz")
528 .unwrap();
529 assert_eq!(8160, parser.into_elem_iter().count());
530 }
531
532 #[test]
533 fn test_new_cached_with_reader() {
534 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
535 let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests")
536 .unwrap()
537 .enable_core_dump()
538 .disable_warnings();
539 let count = parser.into_elem_iter().count();
540 assert_eq!(8160, count);
541 let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests").unwrap();
542 let count = parser.into_elem_iter().count();
543 assert_eq!(8160, count);
544 }
545
546 #[test]
547 fn test_add_suffix_to_filename() {
548 let filename = "example.txt";
550 let suffix = "suffix";
551 let result = add_suffix_to_filename(filename, suffix);
552 assert_eq!(result, "example.suffix.txt");
553
554 let filename = "example.tar.gz";
556 let suffix = "suffix";
557 let result = add_suffix_to_filename(filename, suffix);
558 assert_eq!(result, "example.tar.suffix.gz");
559
560 let filename = "example";
562 let suffix = "suffix";
563 let result = add_suffix_to_filename(filename, suffix);
564 assert_eq!(result, "example.suffix");
565
566 let filename = "";
568 let suffix = "suffix";
569 let result = add_suffix_to_filename(filename, suffix);
570 assert_eq!(result, ".suffix");
571
572 let filename = "example.txt";
574 let suffix = "";
575 let result = add_suffix_to_filename(filename, suffix);
576 assert_eq!(result, "example..txt");
577 }
578
579 #[test]
580 fn test_with_filters() {
581 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
582
583 let filters = vec![
585 Filter::new("peer_ip", "185.1.8.65").unwrap(),
586 Filter::new("type", "w").unwrap(),
587 ];
588
589 let parser = BgpkitParser::new(url).unwrap().with_filters(&filters);
591 let count = parser.into_elem_iter().count();
592
593 assert_eq!(count, 132);
595
596 let filters1 = vec![Filter::new("peer_ip", "185.1.8.65").unwrap()];
598 let filters2 = vec![Filter::new("peer_ip", "185.1.8.50").unwrap()];
599
600 let parser = BgpkitParser::new(url)
601 .unwrap()
602 .with_filters(&filters1)
603 .with_filters(&filters2); let count = parser.into_elem_iter().count();
605
606 assert_eq!(count, 1563);
608 }
609
610 #[test]
611 fn test_add_filters() {
612 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
613
614 let filters = vec![
616 Filter::new("peer_ip", "185.1.8.65").unwrap(),
617 Filter::new("type", "w").unwrap(),
618 ];
619
620 let parser = BgpkitParser::new(url).unwrap().add_filters(&filters);
622 let count = parser.into_elem_iter().count();
623
624 assert_eq!(count, 132);
626
627 let parser = BgpkitParser::new(url)
629 .unwrap()
630 .add_filter("peer_ip", "185.1.8.65")
631 .unwrap()
632 .add_filters(&[Filter::new("type", "w").unwrap()]);
633 let count = parser.into_elem_iter().count();
634 assert_eq!(count, 132);
635 }
636
637 #[test]
638 fn test_with_filters_empty() {
639 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
640
641 let parser = BgpkitParser::new(url).unwrap().with_filters(&[]);
643 let count = parser.into_elem_iter().count();
644
645 assert_eq!(count, 8160);
647 }
648
649 #[test]
650 fn test_add_filters_empty() {
651 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
652
653 let parser = BgpkitParser::new(url)
655 .unwrap()
656 .add_filter("peer_ip", "185.1.8.65")
657 .unwrap()
658 .add_filters(&[]);
659 let count = parser.into_elem_iter().count();
660
661 assert_eq!(count, 3393);
663 }
664
665 #[test]
666 fn test_with_filters_reuse() {
667 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
668
669 let filters = vec![
671 Filter::new("peer_ip", "185.1.8.65").unwrap(),
672 Filter::new("type", "w").unwrap(),
673 ];
674
675 let parser1 = BgpkitParser::new(url).unwrap().with_filters(&filters);
677 let count1 = parser1.into_elem_iter().count();
678
679 let parser2 = BgpkitParser::new(url).unwrap().with_filters(&filters);
680 let count2 = parser2.into_elem_iter().count();
681
682 assert_eq!(count1, 132);
684 assert_eq!(count2, 132);
685 }
686
687 #[test]
688 fn test_from_text_reader_inline() {
689 let dump = "BGP table version is 1, local router ID is 1.2.3.4, vrf id 0\n\
690Default local pref 100, local AS 65001\n\n\
691 Network Next Hop Metric LocPrf Weight Path\n\
692 *> 1.0.0.0/24 10.0.0.1 0 0 13335 i\n";
693 let parser =
694 BgpkitParser::from_text_reader(dump.as_bytes()).expect("inline text-dump parse");
695 let elems: Vec<_> = parser.into_elem_iter().collect();
696 assert_eq!(elems.len(), 1);
697 assert_eq!(elems[0].prefix.prefix.to_string(), "1.0.0.0/24");
698 assert_eq!(elems[0].peer_ip.to_string(), "1.2.3.4");
699 assert_eq!(u32::from(elems[0].peer_asn), 65001);
700 assert_eq!(elems[0].origin_asns, Some(vec![Asn::from(13335u32)]));
701 }
702
703 #[test]
704 fn test_from_auto_reader_detects_text() {
705 let dump = "BGP table version is 1, local router ID is 1.2.3.4, vrf id 0\n\
706Default local pref 100, local AS 65001\n\n\
707 Network Next Hop Metric LocPrf Weight Path\n\
708 *> 1.0.0.0/24 10.0.0.1 0 0 13335 i\n";
709 let parser = BgpkitParser::from_auto_reader(dump.as_bytes()).expect("auto-detect parse");
710 let elems: Vec<_> = parser.into_elem_iter().collect();
711 assert_eq!(elems.len(), 1);
712 assert_eq!(elems[0].origin_asns, Some(vec![Asn::from(13335u32)]));
713 }
714
715 #[test]
716 fn test_from_auto_reader_detects_mrt() {
717 let data: Vec<u8> = vec![0x00u8; 16];
720 let parser =
721 BgpkitParser::from_auto_reader(std::io::Cursor::new(data)).expect("auto-detect parse");
722 let count = parser.into_elem_iter().count();
723 assert_eq!(count, 0);
724 }
725
726 #[test]
727 fn test_text_dump_parser_with_filter() {
728 let dump = "BGP table version is 1, local router ID is 1.2.3.4, vrf id 0\n\
729Default local pref 100, local AS 65001\n\n\
730 Network Next Hop Metric LocPrf Weight Path\n\
731 *> 1.0.0.0/24 10.0.0.1 0 0 13335 i\n\
732 *> 8.8.8.0/24 10.0.0.2 0 0 15169 i\n";
733 let parser = BgpkitParser::from_text_reader(dump.as_bytes())
734 .unwrap()
735 .add_filter("origin_asn", "13335")
736 .unwrap();
737 let elems: Vec<_> = parser.into_elem_iter().collect();
738 assert_eq!(elems.len(), 1);
739 assert_eq!(elems[0].prefix.prefix.to_string(), "1.0.0.0/24");
740 }
741
742 #[test]
743 fn test_text_dump_next_record_errors() {
744 let dump = "BGP table version is 1, local router ID is 1.2.3.4, vrf id 0\n\
745Default local pref 100, local AS 65001\n\n\
746 Network Next Hop Metric LocPrf Weight Path\n\
747 *> 1.0.0.0/24 10.0.0.1 0 0 13335 i\n";
748 let mut parser =
749 BgpkitParser::from_text_reader(dump.as_bytes()).expect("inline text-dump parse");
750 assert!(parser.next_record().is_err());
751 }
752
753 #[test]
754 fn test_text_dump_record_iter_terminates() {
755 let dump = "BGP table version is 1, local router ID is 1.2.3.4, vrf id 0\n\
758Default local pref 100, local AS 65001\n\n\
759 Network Next Hop Metric LocPrf Weight Path\n\
760 *> 1.0.0.0/24 10.0.0.1 0 0 13335 i\n";
761 let parser =
762 BgpkitParser::from_text_reader(dump.as_bytes()).expect("inline text-dump parse");
763 assert_eq!(parser.into_record_iter().count(), 0);
764 }
765}