1mod attr_01_origin;
2mod attr_02_17_as_path;
3mod attr_03_next_hop;
4mod attr_04_med;
5mod attr_05_local_pref;
6mod attr_07_18_aggregator;
7mod attr_08_communities;
8mod attr_09_originator;
9mod attr_10_13_cluster;
10mod attr_14_15_nlri;
11mod attr_16_25_extended_communities;
12mod attr_23_tunnel_encap;
13mod attr_26_aigp;
14mod attr_29_linkstate;
15mod attr_32_large_communities;
16mod attr_35_otc;
17mod attr_37_sfp;
18mod attr_38_bfd_discriminator;
19mod attr_40_bgp_prefix_sid;
20mod attr_41_bier;
21
22use bytes::{Buf, BufMut, Bytes, BytesMut};
23use log::{debug, warn};
24use std::net::IpAddr;
25
26use crate::models::*;
27
28use crate::error::{BgpValidationWarning, ParserError};
29use crate::parser::bgp::attributes::attr_01_origin::{encode_origin, parse_origin};
30use crate::parser::bgp::attributes::attr_02_17_as_path::encode_as_path;
31pub(crate) use crate::parser::bgp::attributes::attr_02_17_as_path::parse_as_path;
32use crate::parser::bgp::attributes::attr_03_next_hop::{encode_next_hop, parse_next_hop};
33use crate::parser::bgp::attributes::attr_04_med::{encode_med, parse_med};
34use crate::parser::bgp::attributes::attr_05_local_pref::{encode_local_pref, parse_local_pref};
35use crate::parser::bgp::attributes::attr_07_18_aggregator::{encode_aggregator, parse_aggregator};
36use crate::parser::bgp::attributes::attr_08_communities::{
37 encode_regular_communities, parse_regular_communities,
38};
39use crate::parser::bgp::attributes::attr_09_originator::{
40 encode_originator_id, parse_originator_id,
41};
42use crate::parser::bgp::attributes::attr_10_13_cluster::{encode_clusters, parse_clusters};
43use crate::parser::bgp::attributes::attr_14_15_nlri::encode_nlri;
44pub(crate) use crate::parser::bgp::attributes::attr_14_15_nlri::parse_nlri;
45use crate::parser::bgp::attributes::attr_16_25_extended_communities::{
46 encode_extended_communities, encode_ipv6_extended_communities, parse_extended_community,
47 parse_ipv6_extended_community,
48};
49use crate::parser::bgp::attributes::attr_23_tunnel_encap::{
50 encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute,
51};
52use crate::parser::bgp::attributes::attr_26_aigp::{encode_aigp, parse_aigp};
53use crate::parser::bgp::attributes::attr_29_linkstate::{
54 encode_link_state_attribute, parse_link_state_attribute,
55};
56use crate::parser::bgp::attributes::attr_32_large_communities::{
57 encode_large_communities, parse_large_communities,
58};
59use crate::parser::bgp::attributes::attr_35_otc::{
60 encode_only_to_customer, parse_only_to_customer,
61};
62use crate::parser::bgp::attributes::attr_37_sfp::{encode_sfp, parse_sfp};
63use crate::parser::bgp::attributes::attr_38_bfd_discriminator::{
64 encode_bfd_discriminator, parse_bfd_discriminator,
65};
66use crate::parser::bgp::attributes::attr_40_bgp_prefix_sid::{
67 encode_bgp_prefix_sid, parse_bgp_prefix_sid,
68};
69use crate::parser::bgp::attributes::attr_41_bier::{encode_bier, parse_bier};
70use crate::parser::ReadUtils;
71
72fn validate_attribute_flags(
74 attr_type: AttrType,
75 flags: AttrFlags,
76 warnings: &mut Vec<BgpValidationWarning>,
77) {
78 let expected_flags = match attr_type {
79 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP => AttrFlags::TRANSITIVE,
81 AttrType::ATOMIC_AGGREGATE => AttrFlags::TRANSITIVE,
83 AttrType::MULTI_EXIT_DISCRIMINATOR
85 | AttrType::ORIGINATOR_ID
86 | AttrType::CLUSTER_LIST
87 | AttrType::MP_REACHABLE_NLRI
88 | AttrType::MP_UNREACHABLE_NLRI => AttrFlags::OPTIONAL,
89 AttrType::AGGREGATOR
91 | AttrType::AS4_AGGREGATOR
92 | AttrType::AS4_PATH
93 | AttrType::COMMUNITIES
94 | AttrType::EXTENDED_COMMUNITIES
95 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
96 | AttrType::LARGE_COMMUNITIES
97 | AttrType::ONLY_TO_CUSTOMER => AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE,
98 AttrType::LOCAL_PREFERENCE => AttrFlags::TRANSITIVE,
100 _ => return, };
103
104 let relevant_flags = flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE);
106 if relevant_flags != expected_flags {
107 warnings.push(BgpValidationWarning::AttributeFlagsError {
108 attr_type,
109 expected_flags: expected_flags.bits(),
110 actual_flags: relevant_flags.bits(),
111 });
112 }
113
114 if flags.contains(AttrFlags::PARTIAL) {
116 match attr_type {
117 AttrType::ORIGIN
119 | AttrType::AS_PATH
120 | AttrType::NEXT_HOP
121 | AttrType::LOCAL_PREFERENCE
122 | AttrType::ATOMIC_AGGREGATE
123 | AttrType::MULTI_EXIT_DISCRIMINATOR
124 | AttrType::ORIGINATOR_ID
125 | AttrType::CLUSTER_LIST
126 | AttrType::MP_REACHABLE_NLRI
127 | AttrType::MP_UNREACHABLE_NLRI => {
128 warnings.push(BgpValidationWarning::AttributeFlagsError {
129 attr_type,
130 expected_flags: expected_flags.bits(),
131 actual_flags: flags.bits(),
132 });
133 }
134 _ => {} }
136 }
137}
138
139fn is_raw_retained_attr(attr_type: AttrType) -> bool {
140 matches!(
141 attr_type,
142 AttrType::RESERVED
143 | AttrType::PMSI_TUNNEL
144 | AttrType::TRAFFIC_ENGINEERING
145 | AttrType::PE_DISTINGUISHER_LABELS
146 | AttrType::BGPSEC_PATH
147 | AttrType::ATTR_SET
148 )
149}
150
151fn is_well_known_mandatory(attr_type: AttrType) -> bool {
153 matches!(
154 attr_type,
155 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP | AttrType::LOCAL_PREFERENCE
156 )
157}
158
159pub(crate) struct AttributeValidationState {
160 warnings: Vec<BgpValidationWarning>,
161 attr_mask: [u64; 4],
162}
163
164impl AttributeValidationState {
165 pub(crate) fn new() -> Self {
166 Self {
167 warnings: Vec::new(),
168 attr_mask: [0; 4],
169 }
170 }
171
172 fn has_raw_attr(&self, attr: u8) -> bool {
173 (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
174 }
175
176 pub(crate) fn has_attr(&self, attr_type: AttrType) -> bool {
177 self.has_raw_attr(u8::from(attr_type))
178 }
179
180 fn set_attr(&mut self, attr: u8) {
181 self.attr_mask[(attr / 64) as usize] |= 1u64 << (attr % 64);
182 }
183
184 pub(crate) fn observe_header(
185 &mut self,
186 raw_attr_type: u8,
187 attr_type: AttrType,
188 flags: AttrFlags,
189 length: usize,
190 ) -> bool {
191 if self.has_raw_attr(raw_attr_type) {
192 self.warnings
193 .push(BgpValidationWarning::DuplicateAttribute { attr_type });
194 }
195 self.set_attr(raw_attr_type);
196
197 validate_attribute_flags(attr_type, flags, &mut self.warnings);
198 validate_attribute_length(attr_type, length, &mut self.warnings);
199
200 flags.contains(AttrFlags::PARTIAL)
201 }
202
203 pub(crate) fn observe_parse_error(
204 &mut self,
205 attr_type: AttrType,
206 partial: bool,
207 error: &ParserError,
208 ) {
209 if partial {
210 self.warnings
211 .push(BgpValidationWarning::PartialAttributeError {
212 attr_type,
213 reason: error.to_string(),
214 });
215 debug!("PARTIAL attribute error: {}", error);
216 } else if is_well_known_mandatory(attr_type) {
217 self.warnings
218 .push(BgpValidationWarning::MalformedAttributeList {
219 reason: format!(
220 "Well-known mandatory attribute {} parsing failed: {}",
221 u8::from(attr_type),
222 error
223 ),
224 });
225 debug!(
226 "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
227 error
228 );
229 } else {
230 self.warnings
231 .push(BgpValidationWarning::OptionalAttributeError {
232 attr_type,
233 reason: error.to_string(),
234 });
235 debug!("Optional attribute error, discarding: {}", error);
236 }
237 }
238
239 pub(crate) fn check_mandatory_attributes(
240 &mut self,
241 is_announcement: bool,
242 has_standard_nlri: bool,
243 ) {
244 if !is_announcement {
245 return;
246 }
247
248 if !self.has_attr(AttrType::ORIGIN) {
249 self.warnings
250 .push(BgpValidationWarning::MissingWellKnownAttribute {
251 attr_type: AttrType::ORIGIN,
252 });
253 }
254
255 if !self.has_attr(AttrType::AS_PATH) {
256 self.warnings
257 .push(BgpValidationWarning::MissingWellKnownAttribute {
258 attr_type: AttrType::AS_PATH,
259 });
260 }
261
262 let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
263 if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
264 self.warnings
265 .push(BgpValidationWarning::MissingWellKnownAttribute {
266 attr_type: AttrType::NEXT_HOP,
267 });
268 }
269 }
270
271 pub(crate) fn finish(self) -> (Vec<BgpValidationWarning>, [u64; 4]) {
272 (self.warnings, self.attr_mask)
273 }
274}
275
276fn validate_attribute_length(
278 attr_type: AttrType,
279 length: usize,
280 warnings: &mut Vec<BgpValidationWarning>,
281) {
282 let expected_length = match attr_type {
283 AttrType::ORIGIN => Some(1),
284 AttrType::NEXT_HOP => Some(4), AttrType::MULTI_EXIT_DISCRIMINATOR => Some(4),
286 AttrType::LOCAL_PREFERENCE => Some(4),
287 AttrType::ATOMIC_AGGREGATE => Some(0),
288 AttrType::ORIGINATOR_ID => Some(4),
289 AttrType::ONLY_TO_CUSTOMER => Some(4),
290 AttrType::AS_PATH
292 | AttrType::AS4_PATH
293 | AttrType::AGGREGATOR
294 | AttrType::AS4_AGGREGATOR
295 | AttrType::COMMUNITIES
296 | AttrType::EXTENDED_COMMUNITIES
297 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
298 | AttrType::LARGE_COMMUNITIES
299 | AttrType::CLUSTER_LIST
300 | AttrType::MP_REACHABLE_NLRI
301 | AttrType::MP_UNREACHABLE_NLRI => None,
302 _ => None, };
304
305 if let Some(expected) = expected_length {
306 if length != expected {
307 warnings.push(BgpValidationWarning::AttributeLengthError {
308 attr_type,
309 expected_length: Some(expected),
310 actual_length: length,
311 });
312 }
313 }
314}
315
316pub fn parse_attributes(
321 mut data: Bytes,
322 asn_len: &AsnLength,
323 add_path: bool,
324 afi: Option<Afi>,
325 safi: Option<Safi>,
326 prefixes: Option<&[NetworkPrefix]>,
327) -> Result<Attributes, ParserError> {
328 let estimated_attrs = (data.remaining() / 3).min(256);
331 let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
332 let mut validation = AttributeValidationState::new();
333
334 while data.remaining() >= 3 {
335 let flag = AttrFlags::from_bits_retain(data.read_u8()?);
340 let attr_type = data.read_u8()?;
341 let attr_length = match flag.contains(AttrFlags::EXTENDED) {
342 false => data.read_u8()? as usize,
343 true => data.read_u16()? as usize,
344 };
345
346 debug!(
347 "reading attribute: type -- {:?}, length -- {}",
348 &attr_type, attr_length
349 );
350
351 let attr_type = AttrType::from(attr_type);
352
353 let partial = validation.observe_header(attr_type.into(), attr_type, flag, attr_length);
354
355 let bytes_left = data.remaining();
356
357 if data.remaining() < attr_length {
358 warn!(
359 "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
360 attr_type, attr_length, bytes_left
361 );
362 break;
364 }
365
366 data.has_n_remaining(attr_length)?;
368 let mut attr_data = data.split_to(attr_length);
369 let raw_bytes = attr_data.clone();
370 let raw_code = u8::from(attr_type);
371
372 if let Some(t) = get_deprecated_attr_type(raw_code) {
373 debug!("deprecated attribute type: {} - {}", raw_code, t);
374 attributes.push(Attribute {
375 value: AttributeValue::Deprecated(AttrRaw {
376 code: raw_code,
377 bytes: raw_bytes,
378 }),
379 flag,
380 });
381 continue;
382 }
383
384 if matches!(attr_type, AttrType::Unknown(_)) {
385 debug!("unknown attribute type: {}", raw_code);
386 attributes.push(Attribute {
387 value: AttributeValue::Unknown(AttrRaw {
388 code: raw_code,
389 bytes: raw_bytes,
390 }),
391 flag,
392 });
393 continue;
394 }
395
396 if is_raw_retained_attr(attr_type) {
397 debug!("raw-retained attribute type: {}", raw_code);
398 attributes.push(Attribute {
399 value: AttributeValue::Raw(AttrRaw {
400 code: raw_code,
401 bytes: raw_bytes,
402 }),
403 flag,
404 });
405 continue;
406 }
407
408 let attr = match attr_type {
409 AttrType::ORIGIN => parse_origin(attr_data),
410 AttrType::AS_PATH => {
411 parse_as_path(attr_data, asn_len).map(|path| AttributeValue::AsPath {
412 path,
413 is_as4: false,
414 })
415 }
416 AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
417 AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
418 AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
419 AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
420 AttrType::AGGREGATOR => {
421 parse_aggregator(attr_data, asn_len).map(|(asn, id)| AttributeValue::Aggregator {
422 asn,
423 id,
424 is_as4: false,
425 })
426 }
427 AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
428 AttrType::CLUSTER_LIST => parse_clusters(attr_data),
429 AttrType::MP_REACHABLE_NLRI => {
430 parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
431 }
432 AttrType::MP_UNREACHABLE_NLRI => {
433 parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
434 }
435 AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32)
436 .map(|path| AttributeValue::AsPath { path, is_as4: true }),
437 AttrType::AS4_AGGREGATOR => {
438 parse_aggregator(attr_data, &AsnLength::Bits32).map(|(asn, id)| {
439 AttributeValue::Aggregator {
440 asn,
441 id,
442 is_as4: true,
443 }
444 })
445 }
446
447 AttrType::COMMUNITIES => parse_regular_communities(attr_data),
449 AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
450 AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
451 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
452 parse_ipv6_extended_community(attr_data)
453 }
454 AttrType::DEVELOPMENT => {
455 let mut value = vec![];
456 for _i in 0..attr_length {
457 value.push(attr_data.read_u8()?);
458 }
459 Ok(AttributeValue::Development(value))
460 }
461 AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
462 AttrType::AIGP => parse_aigp(attr_data),
463 AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
464 AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
465 AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data),
466 AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data),
467 AttrType::BGP_PREFIX_SID => parse_bgp_prefix_sid(attr_data),
468 AttrType::BIER => parse_bier(attr_data),
469 _ => Err(ParserError::Unsupported(format!(
470 "unsupported attribute type: {attr_type:?}"
471 ))),
472 };
473
474 match attr {
475 Ok(value) => {
476 assert_eq!(attr_type, value.attr_type());
477 attributes.push(Attribute { value, flag });
478 }
479 Err(e) => {
480 validation.observe_parse_error(attr_type, partial, &e);
481 attributes.push(Attribute {
482 value: AttributeValue::Raw(AttrRaw {
483 code: raw_code,
484 bytes: raw_bytes,
485 }),
486 flag,
487 });
488 continue;
489 }
490 };
491 }
492
493 let (validation_warnings, attr_mask) = validation.finish();
494 Ok(Attributes {
495 inner: attributes,
496 validation_warnings,
497 attr_mask,
498 })
499}
500
501impl Attribute {
502 pub fn encode(&self, asn_len: AsnLength) -> Bytes {
503 let mut bytes = BytesMut::new();
504
505 let flag = self.flag.bits();
506 let type_code = self.value.attr_code();
507
508 bytes.put_u8(flag);
509 bytes.put_u8(type_code);
510
511 let value_bytes = match &self.value {
512 AttributeValue::Origin(v) => encode_origin(v),
513 AttributeValue::AsPath { path, is_as4 } => {
514 let four_byte = match is_as4 {
515 true => AsnLength::Bits32,
516 false => match asn_len.is_four_byte() {
517 true => AsnLength::Bits32,
518 false => AsnLength::Bits16,
519 },
520 };
521 encode_as_path(path, four_byte)
522 }
523 AttributeValue::NextHop(v) => encode_next_hop(v),
524 AttributeValue::MultiExitDiscriminator(v) => encode_med(*v),
525 AttributeValue::LocalPreference(v) => encode_local_pref(*v),
526 AttributeValue::OnlyToCustomer(v) => encode_only_to_customer(v.into()),
527 AttributeValue::AtomicAggregate => Bytes::default(),
528 AttributeValue::Aggregator { asn, id, is_as4: _ } => {
529 encode_aggregator(asn, &IpAddr::from(*id))
530 }
531 AttributeValue::Communities(v) => encode_regular_communities(v),
532 AttributeValue::ExtendedCommunities(v) => encode_extended_communities(v),
533 AttributeValue::LargeCommunities(v) => encode_large_communities(v),
534 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
535 encode_ipv6_extended_communities(v)
536 }
537 AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
538 AttributeValue::Clusters(v) => encode_clusters(v),
539 AttributeValue::MpReachNlri(v) => {
540 let add_path = v
542 .labeled_prefixes
543 .as_ref()
544 .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
545 encode_nlri(v, true, add_path).unwrap_or_else(|e| {
546 log::warn!("Failed to encode MP_REACH_NLRI: {}", e);
547 Bytes::new()
548 })
549 }
550 AttributeValue::MpUnreachNlri(v) => {
551 encode_nlri(v, false, false).unwrap_or_else(|e| {
553 log::warn!("Failed to encode MP_UNREACH_NLRI: {}", e);
554 Bytes::new()
555 })
556 }
557 AttributeValue::LinkState(v) => encode_link_state_attribute(v),
558 AttributeValue::TunnelEncapsulation(v) => encode_tunnel_encapsulation_attribute(v),
559 AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v),
560 AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v),
561 AttributeValue::Bier(v) => encode_bier(v),
562 AttributeValue::Sfp(v) => encode_sfp(v),
563 AttributeValue::Development(v) => Bytes::copy_from_slice(v),
564 AttributeValue::Raw(v) => v.bytes.clone(),
565 AttributeValue::Deprecated(v) => v.bytes.clone(),
566 AttributeValue::Unknown(v) => v.bytes.clone(),
567 AttributeValue::Aigp(v) => encode_aigp(v),
568 AttributeValue::AttrSet(_v) => {
569 Bytes::new()
571 }
572 };
573
574 match self.is_extended() {
575 false => {
576 bytes.put_u8(value_bytes.len() as u8);
577 }
578 true => {
579 bytes.put_u16(value_bytes.len() as u16);
580 }
581 }
582 bytes.extend(value_bytes);
583 bytes.freeze()
584 }
585}
586
587impl Attributes {
588 pub fn encode(&self, asn_len: AsnLength) -> Bytes {
589 let mut bytes = BytesMut::new();
590 for attr in &self.inner {
591 bytes.extend(attr.encode(asn_len));
592 }
593 bytes.freeze()
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 #[test]
602 fn test_unknwon_attribute_type() {
603 let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
604 let asn_len = AsnLength::Bits16;
605 let add_path = false;
606 let afi = None;
607 let safi = None;
608 let prefixes = None;
609 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
610 assert!(attributes.is_ok());
611 let attributes = attributes.unwrap();
612 assert_eq!(attributes.inner.len(), 1);
613 assert_eq!(
614 attributes.inner[0].value.attr_type(),
615 AttrType::Unknown(254)
616 );
617 }
618
619 #[test]
620 fn test_rfc7606_attribute_flags_error() {
621 let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); let asn_len = AsnLength::Bits16;
624 let add_path = false;
625 let afi = None;
626 let safi = None;
627 let prefixes = None;
628
629 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
630
631 assert!(attributes.has_validation_warnings());
633 let warnings = attributes.validation_warnings();
634 assert!(!warnings.is_empty());
636
637 match &warnings[0] {
638 BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
639 assert_eq!(*attr_type, AttrType::ORIGIN);
640 }
641 _ => panic!("Expected AttributeFlagsError warning"),
642 }
643 }
644
645 #[test]
646 fn test_rfc7606_missing_mandatory_attribute() {
647 let data = Bytes::from(vec![
649 0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, ]);
651 let asn_len = AsnLength::Bits16;
652 let add_path = false;
653 let afi = None;
654 let safi = None;
655 let prefixes = None;
656
657 let mut attributes =
658 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
659 attributes.check_mandatory_attributes(true, true);
661
662 assert!(attributes.has_validation_warnings());
664 let warnings = attributes.validation_warnings();
665 assert_eq!(warnings.len(), 3); for warning in warnings {
669 match warning {
670 BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
671 assert!(matches!(
672 attr_type,
673 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
674 ));
675 }
676 _ => panic!("Expected MissingWellKnownAttribute warning"),
677 }
678 }
679 }
680
681 #[test]
682 fn test_mp_reach_no_next_hop() {
683 let data = Bytes::from(vec![
687 0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, ]);
692 let asn_len = AsnLength::Bits16;
693 let add_path = false;
694 let afi = None;
695 let safi = None;
696 let prefixes = None;
697
698 let mut attributes =
699 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
700 attributes.check_mandatory_attributes(true, false);
702
703 let warnings = attributes.validation_warnings();
705 let has_next_hop_warning = warnings.iter().any(|w| {
706 matches!(
707 w,
708 BgpValidationWarning::MissingWellKnownAttribute {
709 attr_type: AttrType::NEXT_HOP
710 }
711 )
712 });
713 assert!(!has_next_hop_warning);
714 }
715
716 #[test]
717 fn test_pure_withdrawal_no_warnings() {
718 let data = Bytes::from(vec![]);
720 let asn_len = AsnLength::Bits16;
721 let add_path = false;
722 let afi = None;
723 let safi = None;
724 let prefixes = None;
725
726 let mut attributes =
727 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
728 attributes.check_mandatory_attributes(false, false);
730
731 assert!(!attributes.has_validation_warnings());
733
734 let data = Bytes::from(vec![
736 0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, ]);
738 let mut attributes =
739 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
740 attributes.check_mandatory_attributes(false, false);
741 assert!(!attributes.has_validation_warnings());
742 }
743
744 #[test]
745 fn test_rfc7606_duplicate_attribute() {
746 let data = Bytes::from(vec![
748 0x40, 0x01, 0x01, 0x00, 0x40, 0x01, 0x01, 0x01, ]);
751 let asn_len = AsnLength::Bits16;
752 let add_path = false;
753 let afi = None;
754 let safi = None;
755 let prefixes = None;
756
757 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
758
759 assert!(attributes.has_validation_warnings());
761 let warnings = attributes.validation_warnings();
762
763 let has_duplicate_warning = warnings
765 .iter()
766 .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
767 assert!(has_duplicate_warning);
768 }
769
770 #[test]
771 fn test_attribute_type_boundaries() {
772 let asn_len = AsnLength::Bits16;
773 let add_path = false;
774 let afi = None;
775 let safi = None;
776 let prefixes = None;
777
778 const REQUIRED_ATTRS: &[u8] = &[
780 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, ];
784
785 let mut data = REQUIRED_ATTRS.to_vec();
787 data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); let data = Bytes::from(data);
789
790 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
791
792 assert!(attributes.has_attr(AttrType::DEVELOPMENT));
793 assert!(!attributes.has_validation_warnings());
794
795 let mut data = REQUIRED_ATTRS.to_vec();
797 data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); let data = Bytes::from(data);
799
800 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
801
802 assert!(attributes.inner.iter().any(|attr| {
803 matches!(
804 &attr.value,
805 AttributeValue::Raw(raw)
806 if raw.code == u8::from(AttrType::RESERVED)
807 && raw.bytes == Bytes::from_static(&[0x01])
808 )
809 }));
810 assert!(!attributes.validation_warnings.iter().any(|vw| {
811 matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
812 }));
813 }
814
815 #[test]
816 fn test_raw_retention_for_known_unsupported_attribute() {
817 let data = Bytes::from(vec![0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc]); let attributes =
819 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
820
821 assert_eq!(attributes.inner.len(), 1);
822 match &attributes.inner[0].value {
823 AttributeValue::Raw(raw) => {
824 assert_eq!(raw.code, 22);
825 assert_eq!(raw.attr_type(), AttrType::PMSI_TUNNEL);
826 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb, 0xcc]));
827 }
828 value => panic!("expected Raw, got {value:?}"),
829 }
830 assert_eq!(
831 attributes.encode(AsnLength::Bits16),
832 Bytes::from_static(&[0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc])
833 );
834 }
835
836 #[test]
837 fn test_deprecated_code_13_retained_as_deprecated() {
838 let data = Bytes::from(vec![0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04]);
839 let attributes =
840 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
841
842 assert_eq!(attributes.inner.len(), 1);
843 match &attributes.inner[0].value {
844 AttributeValue::Deprecated(raw) => {
845 assert_eq!(raw.code, 13);
846 assert_eq!(raw.attr_type(), AttrType::Unknown(13));
847 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
848 }
849 value => panic!("expected Deprecated, got {value:?}"),
850 }
851 assert_eq!(
852 attributes.encode(AsnLength::Bits16),
853 Bytes::from_static(&[0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04])
854 );
855 }
856
857 #[test]
858 fn test_malformed_typed_attribute_falls_back_to_raw() {
859 let data = Bytes::from(vec![0x40, 0x03, 0x03, 0x01, 0x02, 0x03]); let attributes =
861 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
862
863 assert!(attributes.has_validation_warnings());
864 match &attributes.inner[0].value {
865 AttributeValue::Raw(raw) => {
866 assert_eq!(raw.code, 3);
867 assert_eq!(raw.attr_type(), AttrType::NEXT_HOP);
868 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03]));
869 }
870 value => panic!("expected Raw fallback, got {value:?}"),
871 }
872 assert_eq!(
873 attributes.encode(AsnLength::Bits16),
874 Bytes::from_static(&[0x40, 0x03, 0x03, 0x01, 0x02, 0x03])
875 );
876 }
877
878 #[test]
879 fn test_all_raw_retained_attribute_codes_parse_and_round_trip() {
880 let raw_codes = [0, 22, 24, 27, 33, 128];
881
882 for code in raw_codes {
883 let wire = vec![0xc0, code, 0x02, 0xaa, 0xbb];
884 let attributes = parse_attributes(
885 Bytes::from(wire.clone()),
886 &AsnLength::Bits16,
887 false,
888 None,
889 None,
890 None,
891 )
892 .unwrap();
893 assert_eq!(attributes.inner.len(), 1, "code {code}");
894 match &attributes.inner[0].value {
895 AttributeValue::Raw(raw) => {
896 assert_eq!(raw.code, code);
897 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
898 }
899 value => panic!("expected Raw for code {code}, got {value:?}"),
900 }
901 assert!(attributes.has_attr(AttrType::from(code)), "code {code}");
902 assert_eq!(attributes.encode(AsnLength::Bits16), Bytes::from(wire));
903 }
904 }
905
906 #[test]
907 fn test_unassigned_attribute_code_retained_as_unknown() {
908 let wire = vec![0xc0, 0x7f, 0x02, 0xaa, 0xbb];
909 let attributes = parse_attributes(
910 Bytes::from(wire.clone()),
911 &AsnLength::Bits16,
912 false,
913 None,
914 None,
915 None,
916 )
917 .unwrap();
918
919 assert_eq!(attributes.inner.len(), 1);
920 match &attributes.inner[0].value {
921 AttributeValue::Unknown(raw) => {
922 assert_eq!(raw.code, 0x7f);
923 assert_eq!(raw.attr_type(), AttrType::Unknown(0x7f));
924 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
925 }
926 value => panic!("expected Unknown, got {value:?}"),
927 }
928 assert!(attributes.has_attr(AttrType::Unknown(0x7f)));
929 assert_eq!(attributes.encode(AsnLength::Bits16), Bytes::from(wire));
930 }
931
932 #[test]
933 fn test_structured_tlv_attributes_parse_and_round_trip() {
934 let cases = [
935 (
936 vec![0xc0, 0x26, 0x05, 0x01, 0x01, 0x02, 0x03, 0x04],
937 "BFD Discriminator",
938 ),
939 (
940 vec![0xc0, 0x28, 0x05, 0x7f, 0x00, 0x02, 0xaa, 0xbb],
941 "BGP Prefix-SID",
942 ),
943 (
944 vec![0xc0, 0x29, 0x06, 0x12, 0x34, 0x00, 0x02, 0xde, 0xad],
945 "BIER",
946 ),
947 (vec![0xc0, 0x25, 0x05, 0x7f, 0x00, 0x02, 0xde, 0xad], "SFP"),
948 ];
949
950 for (wire, name) in cases {
951 let data = Bytes::from(wire.clone());
952 let attributes =
953 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
954 assert_eq!(attributes.inner.len(), 1, "{name}");
955 match (name, &attributes.inner[0].value) {
956 ("BFD Discriminator", AttributeValue::BfdDiscriminator(_))
957 | ("BGP Prefix-SID", AttributeValue::BgpPrefixSid(_))
958 | ("BIER", AttributeValue::Bier(_))
959 | ("SFP", AttributeValue::Sfp(_)) => {}
960 (_, value) => panic!("unexpected value for {name}: {value:?}"),
961 }
962 assert_eq!(
963 attributes.encode(AsnLength::Bits16),
964 Bytes::from(wire),
965 "{name}"
966 );
967 }
968 }
969
970 #[test]
971 fn test_rfc7606_attribute_length_error() {
972 let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
974 let asn_len = AsnLength::Bits16;
975 let add_path = false;
976 let afi = None;
977 let safi = None;
978 let prefixes = None;
979
980 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
981
982 assert!(attributes.has_validation_warnings());
984 let warnings = attributes.validation_warnings();
985
986 let has_length_warning = warnings
987 .iter()
988 .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
989 assert!(has_length_warning);
990 }
991
992 #[test]
993 fn test_rfc7606_no_session_reset() {
994 let data = Bytes::from(vec![
996 0x80, 0x01, 0x02, 0x00, 0x01, 0x40, 0x01, 0x01, 0x00, 0x40, 0xFF, 0x01, 0x00, ]);
1000 let asn_len = AsnLength::Bits16;
1001 let add_path = false;
1002 let afi = None;
1003 let safi = None;
1004 let prefixes = None;
1005
1006 let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
1008 assert!(result.is_ok());
1009
1010 let attributes = result.unwrap();
1011 assert!(attributes.has_validation_warnings());
1012
1013 let warnings = attributes.validation_warnings();
1015 assert!(!warnings.is_empty());
1016 }
1017}