1#![allow(unused)]
2use crate::models::*;
8use crate::parser::bgp::messages::parse_bgp_update_message;
9use crate::ParserError;
10use crate::ParserError::ParseError;
11use bytes::Bytes;
12use itertools::Itertools;
13use log::{error, warn};
14use std::collections::HashMap;
15use std::fmt::{Display, Formatter};
16use std::net::{IpAddr, Ipv4Addr};
17
18#[derive(Default, Debug, Clone)]
19pub struct Elementor {
20 pub peer_table: Option<PeerIndexTable>,
21}
22
23#[derive(Debug)]
25pub enum ElemError {
26 UnexpectedPeerIndexTable(Box<PeerIndexTable>),
29 MissingPeerTable,
32 UnsupportedRibGeneric,
34}
35
36impl Display for ElemError {
37 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38 match self {
39 ElemError::UnexpectedPeerIndexTable(_) => {
40 write!(f, "unexpected PeerIndexTable record")
41 }
42 ElemError::MissingPeerTable => {
43 write!(
44 f,
45 "peer table not set; call set_peer_table or use with_peer_table first"
46 )
47 }
48 ElemError::UnsupportedRibGeneric => {
49 write!(f, "RibGenericEntries not yet supported")
50 }
51 }
52 }
53}
54
55impl std::error::Error for ElemError {}
56
57macro_rules! get_attr_value {
59 ($a:tt, $b:expr) => {
60 if let Attribute::$a(x) = $b {
61 Some(x)
62 } else {
63 None
64 }
65 };
66}
67
68#[allow(clippy::type_complexity)]
69fn get_relevant_attributes(
70 attributes: Attributes,
71) -> (
72 Option<AsPath>,
73 Option<AsPath>,
74 Option<Origin>,
75 Option<IpAddr>,
76 Option<u32>,
77 Option<u32>,
78 Option<Vec<MetaCommunity>>,
79 bool,
80 Option<(Asn, BgpIdentifier)>,
81 Option<Nlri>,
82 Option<Nlri>,
83 Option<Asn>,
84 Option<Vec<AttrRaw>>,
85 Option<Vec<AttrRaw>>,
86) {
87 let mut as_path = None;
88 let mut as4_path = None;
89 let mut origin = None;
90 let mut next_hop = None;
91 let mut local_pref = Some(0);
92 let mut med = Some(0);
93 let mut atomic = false;
94 let mut aggregator = None;
95 let mut announced = None;
96 let mut withdrawn = None;
97 let mut otc = None;
98 let mut unknown = vec![];
99 let mut deprecated = vec![];
100
101 let mut communities_vec: Vec<MetaCommunity> = vec![];
102
103 for attr in attributes {
104 match attr {
105 AttributeValue::Origin(v) => origin = Some(v),
106 AttributeValue::AsPath {
107 path,
108 is_as4: false,
109 } => as_path = Some(path),
110 AttributeValue::AsPath { path, is_as4: true } => as4_path = Some(path),
111 AttributeValue::NextHop(v) => next_hop = Some(v),
112 AttributeValue::MultiExitDiscriminator(v) => med = Some(v),
113 AttributeValue::LocalPreference(v) => local_pref = Some(v),
114 AttributeValue::AtomicAggregate => atomic = true,
115 AttributeValue::Communities(v) => communities_vec.extend(
116 v.into_iter()
117 .map(MetaCommunity::Plain)
118 .collect::<Vec<MetaCommunity>>(),
119 ),
120 AttributeValue::ExtendedCommunities(v) => communities_vec.extend(
121 v.into_iter()
122 .map(MetaCommunity::Extended)
123 .collect::<Vec<MetaCommunity>>(),
124 ),
125 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => communities_vec.extend(
126 v.into_iter()
127 .map(MetaCommunity::Ipv6Extended)
128 .collect::<Vec<MetaCommunity>>(),
129 ),
130 AttributeValue::LargeCommunities(v) => communities_vec.extend(
131 v.into_iter()
132 .map(MetaCommunity::Large)
133 .collect::<Vec<MetaCommunity>>(),
134 ),
135 AttributeValue::Aggregator { asn, id, .. } => aggregator = Some((asn, id)),
136 AttributeValue::MpReachNlri(nlri) => announced = Some(nlri),
137 AttributeValue::MpUnreachNlri(nlri) => withdrawn = Some(nlri),
138 AttributeValue::OnlyToCustomer(o) => otc = Some(o),
139
140 AttributeValue::Unknown(t) | AttributeValue::Raw(t) => {
141 unknown.push(t);
142 }
143 AttributeValue::Deprecated(t) => {
144 deprecated.push(t);
145 }
146
147 AttributeValue::OriginatorId(_)
148 | AttributeValue::Clusters(_)
149 | AttributeValue::Development(_)
150 | AttributeValue::LinkState(_)
151 | AttributeValue::TunnelEncapsulation(_)
152 | AttributeValue::Aigp(_)
153 | AttributeValue::BfdDiscriminator(_)
154 | AttributeValue::BgpPrefixSid(_)
155 | AttributeValue::Bier(_)
156 | AttributeValue::Sfp(_)
157 | AttributeValue::AttrSet(_) => {}
158 };
159 }
160
161 let communities = match !communities_vec.is_empty() {
162 true => Some(communities_vec),
163 false => None,
164 };
165
166 let next_hop = next_hop.or_else(|| {
168 announced.as_ref().and_then(|v| {
169 v.next_hop.as_ref().map(|h| match h {
170 NextHopAddress::Ipv4(v) => IpAddr::from(*v),
171 NextHopAddress::Ipv6(v) => IpAddr::from(*v),
172 NextHopAddress::Ipv6LinkLocal(v, _) => IpAddr::from(*v),
173 NextHopAddress::VpnIpv6(_, v) => IpAddr::from(*v),
175 NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => IpAddr::from(*v),
176 })
177 })
178 });
179
180 (
181 as_path,
182 as4_path,
183 origin,
184 next_hop,
185 local_pref,
186 med,
187 communities,
188 atomic,
189 aggregator,
190 announced,
191 withdrawn,
192 otc,
193 if unknown.is_empty() {
194 None
195 } else {
196 Some(unknown)
197 },
198 if deprecated.is_empty() {
199 None
200 } else {
201 Some(deprecated)
202 },
203 )
204}
205
206fn rib_entry_to_elem(prefix: NetworkPrefix, peer: &Peer, entry: RibEntry) -> BgpElem {
207 let (
208 as_path,
209 as4_path,
210 origin,
211 next_hop,
212 local_pref,
213 med,
214 communities,
215 atomic,
216 aggregator,
217 announced,
218 _withdrawn,
219 only_to_customer,
220 unknown,
221 deprecated,
222 ) = get_relevant_attributes(entry.attributes);
223
224 let path = match (as_path, as4_path) {
225 (None, None) => None,
226 (Some(v), None) => Some(v),
227 (None, Some(v)) => Some(v),
228 (Some(v1), Some(v2)) => Some(AsPath::merge_aspath_as4path(&v1, &v2)),
229 };
230
231 let next_hop = match next_hop {
232 Some(v) => Some(v),
233 None => announced.and_then(|v| {
234 v.next_hop.map(|h| match h {
235 NextHopAddress::Ipv4(v) => IpAddr::from(v),
236 NextHopAddress::Ipv6(v) => IpAddr::from(v),
237 NextHopAddress::Ipv6LinkLocal(v, _) => IpAddr::from(v),
238 NextHopAddress::VpnIpv6(_, v) => IpAddr::from(v),
239 NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => IpAddr::from(v),
240 })
241 }),
242 };
243
244 let origin_asns = path
245 .as_ref()
246 .map(|as_path| as_path.iter_origins().collect());
247
248 BgpElem {
249 timestamp: entry.originated_time as f64,
250 elem_type: ElemType::ANNOUNCE,
251 peer_ip: peer.peer_ip,
252 peer_asn: peer.peer_asn,
253 peer_bgp_id: Some(peer.peer_bgp_id),
254 prefix,
255 next_hop,
256 as_path: path,
257 origin,
258 origin_asns,
259 local_pref,
260 med,
261 communities,
262 atomic,
263 aggr_asn: aggregator.map(|v| v.0),
264 aggr_ip: aggregator.map(|v| v.1),
265 only_to_customer,
266 unknown,
267 deprecated,
268 }
269}
270
271pub enum RecordElemIter<'a> {
277 #[doc(hidden)]
278 Empty,
279 #[doc(hidden)]
280 TableDump(Option<BgpElem>),
281 #[doc(hidden)]
282 RibAfi {
283 peer_table: &'a PeerIndexTable,
284 prefix: NetworkPrefix,
285 entries: std::vec::IntoIter<RibEntry>,
286 },
287 #[doc(hidden)]
288 Bgp4Mp(BgpUpdateElemIter),
289}
290
291impl Iterator for RecordElemIter<'_> {
292 type Item = BgpElem;
293
294 fn next(&mut self) -> Option<BgpElem> {
295 match self {
296 RecordElemIter::Empty => None,
297 RecordElemIter::TableDump(elem) => elem.take(),
298 RecordElemIter::Bgp4Mp(iter) => iter.next(),
299 RecordElemIter::RibAfi {
300 peer_table,
301 prefix,
302 entries,
303 } => {
304 let entry = entries.next()?;
305 let pid = entry.peer_index;
306 match peer_table.get_peer_by_id(&pid) {
307 Some(peer) => Some(rib_entry_to_elem(*prefix, peer, entry)),
308 None => {
309 error!("peer ID {} not found in peer_index table", pid);
310 *self = RecordElemIter::Empty;
311 None
312 }
313 }
314 }
315 }
316 }
317
318 fn size_hint(&self) -> (usize, Option<usize>) {
319 match self {
320 RecordElemIter::Empty => (0, Some(0)),
321 RecordElemIter::TableDump(elem) => {
322 let n = elem.is_some() as usize;
323 (n, Some(n))
324 }
325 RecordElemIter::Bgp4Mp(iter) => iter.size_hint(),
326 RecordElemIter::RibAfi { entries, .. } => {
327 let len = entries.len();
328 (len, Some(len))
329 }
330 }
331 }
332}
333
334pub struct BgpUpdateElemIter {
338 timestamp: f64,
339 peer_ip: IpAddr,
340 peer_asn: Asn,
341 peer_bgp_id: Option<BgpIdentifier>,
342 only_to_customer: Option<Asn>,
343 path: Option<AsPath>,
345 origin_asns: Option<Vec<Asn>>,
346 origin: Option<Origin>,
347 next_hop: Option<IpAddr>,
348 local_pref: Option<u32>,
349 med: Option<u32>,
350 communities: Option<Vec<MetaCommunity>>,
351 atomic: bool,
352 aggr_asn: Option<Asn>,
353 aggr_ip: Option<BgpIdentifier>,
354 unknown: Option<Vec<AttrRaw>>,
355 deprecated: Option<Vec<AttrRaw>>,
356 announced:
358 std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
359 withdrawn:
360 std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
361 in_withdrawn_phase: bool,
362}
363
364impl Iterator for BgpUpdateElemIter {
365 type Item = BgpElem;
366
367 fn next(&mut self) -> Option<BgpElem> {
368 if !self.in_withdrawn_phase {
369 if let Some(prefix) = self.announced.next() {
370 return Some(BgpElem {
371 timestamp: self.timestamp,
372 elem_type: ElemType::ANNOUNCE,
373 peer_ip: self.peer_ip,
374 peer_asn: self.peer_asn,
375 peer_bgp_id: self.peer_bgp_id,
376 prefix,
377 next_hop: self.next_hop,
378 as_path: self.path.clone(),
379 origin: self.origin,
380 origin_asns: self.origin_asns.clone(),
381 local_pref: self.local_pref,
382 med: self.med,
383 communities: self.communities.clone(),
384 atomic: self.atomic,
385 aggr_asn: self.aggr_asn,
386 aggr_ip: self.aggr_ip,
387 only_to_customer: self.only_to_customer,
388 unknown: self.unknown.clone(),
389 deprecated: self.deprecated.clone(),
390 });
391 }
392 self.in_withdrawn_phase = true;
393 }
394
395 self.withdrawn.next().map(|prefix| BgpElem {
396 timestamp: self.timestamp,
397 elem_type: ElemType::WITHDRAW,
398 peer_ip: self.peer_ip,
399 peer_asn: self.peer_asn,
400 peer_bgp_id: self.peer_bgp_id,
401 prefix,
402 next_hop: None,
403 as_path: None,
404 origin: None,
405 origin_asns: None,
406 local_pref: None,
407 med: None,
408 communities: None,
409 atomic: false,
410 aggr_asn: None,
411 aggr_ip: None,
412 only_to_customer: None,
413 unknown: None,
414 deprecated: None,
415 })
416 }
417
418 fn size_hint(&self) -> (usize, Option<usize>) {
419 let (ann_lo, ann_hi) = if self.in_withdrawn_phase {
420 (0, Some(0))
421 } else {
422 self.announced.size_hint()
423 };
424 let (wd_lo, wd_hi) = self.withdrawn.size_hint();
425 (ann_lo + wd_lo, ann_hi.and_then(|a| wd_hi.map(|w| a + w)))
426 }
427}
428
429impl Elementor {
430 pub fn new() -> Elementor {
431 Self::default()
432 }
433
434 pub fn set_peer_table(&mut self, record: MrtRecord) -> Result<(), ParserError> {
463 if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(p)) =
464 record.message
465 {
466 self.peer_table = Some(p);
467 Ok(())
468 } else {
469 Err(ParseError("peer_table is not a PeerIndexTable".to_string()))
470 }
471 }
472
473 pub fn with_peer_table(peer_table: PeerIndexTable) -> Elementor {
475 Elementor {
476 peer_table: Some(peer_table),
477 }
478 }
479
480 pub fn record_to_elems_iter(&self, record: MrtRecord) -> Result<RecordElemIter<'_>, ElemError> {
497 let timestamp = {
498 let t = record.common_header.timestamp;
499 if let Some(micro) = &record.common_header.microsecond_timestamp {
500 let m = (*micro as f64) / 1000000.0;
501 t as f64 + m
502 } else {
503 f64::from(t)
504 }
505 };
506
507 match record.message {
508 MrtMessage::TableDumpMessage(msg) => {
509 let (
510 as_path,
511 _as4_path,
512 origin,
513 next_hop,
514 local_pref,
515 med,
516 communities,
517 atomic,
518 aggregator,
519 _announced,
520 _withdrawn,
521 only_to_customer,
522 unknown,
523 deprecated,
524 ) = get_relevant_attributes(msg.attributes);
525
526 let origin_asns = as_path
527 .as_ref()
528 .map(|as_path| as_path.iter_origins().collect());
529
530 Ok(RecordElemIter::TableDump(Some(BgpElem {
531 timestamp: msg.originated_time as f64,
532 elem_type: ElemType::ANNOUNCE,
533 peer_ip: msg.peer_ip,
534 peer_asn: msg.peer_asn,
535 peer_bgp_id: None,
536 prefix: msg.prefix,
537 next_hop,
538 as_path,
539 origin,
540 origin_asns,
541 local_pref,
542 med,
543 communities,
544 atomic,
545 aggr_asn: aggregator.map(|v| v.0),
546 aggr_ip: aggregator.map(|v| v.1),
547 only_to_customer,
548 unknown,
549 deprecated,
550 })))
551 }
552
553 MrtMessage::TableDumpV2Message(msg) => match msg {
554 TableDumpV2Message::PeerIndexTable(p) => {
555 Err(ElemError::UnexpectedPeerIndexTable(Box::new(p)))
556 }
557 TableDumpV2Message::RibAfi(t) => {
558 let peer_table = self
559 .peer_table
560 .as_ref()
561 .ok_or(ElemError::MissingPeerTable)?;
562 Ok(RecordElemIter::RibAfi {
563 peer_table,
564 prefix: t.prefix,
565 entries: t.rib_entries.into_iter(),
566 })
567 }
568 TableDumpV2Message::RibGeneric(_) => Err(ElemError::UnsupportedRibGeneric),
569 TableDumpV2Message::GeoPeerTable(_) => Ok(RecordElemIter::Empty),
570 },
571
572 MrtMessage::Bgp4Mp(msg) => match msg {
573 Bgp4MpEnum::StateChange(_) => Ok(RecordElemIter::Empty),
574 Bgp4MpEnum::Message(v) => {
575 match Elementor::bgp_to_elems_iter(
576 v.bgp_message,
577 timestamp,
578 &v.peer_ip,
579 &v.peer_asn,
580 ) {
581 Some(iter) => Ok(RecordElemIter::Bgp4Mp(iter)),
582 None => Ok(RecordElemIter::Empty),
583 }
584 }
585 },
586 }
587 }
588
589 pub fn bgp_to_elems(
594 msg: BgpMessage,
595 timestamp: f64,
596 peer_ip: &IpAddr,
597 peer_asn: &Asn,
598 ) -> Vec<BgpElem> {
599 Elementor::bgp_to_elems_iter(msg, timestamp, peer_ip, peer_asn)
600 .map(|iter| iter.collect())
601 .unwrap_or_default()
602 }
603
604 pub fn bgp_to_elems_iter(
608 msg: BgpMessage,
609 timestamp: f64,
610 peer_ip: &IpAddr,
611 peer_asn: &Asn,
612 ) -> Option<BgpUpdateElemIter> {
613 match msg {
614 BgpMessage::Update(msg) => Some(Elementor::bgp_update_to_elems_iter(
615 msg, timestamp, peer_ip, peer_asn,
616 )),
617 BgpMessage::Open(_) | BgpMessage::Notification(_) | BgpMessage::KeepAlive => None,
618 }
619 }
620
621 pub fn bgp_update_to_elems(
623 msg: BgpUpdateMessage,
624 timestamp: f64,
625 peer_ip: &IpAddr,
626 peer_asn: &Asn,
627 ) -> Vec<BgpElem> {
628 Elementor::bgp_update_to_elems_iter(msg, timestamp, peer_ip, peer_asn).collect()
629 }
630
631 pub fn bgp_update_to_elems_iter(
634 msg: BgpUpdateMessage,
635 timestamp: f64,
636 peer_ip: &IpAddr,
637 peer_asn: &Asn,
638 ) -> BgpUpdateElemIter {
639 let (
640 as_path,
641 as4_path,
642 origin,
643 next_hop,
644 local_pref,
645 med,
646 communities,
647 atomic,
648 aggregator,
649 announced,
650 withdrawn,
651 only_to_customer,
652 unknown,
653 deprecated,
654 ) = get_relevant_attributes(msg.attributes);
655
656 let path = match (as_path, as4_path) {
657 (None, None) => None,
658 (Some(v), None) => Some(v),
659 (None, Some(v)) => Some(v),
660 (Some(v1), Some(v2)) => Some(AsPath::merge_aspath_as4path(&v1, &v2)),
661 };
662
663 let origin_asns = path
664 .as_ref()
665 .map(|as_path| as_path.iter_origins().collect());
666
667 let nlri_announced = announced.map(|n| n.prefixes).unwrap_or_default();
668 let nlri_withdrawn = withdrawn.map(|n| n.prefixes).unwrap_or_default();
669
670 BgpUpdateElemIter {
671 timestamp,
672 peer_ip: *peer_ip,
673 peer_asn: *peer_asn,
674 peer_bgp_id: None,
675 only_to_customer,
676 path,
677 origin_asns,
678 origin,
679 next_hop,
680 local_pref,
681 med,
682 communities,
683 atomic,
684 aggr_asn: aggregator.as_ref().map(|v| v.0),
685 aggr_ip: aggregator.as_ref().map(|v| v.1),
686 unknown,
687 deprecated,
688 announced: msg.announced_prefixes.into_iter().chain(nlri_announced),
689 withdrawn: msg.withdrawn_prefixes.into_iter().chain(nlri_withdrawn),
690 in_withdrawn_phase: false,
691 }
692 }
693
694 pub fn record_to_elems(&mut self, record: MrtRecord) -> Vec<BgpElem> {
702 match record.message {
703 MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_)) => {
704 self.set_peer_table(record);
705 vec![]
706 }
707 _ => match self.record_to_elems_iter(record) {
708 Ok(iter) => iter.collect(),
709 Err(e) => {
710 error!("{}", e);
711 vec![]
712 }
713 },
714 }
715 }
716}
717
718#[inline(always)]
719pub fn option_to_string<T>(o: &Option<T>) -> String
720where
721 T: Display,
722{
723 if let Some(v) = o {
724 v.to_string()
725 } else {
726 String::new()
727 }
728}
729
730impl From<&BgpElem> for Attributes {
731 fn from(value: &BgpElem) -> Self {
732 let mut values = Vec::<AttributeValue>::new();
733 let mut attributes = Attributes::default();
734 let prefix = value.prefix;
735
736 if value.elem_type == ElemType::WITHDRAW {
737 values.push(AttributeValue::MpUnreachNlri(Nlri::new_unreachable(prefix)));
738 attributes.extend(values);
739 return attributes;
740 }
741
742 values.push(AttributeValue::MpReachNlri(Nlri::new_reachable(
743 prefix,
744 value.next_hop,
745 )));
746
747 if let Some(v) = value.next_hop {
748 values.push(AttributeValue::NextHop(v));
749 }
750
751 if let Some(v) = value.as_path.as_ref() {
752 let is_as4 = match v.get_origin_opt() {
753 None => true,
754 Some(asn) => asn.is_four_byte(),
755 };
756 values.push(AttributeValue::AsPath {
757 path: v.clone(),
758 is_as4,
759 });
760 }
761
762 if let Some(v) = value.origin {
763 values.push(AttributeValue::Origin(v));
764 }
765
766 if let Some(v) = value.local_pref {
767 values.push(AttributeValue::LocalPreference(v));
768 }
769
770 if let Some(v) = value.med {
771 values.push(AttributeValue::MultiExitDiscriminator(v));
772 }
773
774 if let Some(v) = value.communities.as_ref() {
775 let mut communites = vec![];
776 let mut extended_communities = vec![];
777 let mut ipv6_extended_communities = vec![];
778 let mut large_communities = vec![];
779 for c in v {
780 match c {
781 MetaCommunity::Plain(v) => communites.push(*v),
782 MetaCommunity::Extended(v) => extended_communities.push(*v),
783 MetaCommunity::Large(v) => large_communities.push(*v),
784 MetaCommunity::Ipv6Extended(v) => ipv6_extended_communities.push(*v),
785 }
786 }
787 if !communites.is_empty() {
788 values.push(AttributeValue::Communities(communites));
789 }
790 if !extended_communities.is_empty() {
791 values.push(AttributeValue::ExtendedCommunities(extended_communities));
792 }
793 if !large_communities.is_empty() {
794 values.push(AttributeValue::LargeCommunities(large_communities));
795 }
796 if !ipv6_extended_communities.is_empty() {
797 values.push(AttributeValue::Ipv6AddressSpecificExtendedCommunities(
798 ipv6_extended_communities,
799 ));
800 }
801 }
802
803 if let Some(v) = value.aggr_asn {
804 let aggregator_id = match value.aggr_ip {
805 Some(v) => v,
806 None => Ipv4Addr::UNSPECIFIED,
807 };
808 values.push(AttributeValue::Aggregator {
809 asn: v,
810 id: aggregator_id,
811 is_as4: v.is_four_byte(),
812 });
813 }
814
815 if let Some(v) = value.only_to_customer {
816 values.push(AttributeValue::OnlyToCustomer(v));
817 }
818
819 if let Some(v) = value.unknown.as_ref() {
820 for t in v {
821 values.push(AttributeValue::Unknown(t.clone()));
822 }
823 }
824
825 if let Some(v) = value.deprecated.as_ref() {
826 for t in v {
827 values.push(AttributeValue::Deprecated(t.clone()));
828 }
829 }
830
831 attributes.extend(values);
832 attributes
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839 use crate::BgpkitParser;
840 use std::net::{Ipv4Addr, Ipv6Addr};
841 use std::str::FromStr;
842
843 #[test]
844 fn test_option_to_string() {
845 let o1 = Some(1);
846 let o2: Option<u32> = None;
847 assert_eq!(option_to_string(&o1), "1");
848 assert_eq!(option_to_string(&o2), "");
849 }
850
851 #[test]
852 fn test_record_to_elems() {
853 let url_table_dump_v1 = "https://data.ris.ripe.net/rrc00/2003.01/bview.20030101.0000.gz";
854 let url_table_dump_v2 = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
855 let url_bgp4mp = "https://data.ris.ripe.net/rrc00/2021.10/updates.20211001.0000.gz";
856
857 let mut elementor = Elementor::new();
858 let parser = BgpkitParser::new(url_table_dump_v1).unwrap();
859 let mut record_iter = parser.into_record_iter();
860 let record = record_iter.next().unwrap();
861 let elems = elementor.record_to_elems(record);
862 assert_eq!(elems.len(), 1);
863
864 let parser = BgpkitParser::new(url_table_dump_v2).unwrap();
865 let mut record_iter = parser.into_record_iter();
866 let peer_index_table = record_iter.next().unwrap();
867 let _elems = elementor.record_to_elems(peer_index_table);
868 let record = record_iter.next().unwrap();
869 let elems = elementor.record_to_elems(record);
870 assert!(!elems.is_empty());
871
872 let parser = BgpkitParser::new(url_bgp4mp).unwrap();
873 let mut record_iter = parser.into_record_iter();
874 let record = record_iter.next().unwrap();
875 let elems = elementor.record_to_elems(record);
876 assert!(!elems.is_empty());
877 }
878
879 #[test]
880 fn test_attributes_from_bgp_elem() {
881 let mut elem = BgpElem {
882 timestamp: 0.0,
883 elem_type: ElemType::ANNOUNCE,
884 peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
885 peer_asn: Asn::new_32bit(65000),
886 peer_bgp_id: None,
887 prefix: NetworkPrefix::from_str("10.0.1.0/24").unwrap(),
888 next_hop: Some(IpAddr::from_str("10.0.0.2").unwrap()),
889 as_path: Some(AsPath::from_sequence([65000, 65001, 65002])),
890 origin: Some(Origin::EGP),
891 origin_asns: Some(vec![Asn::new_32bit(65000)]),
892 local_pref: Some(100),
893 med: Some(200),
894 communities: Some(vec![
895 MetaCommunity::Plain(Community::NoAdvertise),
896 MetaCommunity::Extended(ExtendedCommunity::Raw([0, 0, 0, 0, 0, 0, 0, 0])),
897 MetaCommunity::Large(LargeCommunity {
898 global_admin: 0,
899 local_data: [0, 0],
900 }),
901 MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
902 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
903 subtype: 0,
904 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
905 local_admin: [0, 0],
906 }),
907 ]),
908 atomic: false,
909 aggr_asn: Some(Asn::new_32bit(65000)),
910 aggr_ip: Some(Ipv4Addr::from_str("10.2.0.0").unwrap()),
911 only_to_customer: Some(Asn::new_32bit(65000)),
912 unknown: Some(vec![AttrRaw {
913 code: AttrType::RESERVED.into(),
914 bytes: Bytes::new(),
915 }]),
916 deprecated: Some(vec![AttrRaw {
917 code: AttrType::RESERVED.into(),
918 bytes: Bytes::new(),
919 }]),
920 };
921
922 let _attributes = Attributes::from(&elem);
923 elem.elem_type = ElemType::WITHDRAW;
924 let _attributes = Attributes::from(&elem);
925 }
926
927 #[test]
928 fn test_get_relevant_attributes() {
929 let attributes = vec![
930 AttributeValue::Origin(Origin::IGP),
931 AttributeValue::AsPath {
932 path: AsPath::from_sequence([65000, 65001, 65002]),
933 is_as4: true,
934 },
935 AttributeValue::NextHop(IpAddr::from_str("10.0.0.1").unwrap()),
936 AttributeValue::MultiExitDiscriminator(100),
937 AttributeValue::LocalPreference(200),
938 AttributeValue::AtomicAggregate,
939 AttributeValue::Aggregator {
940 asn: Asn::new_32bit(65000),
941 id: Ipv4Addr::from_str("10.0.0.1").unwrap(),
942 is_as4: false,
943 },
944 AttributeValue::Communities(vec![Community::NoExport]),
945 AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([
946 0, 0, 0, 0, 0, 0, 0, 0,
947 ])]),
948 AttributeValue::LargeCommunities(vec![LargeCommunity {
949 global_admin: 0,
950 local_data: [0, 0],
951 }]),
952 AttributeValue::Ipv6AddressSpecificExtendedCommunities(vec![Ipv6AddrExtCommunity {
953 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
954 subtype: 0,
955 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
956 local_admin: [0, 0],
957 }]),
958 AttributeValue::MpReachNlri(Nlri::new_reachable(
959 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
960 Some(IpAddr::from_str("10.0.0.1").unwrap()),
961 )),
962 AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
963 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
964 )),
965 AttributeValue::OnlyToCustomer(Asn::new_32bit(65000)),
966 AttributeValue::Unknown(AttrRaw {
967 code: AttrType::RESERVED.into(),
968 bytes: Bytes::new(),
969 }),
970 AttributeValue::Deprecated(AttrRaw {
971 code: AttrType::RESERVED.into(),
972 bytes: Bytes::new(),
973 }),
974 ]
975 .into_iter()
976 .map(Attribute::from)
977 .collect::<Vec<Attribute>>();
978
979 let attributes = Attributes::from(attributes);
980
981 let (
982 _as_path,
983 _as4_path, _origin,
985 _next_hop,
986 _local_pref,
987 _med,
988 _communities,
989 _atomic,
990 _aggregator,
991 _announced,
992 _withdrawn,
993 _only_to_customer,
994 _unknown,
995 _deprecated,
996 ) = get_relevant_attributes(attributes);
997 }
998
999 #[test]
1000 fn test_next_hop_from_nlri() {
1001 let attributes = vec![AttributeValue::NextHop(
1002 IpAddr::from_str("10.0.0.1").unwrap(),
1003 )]
1004 .into_iter()
1005 .map(Attribute::from)
1006 .collect::<Vec<Attribute>>();
1007
1008 let attributes = Attributes::from(attributes);
1009
1010 let (
1011 _as_path,
1012 _as4_path, _origin,
1014 next_hop,
1015 _local_pref,
1016 _med,
1017 _communities,
1018 _atomic,
1019 _aggregator,
1020 _announced,
1021 _withdrawn,
1022 _only_to_customer,
1023 _unknown,
1024 _deprecated,
1025 ) = get_relevant_attributes(attributes);
1026
1027 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.1").unwrap()));
1028
1029 let attributes = vec![AttributeValue::MpReachNlri(Nlri::new_reachable(
1030 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1031 Some(IpAddr::from_str("10.0.0.2").unwrap()),
1032 ))]
1033 .into_iter()
1034 .map(Attribute::from)
1035 .collect::<Vec<Attribute>>();
1036
1037 let attributes = Attributes::from(attributes);
1038
1039 let (
1040 _as_path,
1041 _as4_path, _origin,
1043 next_hop,
1044 _local_pref,
1045 _med,
1046 _communities,
1047 _atomic,
1048 _aggregator,
1049 _announced,
1050 _withdrawn,
1051 _only_to_customer,
1052 _unknown,
1053 _deprecated,
1054 ) = get_relevant_attributes(attributes);
1055
1056 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.2").unwrap()));
1057 }
1058
1059 #[test]
1060 fn test_record_to_elems_iter_equivalence_tabledumpv2_small() {
1061 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1063
1064 let mut elementor = Elementor::new();
1065 let parser = BgpkitParser::new(url).unwrap();
1066 let mut record_iter = parser.into_record_iter();
1067
1068 let peer_index_table = record_iter.next().unwrap();
1070 let _ = elementor.record_to_elems(peer_index_table);
1071
1072 let record = record_iter.next().unwrap();
1074 let elems_vec = elementor.record_to_elems(record.clone());
1075 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1076 assert_eq!(elems_vec, elems_iter);
1077 assert!(!elems_vec.is_empty());
1078 }
1079
1080 #[test]
1081 fn test_record_to_elems_iter_equivalence_bgp4mp() {
1082 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1083
1084 let mut elementor = Elementor::new();
1085 let parser = BgpkitParser::new(url).unwrap();
1086 let mut record_iter = parser.into_record_iter();
1087 let record = record_iter.next().unwrap();
1088
1089 let elems_vec = elementor.record_to_elems(record.clone());
1090 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1091 assert_eq!(elems_vec, elems_iter);
1092 assert!(!elems_vec.is_empty());
1093 }
1094
1095 #[test]
1096 #[ignore = "requires large RIB file download"]
1097 fn test_record_to_elems_iter_equivalence_tabledumpv2() {
1098 let url = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
1099
1100 let mut elementor = Elementor::new();
1101 let parser = BgpkitParser::new(url).unwrap();
1102 let mut record_iter = parser.into_record_iter();
1103
1104 let peer_index_table = record_iter.next().unwrap();
1105 let _ = elementor.record_to_elems(peer_index_table);
1106
1107 let record = record_iter.next().unwrap();
1108 let elems_vec = elementor.record_to_elems(record.clone());
1109 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1110 assert_eq!(elems_vec, elems_iter);
1111 assert!(!elems_vec.is_empty());
1112 }
1113
1114 #[test]
1115 fn test_record_to_elems_iter_tabledumpv2_with_peer_table() {
1116 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1117
1118 let parser = BgpkitParser::new(url).unwrap();
1119 let mut record_iter = parser.into_record_iter();
1120
1121 let peer_index_table = record_iter.next().unwrap();
1122 let mut elementor = Elementor::with_peer_table(
1123 if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)) =
1124 peer_index_table.message
1125 {
1126 pit
1127 } else {
1128 panic!("Expected PeerIndexTable");
1129 },
1130 );
1131
1132 let record = record_iter.next().unwrap();
1133 let elems_vec = elementor.record_to_elems(record.clone());
1134 let elems_iter: Vec<BgpElem> = elementor.record_to_elems_iter(record).unwrap().collect();
1135 assert_eq!(elems_vec, elems_iter);
1136 assert!(!elems_vec.is_empty());
1137 }
1138
1139 #[test]
1140 fn test_record_to_elems_iter_error_unexpected_peer_index_table() {
1141 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1142
1143 let elementor = Elementor::new();
1144 let parser = BgpkitParser::new(url).unwrap();
1145 let mut record_iter = parser.into_record_iter();
1146 let record = record_iter.next().unwrap();
1147
1148 let result = elementor.record_to_elems_iter(record);
1149 assert!(matches!(
1150 result,
1151 Err(ElemError::UnexpectedPeerIndexTable(_))
1152 ));
1153 }
1154
1155 #[test]
1156 fn test_record_to_elems_iter_error_missing_peer_table() {
1157 let url = "https://spaces.bgpkit.org/parser/rib-example-small.bz2";
1159
1160 let elementor = Elementor::new();
1161 let parser = BgpkitParser::new(url).unwrap();
1162 let mut record_iter = parser.into_record_iter();
1163
1164 let _peer_index_table = record_iter.next().unwrap();
1167
1168 let record = record_iter.next().unwrap();
1170 let result = elementor.record_to_elems_iter(record);
1171 assert!(matches!(result, Err(ElemError::MissingPeerTable)));
1172 }
1173
1174 #[test]
1175 fn test_bgp_to_elems_iter_equivalence() {
1176 let timestamp = 0.0;
1177 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1178 let peer_asn = Asn::new_32bit(65000);
1179
1180 let attributes = vec![
1181 AttributeValue::Origin(Origin::IGP),
1182 AttributeValue::AsPath {
1183 path: AsPath::from_sequence([65000, 65001, 65002]),
1184 is_as4: false,
1185 },
1186 AttributeValue::NextHop(peer_ip),
1187 ]
1188 .into_iter()
1189 .map(Attribute::from)
1190 .collect::<Vec<Attribute>>();
1191 let attributes = Attributes::from(attributes);
1192
1193 let announced_prefixes = vec![NetworkPrefix::from_str("10.0.0.0/24").unwrap()];
1194
1195 let bgp_message = BgpMessage::Update(BgpUpdateMessage {
1196 attributes,
1197 announced_prefixes,
1198 withdrawn_prefixes: vec![],
1199 });
1200
1201 let elems_vec =
1202 Elementor::bgp_to_elems(bgp_message.clone(), timestamp, &peer_ip, &peer_asn);
1203 let elems_iter: Vec<BgpElem> =
1204 Elementor::bgp_to_elems_iter(bgp_message, timestamp, &peer_ip, &peer_asn)
1205 .unwrap()
1206 .collect();
1207 assert_eq!(elems_vec, elems_iter);
1208 assert_eq!(elems_vec.len(), 1);
1209 }
1210
1211 #[test]
1212 fn test_bgp_to_elems_iter_non_update_messages() {
1213 use std::net::Ipv4Addr;
1214
1215 let timestamp = 0.0;
1216 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1217 let peer_asn = Asn::new_32bit(65000);
1218
1219 let open_msg = BgpOpenMessage {
1220 version: 4,
1221 asn: Asn::new_32bit(1),
1222 hold_time: 180,
1223 bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
1224 extended_length: false,
1225 opt_params: vec![],
1226 };
1227 assert!(Elementor::bgp_to_elems_iter(
1228 BgpMessage::Open(open_msg),
1229 timestamp,
1230 &peer_ip,
1231 &peer_asn
1232 )
1233 .is_none());
1234
1235 let notification_msg = BgpNotificationMessage {
1236 error: BgpError::Unknown(0, 0),
1237 data: vec![],
1238 };
1239 assert!(Elementor::bgp_to_elems_iter(
1240 BgpMessage::Notification(notification_msg),
1241 timestamp,
1242 &peer_ip,
1243 &peer_asn
1244 )
1245 .is_none());
1246
1247 assert!(Elementor::bgp_to_elems_iter(
1248 BgpMessage::KeepAlive,
1249 timestamp,
1250 &peer_ip,
1251 &peer_asn
1252 )
1253 .is_none());
1254 }
1255
1256 #[test]
1257 fn test_bgp_update_to_elems_iter_equivalence() {
1258 let timestamp = 0.0;
1259 let peer_ip = IpAddr::from_str("10.0.0.1").unwrap();
1260 let peer_asn = Asn::new_32bit(65000);
1261
1262 let attributes = vec![
1263 AttributeValue::Origin(Origin::IGP),
1264 AttributeValue::AsPath {
1265 path: AsPath::from_sequence([65000, 65001, 65002]),
1266 is_as4: false,
1267 },
1268 AttributeValue::NextHop(peer_ip),
1269 ]
1270 .into_iter()
1271 .map(Attribute::from)
1272 .collect::<Vec<Attribute>>();
1273 let attributes = Attributes::from(attributes);
1274
1275 let announced_prefixes = vec![NetworkPrefix::from_str("10.0.0.0/24").unwrap()];
1276 let withdrawn_prefixes = vec![NetworkPrefix::from_str("10.0.1.0/24").unwrap()];
1277
1278 let update = BgpUpdateMessage {
1279 attributes,
1280 announced_prefixes,
1281 withdrawn_prefixes,
1282 };
1283
1284 let elems_vec =
1285 Elementor::bgp_update_to_elems(update.clone(), timestamp, &peer_ip, &peer_asn);
1286 let elems_iter: Vec<BgpElem> =
1287 Elementor::bgp_update_to_elems_iter(update, timestamp, &peer_ip, &peer_asn).collect();
1288 assert_eq!(elems_vec, elems_iter);
1289 assert_eq!(elems_vec.len(), 2);
1290 }
1291
1292 #[test]
1293 fn test_record_elem_iter_size_hint() {
1294 use std::collections::HashMap;
1295
1296 let peer_table = PeerIndexTable {
1297 collector_bgp_id: BgpIdentifier::from_str("10.0.0.1").unwrap(),
1298 view_name: "".to_string(),
1299 id_peer_map: HashMap::new(),
1300 peer_ip_id_map: HashMap::new(),
1301 };
1302
1303 let entries: Vec<RibEntry> = vec![];
1304 let iter = RecordElemIter::RibAfi {
1305 peer_table: &peer_table,
1306 prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1307 entries: entries.into_iter(),
1308 };
1309 assert_eq!(iter.size_hint(), (0, Some(0)));
1310
1311 let entries: Vec<RibEntry> = (0..5)
1312 .map(|i| RibEntry {
1313 peer_index: i as u16,
1314 originated_time: 0,
1315 path_id: None,
1316 attributes: Attributes::default(),
1317 })
1318 .collect();
1319 let iter = RecordElemIter::RibAfi {
1320 peer_table: &peer_table,
1321 prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1322 entries: entries.into_iter(),
1323 };
1324 assert_eq!(iter.size_hint(), (5, Some(5)));
1325 }
1326}