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_29_linkstate;
14mod attr_32_large_communities;
15mod attr_35_otc;
16
17use bytes::{Buf, BufMut, Bytes, BytesMut};
18use log::{debug, warn};
19use std::net::IpAddr;
20
21use crate::models::*;
22
23use crate::error::{BgpValidationWarning, ParserError};
24use crate::parser::bgp::attributes::attr_01_origin::{encode_origin, parse_origin};
25use crate::parser::bgp::attributes::attr_02_17_as_path::encode_as_path;
26pub(crate) use crate::parser::bgp::attributes::attr_02_17_as_path::parse_as_path;
27use crate::parser::bgp::attributes::attr_03_next_hop::{encode_next_hop, parse_next_hop};
28use crate::parser::bgp::attributes::attr_04_med::{encode_med, parse_med};
29use crate::parser::bgp::attributes::attr_05_local_pref::{encode_local_pref, parse_local_pref};
30use crate::parser::bgp::attributes::attr_07_18_aggregator::{encode_aggregator, parse_aggregator};
31use crate::parser::bgp::attributes::attr_08_communities::{
32 encode_regular_communities, parse_regular_communities,
33};
34use crate::parser::bgp::attributes::attr_09_originator::{
35 encode_originator_id, parse_originator_id,
36};
37use crate::parser::bgp::attributes::attr_10_13_cluster::{encode_clusters, parse_clusters};
38use crate::parser::bgp::attributes::attr_14_15_nlri::encode_nlri;
39pub(crate) use crate::parser::bgp::attributes::attr_14_15_nlri::parse_nlri;
40use crate::parser::bgp::attributes::attr_16_25_extended_communities::{
41 encode_extended_communities, encode_ipv6_extended_communities, parse_extended_community,
42 parse_ipv6_extended_community,
43};
44use crate::parser::bgp::attributes::attr_23_tunnel_encap::{
45 encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute,
46};
47use crate::parser::bgp::attributes::attr_29_linkstate::{
48 encode_link_state_attribute, parse_link_state_attribute,
49};
50use crate::parser::bgp::attributes::attr_32_large_communities::{
51 encode_large_communities, parse_large_communities,
52};
53use crate::parser::bgp::attributes::attr_35_otc::{
54 encode_only_to_customer, parse_only_to_customer,
55};
56use crate::parser::ReadUtils;
57
58fn validate_attribute_flags(
60 attr_type: AttrType,
61 flags: AttrFlags,
62 warnings: &mut Vec<BgpValidationWarning>,
63) {
64 let expected_flags = match attr_type {
65 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP => AttrFlags::TRANSITIVE,
67 AttrType::ATOMIC_AGGREGATE => AttrFlags::TRANSITIVE,
69 AttrType::MULTI_EXIT_DISCRIMINATOR
71 | AttrType::ORIGINATOR_ID
72 | AttrType::CLUSTER_LIST
73 | AttrType::MP_REACHABLE_NLRI
74 | AttrType::MP_UNREACHABLE_NLRI => AttrFlags::OPTIONAL,
75 AttrType::AGGREGATOR
77 | AttrType::AS4_AGGREGATOR
78 | AttrType::AS4_PATH
79 | AttrType::COMMUNITIES
80 | AttrType::EXTENDED_COMMUNITIES
81 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
82 | AttrType::LARGE_COMMUNITIES
83 | AttrType::ONLY_TO_CUSTOMER => AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE,
84 AttrType::LOCAL_PREFERENCE => AttrFlags::TRANSITIVE,
86 _ => return, };
89
90 let relevant_flags = flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE);
92 if relevant_flags != expected_flags {
93 warnings.push(BgpValidationWarning::AttributeFlagsError {
94 attr_type,
95 expected_flags: expected_flags.bits(),
96 actual_flags: relevant_flags.bits(),
97 });
98 }
99
100 if flags.contains(AttrFlags::PARTIAL) {
102 match attr_type {
103 AttrType::ORIGIN
105 | AttrType::AS_PATH
106 | AttrType::NEXT_HOP
107 | AttrType::LOCAL_PREFERENCE
108 | AttrType::ATOMIC_AGGREGATE
109 | AttrType::MULTI_EXIT_DISCRIMINATOR
110 | AttrType::ORIGINATOR_ID
111 | AttrType::CLUSTER_LIST
112 | AttrType::MP_REACHABLE_NLRI
113 | AttrType::MP_UNREACHABLE_NLRI => {
114 warnings.push(BgpValidationWarning::AttributeFlagsError {
115 attr_type,
116 expected_flags: expected_flags.bits(),
117 actual_flags: flags.bits(),
118 });
119 }
120 _ => {} }
122 }
123}
124
125fn is_well_known_mandatory(attr_type: AttrType) -> bool {
127 matches!(
128 attr_type,
129 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP | AttrType::LOCAL_PREFERENCE
130 )
131}
132
133pub(crate) struct AttributeValidationState {
134 warnings: Vec<BgpValidationWarning>,
135 attr_mask: [u64; 4],
136}
137
138impl AttributeValidationState {
139 pub(crate) fn new() -> Self {
140 Self {
141 warnings: Vec::new(),
142 attr_mask: [0; 4],
143 }
144 }
145
146 fn has_raw_attr(&self, attr: u8) -> bool {
147 (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
148 }
149
150 pub(crate) fn has_attr(&self, attr_type: AttrType) -> bool {
151 self.has_raw_attr(u8::from(attr_type))
152 }
153
154 fn set_attr(&mut self, attr: u8) {
155 self.attr_mask[(attr / 64) as usize] |= 1u64 << (attr % 64);
156 }
157
158 pub(crate) fn observe_header(
159 &mut self,
160 raw_attr_type: u8,
161 attr_type: AttrType,
162 flags: AttrFlags,
163 length: usize,
164 ) -> bool {
165 if self.has_raw_attr(raw_attr_type) {
166 self.warnings
167 .push(BgpValidationWarning::DuplicateAttribute { attr_type });
168 }
169 self.set_attr(raw_attr_type);
170
171 validate_attribute_flags(attr_type, flags, &mut self.warnings);
172 validate_attribute_length(attr_type, length, &mut self.warnings);
173
174 flags.contains(AttrFlags::PARTIAL)
175 }
176
177 pub(crate) fn observe_parse_error(
178 &mut self,
179 attr_type: AttrType,
180 partial: bool,
181 error: &ParserError,
182 ) {
183 if partial {
184 self.warnings
185 .push(BgpValidationWarning::PartialAttributeError {
186 attr_type,
187 reason: error.to_string(),
188 });
189 debug!("PARTIAL attribute error: {}", error);
190 } else if is_well_known_mandatory(attr_type) {
191 self.warnings
192 .push(BgpValidationWarning::MalformedAttributeList {
193 reason: format!(
194 "Well-known mandatory attribute {} parsing failed: {}",
195 u8::from(attr_type),
196 error
197 ),
198 });
199 debug!(
200 "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
201 error
202 );
203 } else {
204 self.warnings
205 .push(BgpValidationWarning::OptionalAttributeError {
206 attr_type,
207 reason: error.to_string(),
208 });
209 debug!("Optional attribute error, discarding: {}", error);
210 }
211 }
212
213 pub(crate) fn check_mandatory_attributes(
214 &mut self,
215 is_announcement: bool,
216 has_standard_nlri: bool,
217 ) {
218 if !is_announcement {
219 return;
220 }
221
222 if !self.has_attr(AttrType::ORIGIN) {
223 self.warnings
224 .push(BgpValidationWarning::MissingWellKnownAttribute {
225 attr_type: AttrType::ORIGIN,
226 });
227 }
228
229 if !self.has_attr(AttrType::AS_PATH) {
230 self.warnings
231 .push(BgpValidationWarning::MissingWellKnownAttribute {
232 attr_type: AttrType::AS_PATH,
233 });
234 }
235
236 let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
237 if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
238 self.warnings
239 .push(BgpValidationWarning::MissingWellKnownAttribute {
240 attr_type: AttrType::NEXT_HOP,
241 });
242 }
243 }
244
245 pub(crate) fn finish(self) -> (Vec<BgpValidationWarning>, [u64; 4]) {
246 (self.warnings, self.attr_mask)
247 }
248}
249
250fn validate_attribute_length(
252 attr_type: AttrType,
253 length: usize,
254 warnings: &mut Vec<BgpValidationWarning>,
255) {
256 let expected_length = match attr_type {
257 AttrType::ORIGIN => Some(1),
258 AttrType::NEXT_HOP => Some(4), AttrType::MULTI_EXIT_DISCRIMINATOR => Some(4),
260 AttrType::LOCAL_PREFERENCE => Some(4),
261 AttrType::ATOMIC_AGGREGATE => Some(0),
262 AttrType::ORIGINATOR_ID => Some(4),
263 AttrType::ONLY_TO_CUSTOMER => Some(4),
264 AttrType::AS_PATH
266 | AttrType::AS4_PATH
267 | AttrType::AGGREGATOR
268 | AttrType::AS4_AGGREGATOR
269 | AttrType::COMMUNITIES
270 | AttrType::EXTENDED_COMMUNITIES
271 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
272 | AttrType::LARGE_COMMUNITIES
273 | AttrType::CLUSTER_LIST
274 | AttrType::MP_REACHABLE_NLRI
275 | AttrType::MP_UNREACHABLE_NLRI => None,
276 _ => None, };
278
279 if let Some(expected) = expected_length {
280 if length != expected {
281 warnings.push(BgpValidationWarning::AttributeLengthError {
282 attr_type,
283 expected_length: Some(expected),
284 actual_length: length,
285 });
286 }
287 }
288}
289
290pub fn parse_attributes(
295 mut data: Bytes,
296 asn_len: &AsnLength,
297 add_path: bool,
298 afi: Option<Afi>,
299 safi: Option<Safi>,
300 prefixes: Option<&[NetworkPrefix]>,
301) -> Result<Attributes, ParserError> {
302 let estimated_attrs = (data.remaining() / 3).min(256);
305 let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
306 let mut validation = AttributeValidationState::new();
307
308 while data.remaining() >= 3 {
309 let flag = AttrFlags::from_bits_retain(data.read_u8()?);
314 let attr_type = data.read_u8()?;
315 let attr_length = match flag.contains(AttrFlags::EXTENDED) {
316 false => data.read_u8()? as usize,
317 true => data.read_u16()? as usize,
318 };
319
320 debug!(
321 "reading attribute: type -- {:?}, length -- {}",
322 &attr_type, attr_length
323 );
324
325 let parsed_attr_type = AttrType::from(attr_type);
326
327 let partial = validation.observe_header(attr_type, parsed_attr_type, flag, attr_length);
328
329 let attr_type = match parsed_attr_type {
330 attr_type @ AttrType::Unknown(unknown_type) => {
331 let bytes = data.read_n_bytes(attr_length)?;
333 let attr_value = match get_deprecated_attr_type(unknown_type) {
334 Some(t) => {
335 debug!("deprecated attribute type: {} - {}", unknown_type, t);
336 AttributeValue::Deprecated(AttrRaw { attr_type, bytes })
337 }
338 None => {
339 debug!("unknown attribute type: {}", unknown_type);
340 AttributeValue::Unknown(AttrRaw { attr_type, bytes })
341 }
342 };
343
344 assert_eq!(attr_type, attr_value.attr_type());
345 attributes.push(Attribute {
346 value: attr_value,
347 flag,
348 });
349 continue;
350 }
351 t => t,
352 };
353
354 let bytes_left = data.remaining();
355
356 if data.remaining() < attr_length {
357 warn!(
358 "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
359 attr_type, attr_length, bytes_left
360 );
361 break;
363 }
364
365 data.has_n_remaining(attr_length)?;
367 let mut attr_data = data.split_to(attr_length);
368
369 let attr = match attr_type {
370 AttrType::ORIGIN => parse_origin(attr_data),
371 AttrType::AS_PATH => {
372 parse_as_path(attr_data, asn_len).map(|path| AttributeValue::AsPath {
373 path,
374 is_as4: false,
375 })
376 }
377 AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
378 AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
379 AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
380 AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
381 AttrType::AGGREGATOR => {
382 parse_aggregator(attr_data, asn_len).map(|(asn, id)| AttributeValue::Aggregator {
383 asn,
384 id,
385 is_as4: false,
386 })
387 }
388 AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
389 AttrType::CLUSTER_LIST => parse_clusters(attr_data),
390 AttrType::MP_REACHABLE_NLRI => {
391 parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
392 }
393 AttrType::MP_UNREACHABLE_NLRI => {
394 parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
395 }
396 AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32)
397 .map(|path| AttributeValue::AsPath { path, is_as4: true }),
398 AttrType::AS4_AGGREGATOR => {
399 parse_aggregator(attr_data, &AsnLength::Bits32).map(|(asn, id)| {
400 AttributeValue::Aggregator {
401 asn,
402 id,
403 is_as4: true,
404 }
405 })
406 }
407
408 AttrType::COMMUNITIES => parse_regular_communities(attr_data),
410 AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
411 AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
412 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
413 parse_ipv6_extended_community(attr_data)
414 }
415 AttrType::DEVELOPMENT => {
416 let mut value = vec![];
417 for _i in 0..attr_length {
418 value.push(attr_data.read_u8()?);
419 }
420 Ok(AttributeValue::Development(value))
421 }
422 AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
423 AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
424 AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
425 _ => Err(ParserError::Unsupported(format!(
426 "unsupported attribute type: {attr_type:?}"
427 ))),
428 };
429
430 match attr {
431 Ok(value) => {
432 assert_eq!(attr_type, value.attr_type());
433 attributes.push(Attribute { value, flag });
434 }
435 Err(e) => {
436 validation.observe_parse_error(attr_type, partial, &e);
437 continue;
438 }
439 };
440 }
441
442 let (validation_warnings, attr_mask) = validation.finish();
443 Ok(Attributes {
444 inner: attributes,
445 validation_warnings,
446 attr_mask,
447 })
448}
449
450impl Attribute {
451 pub fn encode(&self, asn_len: AsnLength) -> Bytes {
452 let mut bytes = BytesMut::new();
453
454 let flag = self.flag.bits();
455 let type_code = self.value.attr_type().into();
456
457 bytes.put_u8(flag);
458 bytes.put_u8(type_code);
459
460 let value_bytes = match &self.value {
461 AttributeValue::Origin(v) => encode_origin(v),
462 AttributeValue::AsPath { path, is_as4 } => {
463 let four_byte = match is_as4 {
464 true => AsnLength::Bits32,
465 false => match asn_len.is_four_byte() {
466 true => AsnLength::Bits32,
467 false => AsnLength::Bits16,
468 },
469 };
470 encode_as_path(path, four_byte)
471 }
472 AttributeValue::NextHop(v) => encode_next_hop(v),
473 AttributeValue::MultiExitDiscriminator(v) => encode_med(*v),
474 AttributeValue::LocalPreference(v) => encode_local_pref(*v),
475 AttributeValue::OnlyToCustomer(v) => encode_only_to_customer(v.into()),
476 AttributeValue::AtomicAggregate => Bytes::default(),
477 AttributeValue::Aggregator { asn, id, is_as4: _ } => {
478 encode_aggregator(asn, &IpAddr::from(*id))
479 }
480 AttributeValue::Communities(v) => encode_regular_communities(v),
481 AttributeValue::ExtendedCommunities(v) => encode_extended_communities(v),
482 AttributeValue::LargeCommunities(v) => encode_large_communities(v),
483 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
484 encode_ipv6_extended_communities(v)
485 }
486 AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
487 AttributeValue::Clusters(v) => encode_clusters(v),
488 AttributeValue::MpReachNlri(v) => {
489 let add_path = v
491 .labeled_prefixes
492 .as_ref()
493 .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
494 encode_nlri(v, true, add_path).unwrap_or_else(|e| {
495 log::warn!("Failed to encode MP_REACH_NLRI: {}", e);
496 Bytes::new()
497 })
498 }
499 AttributeValue::MpUnreachNlri(v) => {
500 encode_nlri(v, false, false).unwrap_or_else(|e| {
502 log::warn!("Failed to encode MP_UNREACH_NLRI: {}", e);
503 Bytes::new()
504 })
505 }
506 AttributeValue::LinkState(v) => encode_link_state_attribute(v),
507 AttributeValue::TunnelEncapsulation(v) => encode_tunnel_encapsulation_attribute(v),
508 AttributeValue::Development(v) => Bytes::from(v.to_owned()),
509 AttributeValue::Deprecated(v) => Bytes::from(v.bytes.to_owned()),
510 AttributeValue::Unknown(v) => Bytes::from(v.bytes.to_owned()),
511 AttributeValue::Aigp(_v) => {
512 Bytes::new()
514 }
515 AttributeValue::AttrSet(_v) => {
516 Bytes::new()
518 }
519 };
520
521 match self.is_extended() {
522 false => {
523 bytes.put_u8(value_bytes.len() as u8);
524 }
525 true => {
526 bytes.put_u16(value_bytes.len() as u16);
527 }
528 }
529 bytes.extend(value_bytes);
530 bytes.freeze()
531 }
532}
533
534impl Attributes {
535 pub fn encode(&self, asn_len: AsnLength) -> Bytes {
536 let mut bytes = BytesMut::new();
537 for attr in &self.inner {
538 bytes.extend(attr.encode(asn_len));
539 }
540 bytes.freeze()
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn test_unknwon_attribute_type() {
550 let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
551 let asn_len = AsnLength::Bits16;
552 let add_path = false;
553 let afi = None;
554 let safi = None;
555 let prefixes = None;
556 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
557 assert!(attributes.is_ok());
558 let attributes = attributes.unwrap();
559 assert_eq!(attributes.inner.len(), 1);
560 assert_eq!(
561 attributes.inner[0].value.attr_type(),
562 AttrType::Unknown(254)
563 );
564 }
565
566 #[test]
567 fn test_rfc7606_attribute_flags_error() {
568 let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); let asn_len = AsnLength::Bits16;
571 let add_path = false;
572 let afi = None;
573 let safi = None;
574 let prefixes = None;
575
576 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
577
578 assert!(attributes.has_validation_warnings());
580 let warnings = attributes.validation_warnings();
581 assert!(!warnings.is_empty());
583
584 match &warnings[0] {
585 BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
586 assert_eq!(*attr_type, AttrType::ORIGIN);
587 }
588 _ => panic!("Expected AttributeFlagsError warning"),
589 }
590 }
591
592 #[test]
593 fn test_rfc7606_missing_mandatory_attribute() {
594 let data = Bytes::from(vec![
596 0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, ]);
598 let asn_len = AsnLength::Bits16;
599 let add_path = false;
600 let afi = None;
601 let safi = None;
602 let prefixes = None;
603
604 let mut attributes =
605 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
606 attributes.check_mandatory_attributes(true, true);
608
609 assert!(attributes.has_validation_warnings());
611 let warnings = attributes.validation_warnings();
612 assert_eq!(warnings.len(), 3); for warning in warnings {
616 match warning {
617 BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
618 assert!(matches!(
619 attr_type,
620 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
621 ));
622 }
623 _ => panic!("Expected MissingWellKnownAttribute warning"),
624 }
625 }
626 }
627
628 #[test]
629 fn test_mp_reach_no_next_hop() {
630 let data = Bytes::from(vec![
634 0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, ]);
639 let asn_len = AsnLength::Bits16;
640 let add_path = false;
641 let afi = None;
642 let safi = None;
643 let prefixes = None;
644
645 let mut attributes =
646 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
647 attributes.check_mandatory_attributes(true, false);
649
650 let warnings = attributes.validation_warnings();
652 let has_next_hop_warning = warnings.iter().any(|w| {
653 matches!(
654 w,
655 BgpValidationWarning::MissingWellKnownAttribute {
656 attr_type: AttrType::NEXT_HOP
657 }
658 )
659 });
660 assert!(!has_next_hop_warning);
661 }
662
663 #[test]
664 fn test_pure_withdrawal_no_warnings() {
665 let data = Bytes::from(vec![]);
667 let asn_len = AsnLength::Bits16;
668 let add_path = false;
669 let afi = None;
670 let safi = None;
671 let prefixes = None;
672
673 let mut attributes =
674 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
675 attributes.check_mandatory_attributes(false, false);
677
678 assert!(!attributes.has_validation_warnings());
680
681 let data = Bytes::from(vec![
683 0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, ]);
685 let mut attributes =
686 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
687 attributes.check_mandatory_attributes(false, false);
688 assert!(!attributes.has_validation_warnings());
689 }
690
691 #[test]
692 fn test_rfc7606_duplicate_attribute() {
693 let data = Bytes::from(vec![
695 0x40, 0x01, 0x01, 0x00, 0x40, 0x01, 0x01, 0x01, ]);
698 let asn_len = AsnLength::Bits16;
699 let add_path = false;
700 let afi = None;
701 let safi = None;
702 let prefixes = None;
703
704 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
705
706 assert!(attributes.has_validation_warnings());
708 let warnings = attributes.validation_warnings();
709
710 let has_duplicate_warning = warnings
712 .iter()
713 .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
714 assert!(has_duplicate_warning);
715 }
716
717 #[test]
718 fn test_attribute_type_boundaries() {
719 let asn_len = AsnLength::Bits16;
720 let add_path = false;
721 let afi = None;
722 let safi = None;
723 let prefixes = None;
724
725 const REQUIRED_ATTRS: &[u8] = &[
727 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, ];
731
732 let mut data = REQUIRED_ATTRS.to_vec();
734 data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); let data = Bytes::from(data);
736
737 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
738
739 assert!(attributes.has_attr(AttrType::DEVELOPMENT));
740 assert!(!attributes.has_validation_warnings());
741
742 let mut data = REQUIRED_ATTRS.to_vec();
744 data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); let data = Bytes::from(data);
746
747 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
748
749 assert!(attributes.validation_warnings.iter().any(|vw| {
751 matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
752 }));
753 }
754
755 #[test]
756 fn test_rfc7606_attribute_length_error() {
757 let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
759 let asn_len = AsnLength::Bits16;
760 let add_path = false;
761 let afi = None;
762 let safi = None;
763 let prefixes = None;
764
765 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
766
767 assert!(attributes.has_validation_warnings());
769 let warnings = attributes.validation_warnings();
770
771 let has_length_warning = warnings
772 .iter()
773 .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
774 assert!(has_length_warning);
775 }
776
777 #[test]
778 fn test_rfc7606_no_session_reset() {
779 let data = Bytes::from(vec![
781 0x80, 0x01, 0x02, 0x00, 0x01, 0x40, 0x01, 0x01, 0x00, 0x40, 0xFF, 0x01, 0x00, ]);
785 let asn_len = AsnLength::Bits16;
786 let add_path = false;
787 let afi = None;
788 let safi = None;
789 let prefixes = None;
790
791 let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
793 assert!(result.is_ok());
794
795 let attributes = result.unwrap();
796 assert!(attributes.has_validation_warnings());
797
798 let warnings = attributes.validation_warnings();
800 assert!(!warnings.is_empty());
801 }
802}