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