1#![allow(unused)]
2use crate::models::*;
8use crate::parser::bgp::messages::parse_bgp_update_message;
9use crate::ParserError;
10use crate::ParserError::ParseError;
11use itertools::Itertools;
12use log::{error, warn};
13use std::collections::HashMap;
14use std::fmt::{Display, Formatter};
15use std::net::{IpAddr, Ipv4Addr};
16
17#[derive(Default, Debug, Clone)]
18pub struct Elementor {
19 pub peer_table: Option<PeerIndexTable>,
20}
21
22macro_rules! get_attr_value {
24 ($a:tt, $b:expr) => {
25 if let Attribute::$a(x) = $b {
26 Some(x)
27 } else {
28 None
29 }
30 };
31}
32
33#[allow(clippy::type_complexity)]
34fn get_relevant_attributes(
35 attributes: Attributes,
36) -> (
37 Option<AsPath>,
38 Option<AsPath>,
39 Option<Origin>,
40 Option<IpAddr>,
41 Option<u32>,
42 Option<u32>,
43 Option<Vec<MetaCommunity>>,
44 bool,
45 Option<(Asn, BgpIdentifier)>,
46 Option<Nlri>,
47 Option<Nlri>,
48 Option<Asn>,
49 Option<Vec<AttrRaw>>,
50 Option<Vec<AttrRaw>>,
51) {
52 let mut as_path = None;
53 let mut as4_path = None;
54 let mut origin = None;
55 let mut next_hop = None;
56 let mut local_pref = Some(0);
57 let mut med = Some(0);
58 let mut atomic = false;
59 let mut aggregator = None;
60 let mut announced = None;
61 let mut withdrawn = None;
62 let mut otc = None;
63 let mut unknown = vec![];
64 let mut deprecated = vec![];
65
66 let mut communities_vec: Vec<MetaCommunity> = vec![];
67
68 for attr in attributes {
69 match attr {
70 AttributeValue::Origin(v) => origin = Some(v),
71 AttributeValue::AsPath {
72 path,
73 is_as4: false,
74 } => as_path = Some(path),
75 AttributeValue::AsPath { path, is_as4: true } => as4_path = Some(path),
76 AttributeValue::NextHop(v) => next_hop = Some(v),
77 AttributeValue::MultiExitDiscriminator(v) => med = Some(v),
78 AttributeValue::LocalPreference(v) => local_pref = Some(v),
79 AttributeValue::AtomicAggregate => atomic = true,
80 AttributeValue::Communities(v) => communities_vec.extend(
81 v.into_iter()
82 .map(MetaCommunity::Plain)
83 .collect::<Vec<MetaCommunity>>(),
84 ),
85 AttributeValue::ExtendedCommunities(v) => communities_vec.extend(
86 v.into_iter()
87 .map(MetaCommunity::Extended)
88 .collect::<Vec<MetaCommunity>>(),
89 ),
90 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => communities_vec.extend(
91 v.into_iter()
92 .map(MetaCommunity::Ipv6Extended)
93 .collect::<Vec<MetaCommunity>>(),
94 ),
95 AttributeValue::LargeCommunities(v) => communities_vec.extend(
96 v.into_iter()
97 .map(MetaCommunity::Large)
98 .collect::<Vec<MetaCommunity>>(),
99 ),
100 AttributeValue::Aggregator { asn, id, .. } => aggregator = Some((asn, id)),
101 AttributeValue::MpReachNlri(nlri) => announced = Some(nlri),
102 AttributeValue::MpUnreachNlri(nlri) => withdrawn = Some(nlri),
103 AttributeValue::OnlyToCustomer(o) => otc = Some(o),
104
105 AttributeValue::Unknown(t) => {
106 unknown.push(t);
107 }
108 AttributeValue::Deprecated(t) => {
109 deprecated.push(t);
110 }
111
112 AttributeValue::OriginatorId(_)
113 | AttributeValue::Clusters(_)
114 | AttributeValue::Development(_)
115 | AttributeValue::LinkState(_)
116 | AttributeValue::TunnelEncapsulation(_) => {}
117 };
118 }
119
120 let communities = match !communities_vec.is_empty() {
121 true => Some(communities_vec),
122 false => None,
123 };
124
125 let next_hop = next_hop.or_else(|| {
127 announced.as_ref().and_then(|v| {
128 v.next_hop.as_ref().map(|h| match h {
129 NextHopAddress::Ipv4(v) => IpAddr::from(*v),
130 NextHopAddress::Ipv6(v) => IpAddr::from(*v),
131 NextHopAddress::Ipv6LinkLocal(v, _) => IpAddr::from(*v),
132 NextHopAddress::VpnIpv6(_, v) => IpAddr::from(*v),
134 NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => IpAddr::from(*v),
135 })
136 })
137 });
138
139 (
140 as_path,
141 as4_path,
142 origin,
143 next_hop,
144 local_pref,
145 med,
146 communities,
147 atomic,
148 aggregator,
149 announced,
150 withdrawn,
151 otc,
152 if unknown.is_empty() {
153 None
154 } else {
155 Some(unknown)
156 },
157 if deprecated.is_empty() {
158 None
159 } else {
160 Some(deprecated)
161 },
162 )
163}
164
165impl Elementor {
166 pub fn new() -> Elementor {
167 Self::default()
168 }
169
170 pub fn set_peer_table(&mut self, record: MrtRecord) -> Result<(), ParserError> {
199 if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(p)) =
200 record.message
201 {
202 self.peer_table = Some(p);
203 Ok(())
204 } else {
205 Err(ParseError("peer_table is not a PeerIndexTable".to_string()))
206 }
207 }
208
209 pub fn bgp_to_elems(
214 msg: BgpMessage,
215 timestamp: f64,
216 peer_ip: &IpAddr,
217 peer_asn: &Asn,
218 ) -> Vec<BgpElem> {
219 match msg {
220 BgpMessage::Update(msg) => {
221 Elementor::bgp_update_to_elems(msg, timestamp, peer_ip, peer_asn)
222 }
223 BgpMessage::Open(_) | BgpMessage::Notification(_) | BgpMessage::KeepAlive => {
224 vec![]
225 }
226 }
227 }
228
229 pub fn bgp_update_to_elems(
231 msg: BgpUpdateMessage,
232 timestamp: f64,
233 peer_ip: &IpAddr,
234 peer_asn: &Asn,
235 ) -> Vec<BgpElem> {
236 let mut elems = vec![];
237
238 let (
239 as_path,
240 as4_path, origin,
242 next_hop,
243 local_pref,
244 med,
245 communities,
246 atomic,
247 aggregator,
248 announced,
249 withdrawn,
250 only_to_customer,
251 unknown,
252 deprecated,
253 ) = get_relevant_attributes(msg.attributes);
254
255 let path = match (as_path, as4_path) {
256 (None, None) => None,
257 (Some(v), None) => Some(v),
258 (None, Some(v)) => Some(v),
259 (Some(v1), Some(v2)) => Some(AsPath::merge_aspath_as4path(&v1, &v2)),
260 };
261
262 let origin_asns = path
263 .as_ref()
264 .map(|as_path| as_path.iter_origins().collect());
265
266 elems.extend(msg.announced_prefixes.into_iter().map(|p| BgpElem {
267 timestamp,
268 elem_type: ElemType::ANNOUNCE,
269 peer_ip: *peer_ip,
270 peer_asn: *peer_asn,
271 prefix: p,
272 next_hop,
273 as_path: path.clone(),
274 origin_asns: origin_asns.clone(),
275 origin,
276 local_pref,
277 med,
278 communities: communities.clone(),
279 atomic,
280 aggr_asn: aggregator.as_ref().map(|v| v.0),
281 aggr_ip: aggregator.as_ref().map(|v| v.1),
282 only_to_customer,
283 unknown: unknown.clone(),
284 deprecated: deprecated.clone(),
285 }));
286
287 if let Some(nlri) = announced {
288 elems.extend(nlri.prefixes.into_iter().map(|p| BgpElem {
289 timestamp,
290 elem_type: ElemType::ANNOUNCE,
291 peer_ip: *peer_ip,
292 peer_asn: *peer_asn,
293 prefix: p,
294 next_hop,
295 as_path: path.clone(),
296 origin,
297 origin_asns: origin_asns.clone(),
298 local_pref,
299 med,
300 communities: communities.clone(),
301 atomic,
302 aggr_asn: aggregator.as_ref().map(|v| v.0),
303 aggr_ip: aggregator.as_ref().map(|v| v.1),
304 only_to_customer,
305 unknown: unknown.clone(),
306 deprecated: deprecated.clone(),
307 }));
308 }
309
310 elems.extend(msg.withdrawn_prefixes.into_iter().map(|p| BgpElem {
311 timestamp,
312 elem_type: ElemType::WITHDRAW,
313 peer_ip: *peer_ip,
314 peer_asn: *peer_asn,
315 prefix: p,
316 next_hop: None,
317 as_path: None,
318 origin: None,
319 origin_asns: None,
320 local_pref: None,
321 med: None,
322 communities: None,
323 atomic: false,
324 aggr_asn: None,
325 aggr_ip: None,
326 only_to_customer,
327 unknown: None,
328 deprecated: None,
329 }));
330 if let Some(nlri) = withdrawn {
331 elems.extend(nlri.prefixes.into_iter().map(|p| BgpElem {
332 timestamp,
333 elem_type: ElemType::WITHDRAW,
334 peer_ip: *peer_ip,
335 peer_asn: *peer_asn,
336 prefix: p,
337 next_hop: None,
338 as_path: None,
339 origin: None,
340 origin_asns: None,
341 local_pref: None,
342 med: None,
343 communities: None,
344 atomic: false,
345 aggr_asn: None,
346 aggr_ip: None,
347 only_to_customer,
348 unknown: None,
349 deprecated: None,
350 }));
351 };
352 elems
353 }
354
355 pub fn record_to_elems(&mut self, record: MrtRecord) -> Vec<BgpElem> {
357 let mut elems = vec![];
358 let t = record.common_header.timestamp;
359 let timestamp: f64 = if let Some(micro) = &record.common_header.microsecond_timestamp {
360 let m = (*micro as f64) / 1000000.0;
361 t as f64 + m
362 } else {
363 f64::from(t)
364 };
365
366 match record.message {
367 MrtMessage::TableDumpMessage(msg) => {
368 let (
369 as_path,
370 _as4_path, origin,
372 next_hop,
373 local_pref,
374 med,
375 communities,
376 atomic,
377 aggregator,
378 _announced,
379 _withdrawn,
380 only_to_customer,
381 unknown,
382 deprecated,
383 ) = get_relevant_attributes(msg.attributes);
384
385 let origin_asns = as_path
386 .as_ref()
387 .map(|as_path| as_path.iter_origins().collect());
388
389 elems.push(BgpElem {
390 timestamp,
391 elem_type: ElemType::ANNOUNCE,
392 peer_ip: msg.peer_ip,
393 peer_asn: msg.peer_asn,
394 prefix: msg.prefix,
395 next_hop,
396 as_path,
397 origin,
398 origin_asns,
399 local_pref,
400 med,
401 communities,
402 atomic,
403 aggr_asn: aggregator.map(|v| v.0),
404 aggr_ip: aggregator.map(|v| v.1),
405 only_to_customer,
406 unknown,
407 deprecated,
408 });
409 }
410
411 MrtMessage::TableDumpV2Message(msg) => {
412 match msg {
413 TableDumpV2Message::PeerIndexTable(p) => {
414 self.peer_table = Some(p);
415 }
416 TableDumpV2Message::RibAfi(t) => {
417 let prefix = t.prefix;
418 for e in t.rib_entries {
419 let pid = e.peer_index;
420 let peer = match self.peer_table.as_ref() {
421 None => {
422 error!("peer_table is None");
423 break;
424 }
425 Some(table) => match table.get_peer_by_id(&pid) {
426 None => {
427 error!("peer ID {} not found in peer_index table", pid);
428 break;
429 }
430 Some(peer) => peer,
431 },
432 };
433 let (
434 as_path,
435 as4_path, origin,
437 next_hop,
438 local_pref,
439 med,
440 communities,
441 atomic,
442 aggregator,
443 announced,
444 _withdrawn,
445 only_to_customer,
446 unknown,
447 deprecated,
448 ) = get_relevant_attributes(e.attributes);
449
450 let path = match (as_path, as4_path) {
451 (None, None) => None,
452 (Some(v), None) => Some(v),
453 (None, Some(v)) => Some(v),
454 (Some(v1), Some(v2)) => {
455 Some(AsPath::merge_aspath_as4path(&v1, &v2))
456 }
457 };
458
459 let next = match next_hop {
460 None => {
461 if let Some(v) = announced {
462 if let Some(h) = v.next_hop {
463 match h {
464 NextHopAddress::Ipv4(v) => Some(IpAddr::from(v)),
465 NextHopAddress::Ipv6(v) => Some(IpAddr::from(v)),
466 NextHopAddress::Ipv6LinkLocal(v, _) => {
467 Some(IpAddr::from(v))
468 }
469 NextHopAddress::VpnIpv6(_, v) => {
471 Some(IpAddr::from(v))
472 }
473 NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => {
474 Some(IpAddr::from(v))
475 }
476 }
477 } else {
478 None
479 }
480 } else {
481 None
482 }
483 }
484 Some(v) => Some(v),
485 };
486
487 let origin_asns = path
488 .as_ref()
489 .map(|as_path| as_path.iter_origins().collect());
490
491 elems.push(BgpElem {
492 timestamp,
493 elem_type: ElemType::ANNOUNCE,
494 peer_ip: peer.peer_ip,
495 peer_asn: peer.peer_asn,
496 prefix,
497 next_hop: next,
498 as_path: path,
499 origin,
500 origin_asns,
501 local_pref,
502 med,
503 communities,
504 atomic,
505 aggr_asn: aggregator.map(|v| v.0),
506 aggr_ip: aggregator.map(|v| v.1),
507 only_to_customer,
508 unknown,
509 deprecated,
510 });
511 }
512 }
513 TableDumpV2Message::RibGeneric(_t) => {
514 warn!(
515 "to_elem for TableDumpV2Message::RibGenericEntries not yet implemented"
516 );
517 }
518 TableDumpV2Message::GeoPeerTable(_t) => {
519 }
522 }
523 }
524 MrtMessage::Bgp4Mp(msg) => match msg {
525 Bgp4MpEnum::StateChange(_) => {}
526 Bgp4MpEnum::Message(v) => {
527 elems.extend(Elementor::bgp_to_elems(
528 v.bgp_message,
529 timestamp,
530 &v.peer_ip,
531 &v.peer_asn,
532 ));
533 }
534 },
535 }
536 elems
537 }
538}
539
540#[inline(always)]
541pub fn option_to_string<T>(o: &Option<T>) -> String
542where
543 T: Display,
544{
545 if let Some(v) = o {
546 v.to_string()
547 } else {
548 String::new()
549 }
550}
551
552impl From<&BgpElem> for Attributes {
553 fn from(value: &BgpElem) -> Self {
554 let mut values = Vec::<AttributeValue>::new();
555 let mut attributes = Attributes::default();
556 let prefix = value.prefix;
557
558 if value.elem_type == ElemType::WITHDRAW {
559 values.push(AttributeValue::MpUnreachNlri(Nlri::new_unreachable(prefix)));
560 attributes.extend(values);
561 return attributes;
562 }
563
564 values.push(AttributeValue::MpReachNlri(Nlri::new_reachable(
565 prefix,
566 value.next_hop,
567 )));
568
569 if let Some(v) = value.next_hop {
570 values.push(AttributeValue::NextHop(v));
571 }
572
573 if let Some(v) = value.as_path.as_ref() {
574 let is_as4 = match v.get_origin_opt() {
575 None => true,
576 Some(asn) => asn.is_four_byte(),
577 };
578 values.push(AttributeValue::AsPath {
579 path: v.clone(),
580 is_as4,
581 });
582 }
583
584 if let Some(v) = value.origin {
585 values.push(AttributeValue::Origin(v));
586 }
587
588 if let Some(v) = value.local_pref {
589 values.push(AttributeValue::LocalPreference(v));
590 }
591
592 if let Some(v) = value.med {
593 values.push(AttributeValue::MultiExitDiscriminator(v));
594 }
595
596 if let Some(v) = value.communities.as_ref() {
597 let mut communites = vec![];
598 let mut extended_communities = vec![];
599 let mut ipv6_extended_communities = vec![];
600 let mut large_communities = vec![];
601 for c in v {
602 match c {
603 MetaCommunity::Plain(v) => communites.push(*v),
604 MetaCommunity::Extended(v) => extended_communities.push(*v),
605 MetaCommunity::Large(v) => large_communities.push(*v),
606 MetaCommunity::Ipv6Extended(v) => ipv6_extended_communities.push(*v),
607 }
608 }
609 if !communites.is_empty() {
610 values.push(AttributeValue::Communities(communites));
611 }
612 if !extended_communities.is_empty() {
613 values.push(AttributeValue::ExtendedCommunities(extended_communities));
614 }
615 if !large_communities.is_empty() {
616 values.push(AttributeValue::LargeCommunities(large_communities));
617 }
618 if !ipv6_extended_communities.is_empty() {
619 values.push(AttributeValue::Ipv6AddressSpecificExtendedCommunities(
620 ipv6_extended_communities,
621 ));
622 }
623 }
624
625 if let Some(v) = value.aggr_asn {
626 let aggregator_id = match value.aggr_ip {
627 Some(v) => v,
628 None => Ipv4Addr::UNSPECIFIED,
629 };
630 values.push(AttributeValue::Aggregator {
631 asn: v,
632 id: aggregator_id,
633 is_as4: v.is_four_byte(),
634 });
635 }
636
637 if let Some(v) = value.only_to_customer {
638 values.push(AttributeValue::OnlyToCustomer(v));
639 }
640
641 if let Some(v) = value.unknown.as_ref() {
642 for t in v {
643 values.push(AttributeValue::Unknown(t.clone()));
644 }
645 }
646
647 if let Some(v) = value.deprecated.as_ref() {
648 for t in v {
649 values.push(AttributeValue::Deprecated(t.clone()));
650 }
651 }
652
653 attributes.extend(values);
654 attributes
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use crate::BgpkitParser;
662 use std::net::{Ipv4Addr, Ipv6Addr};
663 use std::str::FromStr;
664
665 #[test]
666 fn test_option_to_string() {
667 let o1 = Some(1);
668 let o2: Option<u32> = None;
669 assert_eq!(option_to_string(&o1), "1");
670 assert_eq!(option_to_string(&o2), "");
671 }
672
673 #[test]
674 fn test_record_to_elems() {
675 let url_table_dump_v1 = "https://data.ris.ripe.net/rrc00/2003.01/bview.20030101.0000.gz";
676 let url_table_dump_v2 = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
677 let url_bgp4mp = "https://data.ris.ripe.net/rrc00/2021.10/updates.20211001.0000.gz";
678
679 let mut elementor = Elementor::new();
680 let parser = BgpkitParser::new(url_table_dump_v1).unwrap();
681 let mut record_iter = parser.into_record_iter();
682 let record = record_iter.next().unwrap();
683 let elems = elementor.record_to_elems(record);
684 assert_eq!(elems.len(), 1);
685
686 let parser = BgpkitParser::new(url_table_dump_v2).unwrap();
687 let mut record_iter = parser.into_record_iter();
688 let peer_index_table = record_iter.next().unwrap();
689 let _elems = elementor.record_to_elems(peer_index_table);
690 let record = record_iter.next().unwrap();
691 let elems = elementor.record_to_elems(record);
692 assert!(!elems.is_empty());
693
694 let parser = BgpkitParser::new(url_bgp4mp).unwrap();
695 let mut record_iter = parser.into_record_iter();
696 let record = record_iter.next().unwrap();
697 let elems = elementor.record_to_elems(record);
698 assert!(!elems.is_empty());
699 }
700
701 #[test]
702 fn test_attributes_from_bgp_elem() {
703 let mut elem = BgpElem {
704 timestamp: 0.0,
705 elem_type: ElemType::ANNOUNCE,
706 peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
707 peer_asn: Asn::new_32bit(65000),
708 prefix: NetworkPrefix::from_str("10.0.1.0/24").unwrap(),
709 next_hop: Some(IpAddr::from_str("10.0.0.2").unwrap()),
710 as_path: Some(AsPath::from_sequence([65000, 65001, 65002])),
711 origin: Some(Origin::EGP),
712 origin_asns: Some(vec![Asn::new_32bit(65000)]),
713 local_pref: Some(100),
714 med: Some(200),
715 communities: Some(vec![
716 MetaCommunity::Plain(Community::NoAdvertise),
717 MetaCommunity::Extended(ExtendedCommunity::Raw([0, 0, 0, 0, 0, 0, 0, 0])),
718 MetaCommunity::Large(LargeCommunity {
719 global_admin: 0,
720 local_data: [0, 0],
721 }),
722 MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
723 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
724 subtype: 0,
725 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
726 local_admin: [0, 0],
727 }),
728 ]),
729 atomic: false,
730 aggr_asn: Some(Asn::new_32bit(65000)),
731 aggr_ip: Some(Ipv4Addr::from_str("10.2.0.0").unwrap()),
732 only_to_customer: Some(Asn::new_32bit(65000)),
733 unknown: Some(vec![AttrRaw {
734 attr_type: AttrType::RESERVED,
735 bytes: vec![],
736 }]),
737 deprecated: Some(vec![AttrRaw {
738 attr_type: AttrType::RESERVED,
739 bytes: vec![],
740 }]),
741 };
742
743 let _attributes = Attributes::from(&elem);
744 elem.elem_type = ElemType::WITHDRAW;
745 let _attributes = Attributes::from(&elem);
746 }
747
748 #[test]
749 fn test_get_relevant_attributes() {
750 let attributes = vec![
751 AttributeValue::Origin(Origin::IGP),
752 AttributeValue::AsPath {
753 path: AsPath::from_sequence([65000, 65001, 65002]),
754 is_as4: true,
755 },
756 AttributeValue::NextHop(IpAddr::from_str("10.0.0.1").unwrap()),
757 AttributeValue::MultiExitDiscriminator(100),
758 AttributeValue::LocalPreference(200),
759 AttributeValue::AtomicAggregate,
760 AttributeValue::Aggregator {
761 asn: Asn::new_32bit(65000),
762 id: Ipv4Addr::from_str("10.0.0.1").unwrap(),
763 is_as4: false,
764 },
765 AttributeValue::Communities(vec![Community::NoExport]),
766 AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([
767 0, 0, 0, 0, 0, 0, 0, 0,
768 ])]),
769 AttributeValue::LargeCommunities(vec![LargeCommunity {
770 global_admin: 0,
771 local_data: [0, 0],
772 }]),
773 AttributeValue::Ipv6AddressSpecificExtendedCommunities(vec![Ipv6AddrExtCommunity {
774 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
775 subtype: 0,
776 global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
777 local_admin: [0, 0],
778 }]),
779 AttributeValue::MpReachNlri(Nlri::new_reachable(
780 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
781 Some(IpAddr::from_str("10.0.0.1").unwrap()),
782 )),
783 AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
784 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
785 )),
786 AttributeValue::OnlyToCustomer(Asn::new_32bit(65000)),
787 AttributeValue::Unknown(AttrRaw {
788 attr_type: AttrType::RESERVED,
789 bytes: vec![],
790 }),
791 AttributeValue::Deprecated(AttrRaw {
792 attr_type: AttrType::RESERVED,
793 bytes: vec![],
794 }),
795 ]
796 .into_iter()
797 .map(Attribute::from)
798 .collect::<Vec<Attribute>>();
799
800 let attributes = Attributes::from(attributes);
801
802 let (
803 _as_path,
804 _as4_path, _origin,
806 _next_hop,
807 _local_pref,
808 _med,
809 _communities,
810 _atomic,
811 _aggregator,
812 _announced,
813 _withdrawn,
814 _only_to_customer,
815 _unknown,
816 _deprecated,
817 ) = get_relevant_attributes(attributes);
818 }
819
820 #[test]
821 fn test_next_hop_from_nlri() {
822 let attributes = vec![AttributeValue::NextHop(
823 IpAddr::from_str("10.0.0.1").unwrap(),
824 )]
825 .into_iter()
826 .map(Attribute::from)
827 .collect::<Vec<Attribute>>();
828
829 let attributes = Attributes::from(attributes);
830
831 let (
832 _as_path,
833 _as4_path, _origin,
835 next_hop,
836 _local_pref,
837 _med,
838 _communities,
839 _atomic,
840 _aggregator,
841 _announced,
842 _withdrawn,
843 _only_to_customer,
844 _unknown,
845 _deprecated,
846 ) = get_relevant_attributes(attributes);
847
848 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.1").unwrap()));
849
850 let attributes = vec![AttributeValue::MpReachNlri(Nlri::new_reachable(
851 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
852 Some(IpAddr::from_str("10.0.0.2").unwrap()),
853 ))]
854 .into_iter()
855 .map(Attribute::from)
856 .collect::<Vec<Attribute>>();
857
858 let attributes = Attributes::from(attributes);
859
860 let (
861 _as_path,
862 _as4_path, _origin,
864 next_hop,
865 _local_pref,
866 _med,
867 _communities,
868 _atomic,
869 _aggregator,
870 _announced,
871 _withdrawn,
872 _only_to_customer,
873 _unknown,
874 _deprecated,
875 ) = get_relevant_attributes(attributes);
876
877 assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.2").unwrap()));
878 }
879}