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