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 => parse_as_path(attr_data, asn_len).map(AttributeValue::AsPath),
417 AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
418 AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
419 AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
420 AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
421 AttrType::AGGREGATOR => parse_aggregator(attr_data, asn_len)
422 .map(|(asn, id)| AttributeValue::Aggregator { asn, id }),
423 AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
424 AttrType::CLUSTER_LIST => parse_clusters(attr_data),
425 AttrType::MP_REACHABLE_NLRI => {
426 parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
427 }
428 AttrType::MP_UNREACHABLE_NLRI => {
429 parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
430 }
431 AttrType::AS4_PATH => {
432 parse_as_path(attr_data, &AsnLength::Bits32).map(AttributeValue::As4Path)
433 }
434 AttrType::AS4_AGGREGATOR => parse_aggregator(attr_data, &AsnLength::Bits32)
435 .map(|(asn, id)| AttributeValue::As4Aggregator { asn, id }),
436
437 AttrType::COMMUNITIES => parse_regular_communities(attr_data),
439 AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
440 AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
441 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
442 parse_ipv6_extended_community(attr_data)
443 }
444 AttrType::DEVELOPMENT => {
445 let mut value = vec![];
446 for _i in 0..attr_length {
447 value.push(attr_data.read_u8()?);
448 }
449 Ok(AttributeValue::Development(value))
450 }
451 AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
452 AttrType::AIGP => parse_aigp(attr_data),
453 AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
454 AttrType::TRAFFIC_ENGINEERING => parse_traffic_engineering(attr_data),
455 AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
456 AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data),
457 AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data),
458 AttrType::BGP_PREFIX_SID => parse_bgp_prefix_sid(attr_data),
459 AttrType::BIER => parse_bier(attr_data),
460 _ => Err(ParserError::Unsupported(format!(
461 "unsupported attribute type: {attr_type:?}"
462 ))),
463 };
464
465 match attr {
466 Ok(value) => {
467 assert_eq!(attr_type, value.attr_type());
468 attributes.push(Attribute { value, flag });
469 }
470 Err(e) => {
471 validation.observe_parse_error(attr_type, partial, &e);
472 attributes.push(Attribute {
473 value: AttributeValue::Raw(AttrRaw {
474 code: raw_code,
475 bytes: raw_bytes,
476 }),
477 flag,
478 });
479 continue;
480 }
481 };
482 }
483
484 let (validation_warnings, attr_mask) = validation.finish();
485 Ok(Attributes {
486 inner: attributes,
487 validation_warnings,
488 attr_mask,
489 })
490}
491
492impl Attribute {
493 pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
499 buf.put_u8(self.flag.bits());
500 buf.put_u8(self.value.attr_code());
501
502 let write_value = |b: &mut BytesMut| -> Result<(), EncodingError> {
503 match &self.value {
504 AttributeValue::Origin(v) => b.put_slice(&encode_origin(v)),
505 AttributeValue::AsPath(path) => {
507 encode_as_path(path, asn_len, b)?;
508 }
509 AttributeValue::As4Path(path) => {
512 encode_as_path(path, AsnLength::Bits32, b)?;
513 }
514 AttributeValue::NextHop(v) => b.put_slice(&encode_next_hop(v)),
515 AttributeValue::MultiExitDiscriminator(v) => b.put_slice(&encode_med(*v)),
516 AttributeValue::LocalPreference(v) => b.put_slice(&encode_local_pref(*v)),
517 AttributeValue::OnlyToCustomer(v) => {
518 b.put_slice(&encode_only_to_customer(v.into()))
519 }
520 AttributeValue::AtomicAggregate => {}
521 AttributeValue::Aggregator { asn, id } => {
522 b.put_slice(&encode_aggregator(asn, &IpAddr::from(*id), asn_len)?)
523 }
524 AttributeValue::As4Aggregator { asn, id } => b.put_slice(&encode_aggregator(
525 asn,
526 &IpAddr::from(*id),
527 AsnLength::Bits32,
528 )?),
529 AttributeValue::Communities(v) => b.put_slice(&encode_regular_communities(v)),
530 AttributeValue::ExtendedCommunities(v) => {
531 b.put_slice(&encode_extended_communities(v))
532 }
533 AttributeValue::LargeCommunities(v) => b.put_slice(&encode_large_communities(v)),
534 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
535 b.put_slice(&encode_ipv6_extended_communities(v))
536 }
537 AttributeValue::OriginatorId(v) => {
538 b.put_slice(&encode_originator_id(&IpAddr::from(*v)))
539 }
540 AttributeValue::Clusters(v) => b.put_slice(&encode_clusters(v)),
541 AttributeValue::MpReachNlri(v) => {
542 let add_path = v
544 .labeled_prefixes
545 .as_ref()
546 .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
547 encode_nlri(v, true, add_path, b)?;
548 }
549 AttributeValue::MpUnreachNlri(v) => {
550 encode_nlri(v, false, false, b)?;
552 }
553 AttributeValue::LinkState(v) => encode_link_state_attribute(v, b)?,
554 AttributeValue::TunnelEncapsulation(v) => {
555 encode_tunnel_encapsulation_attribute(v, b)?
556 }
557 AttributeValue::TrafficEngineering(v) => encode_traffic_engineering(v, b)?,
558 AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v, b)?,
559 AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v, b)?,
560 AttributeValue::Bier(v) => encode_bier(v, b)?,
561 AttributeValue::Sfp(v) => encode_sfp(v, b)?,
562 AttributeValue::Development(v) => b.put_slice(v),
563 AttributeValue::Raw(v) => b.put_slice(&v.bytes),
564 AttributeValue::Deprecated(v) => b.put_slice(&v.bytes),
565 AttributeValue::Unknown(v) => b.put_slice(&v.bytes),
566 AttributeValue::Aigp(v) => b.put_slice(&encode_aigp(v)),
567 AttributeValue::AttrSet(_v) => {
568 return Err(EncodingError::unencodable(
569 "ATTR_SET attribute",
570 "encoding not implemented",
571 ));
572 }
573 }
574 Ok(())
575 };
576
577 match self.is_extended() {
578 false => with_u8_len(buf, "BGP attribute value length", write_value),
579 true => with_u16_len(buf, "BGP attribute value length (extended)", write_value),
580 }
581 }
582
583 pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
585 let mut buf = BytesMut::new();
586 self.encode_to(asn_len, &mut buf)?;
587 Ok(buf.freeze())
588 }
589}
590
591impl Attributes {
592 pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
594 for attr in &self.inner {
595 attr.encode_to(asn_len, buf)?;
596 }
597 Ok(())
598 }
599
600 pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
602 let mut buf = BytesMut::new();
603 self.encode_to(asn_len, &mut buf)?;
604 Ok(buf.freeze())
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn test_unknwon_attribute_type() {
614 let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
615 let asn_len = AsnLength::Bits16;
616 let add_path = false;
617 let afi = None;
618 let safi = None;
619 let prefixes = None;
620 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
621 assert!(attributes.is_ok());
622 let attributes = attributes.unwrap();
623 assert_eq!(attributes.inner.len(), 1);
624 assert_eq!(
625 attributes.inner[0].value.attr_type(),
626 AttrType::Unknown(254)
627 );
628 }
629
630 #[test]
631 fn test_rfc7606_attribute_flags_error() {
632 let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); let asn_len = AsnLength::Bits16;
635 let add_path = false;
636 let afi = None;
637 let safi = None;
638 let prefixes = None;
639
640 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
641
642 assert!(attributes.has_validation_warnings());
644 let warnings = attributes.validation_warnings();
645 assert!(!warnings.is_empty());
647
648 match &warnings[0] {
649 BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
650 assert_eq!(*attr_type, AttrType::ORIGIN);
651 }
652 _ => panic!("Expected AttributeFlagsError warning"),
653 }
654 }
655
656 #[test]
657 fn test_rfc7606_missing_mandatory_attribute() {
658 let data = Bytes::from(vec![
660 0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, ]);
662 let asn_len = AsnLength::Bits16;
663 let add_path = false;
664 let afi = None;
665 let safi = None;
666 let prefixes = None;
667
668 let mut attributes =
669 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
670 attributes.check_mandatory_attributes(true, true);
672
673 assert!(attributes.has_validation_warnings());
675 let warnings = attributes.validation_warnings();
676 assert_eq!(warnings.len(), 3); for warning in warnings {
680 match warning {
681 BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
682 assert!(matches!(
683 attr_type,
684 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
685 ));
686 }
687 _ => panic!("Expected MissingWellKnownAttribute warning"),
688 }
689 }
690 }
691
692 #[test]
693 fn test_mp_reach_no_next_hop() {
694 let data = Bytes::from(vec![
698 0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, ]);
703 let asn_len = AsnLength::Bits16;
704 let add_path = false;
705 let afi = None;
706 let safi = None;
707 let prefixes = None;
708
709 let mut attributes =
710 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
711 attributes.check_mandatory_attributes(true, false);
713
714 let warnings = attributes.validation_warnings();
716 let has_next_hop_warning = warnings.iter().any(|w| {
717 matches!(
718 w,
719 BgpValidationWarning::MissingWellKnownAttribute {
720 attr_type: AttrType::NEXT_HOP
721 }
722 )
723 });
724 assert!(!has_next_hop_warning);
725 }
726
727 #[test]
728 fn test_pure_withdrawal_no_warnings() {
729 let data = Bytes::from(vec![]);
731 let asn_len = AsnLength::Bits16;
732 let add_path = false;
733 let afi = None;
734 let safi = None;
735 let prefixes = None;
736
737 let mut attributes =
738 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
739 attributes.check_mandatory_attributes(false, false);
741
742 assert!(!attributes.has_validation_warnings());
744
745 let data = Bytes::from(vec![
747 0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, ]);
749 let mut attributes =
750 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
751 attributes.check_mandatory_attributes(false, false);
752 assert!(!attributes.has_validation_warnings());
753 }
754
755 #[test]
756 fn test_rfc7606_duplicate_attribute() {
757 let data = Bytes::from(vec![
759 0x40, 0x01, 0x01, 0x00, 0x40, 0x01, 0x01, 0x01, ]);
762 let asn_len = AsnLength::Bits16;
763 let add_path = false;
764 let afi = None;
765 let safi = None;
766 let prefixes = None;
767
768 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
769
770 assert!(attributes.has_validation_warnings());
772 let warnings = attributes.validation_warnings();
773
774 let has_duplicate_warning = warnings
776 .iter()
777 .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
778 assert!(has_duplicate_warning);
779 }
780
781 #[test]
782 fn test_attribute_type_boundaries() {
783 let asn_len = AsnLength::Bits16;
784 let add_path = false;
785 let afi = None;
786 let safi = None;
787 let prefixes = None;
788
789 const REQUIRED_ATTRS: &[u8] = &[
791 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, ];
795
796 let mut data = REQUIRED_ATTRS.to_vec();
798 data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); let data = Bytes::from(data);
800
801 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
802
803 assert!(attributes.has_attr(AttrType::DEVELOPMENT));
804 assert!(!attributes.has_validation_warnings());
805
806 let mut data = REQUIRED_ATTRS.to_vec();
808 data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); let data = Bytes::from(data);
810
811 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
812
813 assert!(attributes.inner.iter().any(|attr| {
814 matches!(
815 &attr.value,
816 AttributeValue::Raw(raw)
817 if raw.code == u8::from(AttrType::RESERVED)
818 && raw.bytes == Bytes::from_static(&[0x01])
819 )
820 }));
821 assert!(!attributes.validation_warnings.iter().any(|vw| {
822 matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
823 }));
824 }
825
826 #[test]
827 fn test_raw_retention_for_known_unsupported_attribute() {
828 let data = Bytes::from(vec![0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc]); let attributes =
830 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
831
832 assert_eq!(attributes.inner.len(), 1);
833 match &attributes.inner[0].value {
834 AttributeValue::Raw(raw) => {
835 assert_eq!(raw.code, 22);
836 assert_eq!(raw.attr_type(), AttrType::PMSI_TUNNEL);
837 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb, 0xcc]));
838 }
839 value => panic!("expected Raw, got {value:?}"),
840 }
841 assert_eq!(
842 attributes.encode(AsnLength::Bits16).unwrap(),
843 Bytes::from_static(&[0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc])
844 );
845 }
846
847 #[test]
848 fn test_deprecated_code_13_retained_as_deprecated() {
849 let data = Bytes::from(vec![0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04]);
850 let attributes =
851 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
852
853 assert_eq!(attributes.inner.len(), 1);
854 match &attributes.inner[0].value {
855 AttributeValue::Deprecated(raw) => {
856 assert_eq!(raw.code, 13);
857 assert_eq!(raw.attr_type(), AttrType::Unknown(13));
858 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
859 }
860 value => panic!("expected Deprecated, got {value:?}"),
861 }
862 assert_eq!(
863 attributes.encode(AsnLength::Bits16).unwrap(),
864 Bytes::from_static(&[0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04])
865 );
866 }
867
868 #[test]
869 fn test_malformed_typed_attribute_falls_back_to_raw() {
870 let data = Bytes::from(vec![0x40, 0x03, 0x03, 0x01, 0x02, 0x03]); let attributes =
872 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
873
874 assert!(attributes.has_validation_warnings());
875 match &attributes.inner[0].value {
876 AttributeValue::Raw(raw) => {
877 assert_eq!(raw.code, 3);
878 assert_eq!(raw.attr_type(), AttrType::NEXT_HOP);
879 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03]));
880 }
881 value => panic!("expected Raw fallback, got {value:?}"),
882 }
883 assert_eq!(
884 attributes.encode(AsnLength::Bits16).unwrap(),
885 Bytes::from_static(&[0x40, 0x03, 0x03, 0x01, 0x02, 0x03])
886 );
887 }
888
889 #[test]
890 fn test_all_raw_retained_attribute_codes_parse_and_round_trip() {
891 let raw_codes = [0, 22, 24, 27, 33, 128];
892
893 for code in raw_codes {
894 let wire = vec![0xc0, code, 0x02, 0xaa, 0xbb];
895 let attributes = parse_attributes(
896 Bytes::from(wire.clone()),
897 &AsnLength::Bits16,
898 false,
899 None,
900 None,
901 None,
902 )
903 .unwrap();
904 assert_eq!(attributes.inner.len(), 1, "code {code}");
905 match &attributes.inner[0].value {
906 AttributeValue::Raw(raw) => {
907 assert_eq!(raw.code, code);
908 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
909 }
910 value => panic!("expected Raw for code {code}, got {value:?}"),
911 }
912 assert!(attributes.has_attr(AttrType::from(code)), "code {code}");
913 assert_eq!(
914 attributes.encode(AsnLength::Bits16).unwrap(),
915 Bytes::from(wire)
916 );
917 }
918 }
919
920 #[test]
921 fn test_unassigned_attribute_code_retained_as_unknown() {
922 let wire = vec![0xc0, 0x7f, 0x02, 0xaa, 0xbb];
923 let attributes = parse_attributes(
924 Bytes::from(wire.clone()),
925 &AsnLength::Bits16,
926 false,
927 None,
928 None,
929 None,
930 )
931 .unwrap();
932
933 assert_eq!(attributes.inner.len(), 1);
934 match &attributes.inner[0].value {
935 AttributeValue::Unknown(raw) => {
936 assert_eq!(raw.code, 0x7f);
937 assert_eq!(raw.attr_type(), AttrType::Unknown(0x7f));
938 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
939 }
940 value => panic!("expected Unknown, got {value:?}"),
941 }
942 assert!(attributes.has_attr(AttrType::Unknown(0x7f)));
943 assert_eq!(
944 attributes.encode(AsnLength::Bits16).unwrap(),
945 Bytes::from(wire)
946 );
947 }
948
949 #[test]
950 fn test_structured_tlv_attributes_parse_and_round_trip() {
951 let cases = [
952 (
953 vec![0xc0, 0x26, 0x05, 0x01, 0x01, 0x02, 0x03, 0x04],
954 "BFD Discriminator",
955 ),
956 (
957 vec![0xc0, 0x28, 0x05, 0x7f, 0x00, 0x02, 0xaa, 0xbb],
958 "BGP Prefix-SID",
959 ),
960 (
961 vec![0xc0, 0x29, 0x06, 0x12, 0x34, 0x00, 0x02, 0xde, 0xad],
962 "BIER",
963 ),
964 (vec![0xc0, 0x25, 0x05, 0x7f, 0x00, 0x02, 0xde, 0xad], "SFP"),
965 ];
966
967 for (wire, name) in cases {
968 let data = Bytes::from(wire.clone());
969 let attributes =
970 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
971 assert_eq!(attributes.inner.len(), 1, "{name}");
972 match (name, &attributes.inner[0].value) {
973 ("BFD Discriminator", AttributeValue::BfdDiscriminator(_))
974 | ("BGP Prefix-SID", AttributeValue::BgpPrefixSid(_))
975 | ("BIER", AttributeValue::Bier(_))
976 | ("SFP", AttributeValue::Sfp(_)) => {}
977 (_, value) => panic!("unexpected value for {name}: {value:?}"),
978 }
979 assert_eq!(
980 attributes.encode(AsnLength::Bits16).unwrap(),
981 Bytes::from(wire),
982 "{name}"
983 );
984 }
985 }
986
987 #[test]
988 fn test_rfc7606_attribute_length_error() {
989 let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
991 let asn_len = AsnLength::Bits16;
992 let add_path = false;
993 let afi = None;
994 let safi = None;
995 let prefixes = None;
996
997 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
998
999 assert!(attributes.has_validation_warnings());
1001 let warnings = attributes.validation_warnings();
1002
1003 let has_length_warning = warnings
1004 .iter()
1005 .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
1006 assert!(has_length_warning);
1007 }
1008
1009 #[test]
1010 fn test_rfc7606_no_session_reset() {
1011 let data = Bytes::from(vec![
1013 0x80, 0x01, 0x02, 0x00, 0x01, 0x40, 0x01, 0x01, 0x00, 0x40, 0xFF, 0x01, 0x00, ]);
1017 let asn_len = AsnLength::Bits16;
1018 let add_path = false;
1019 let afi = None;
1020 let safi = None;
1021 let prefixes = None;
1022
1023 let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
1025 assert!(result.is_ok());
1026
1027 let attributes = result.unwrap();
1028 assert!(attributes.has_validation_warnings());
1029
1030 let warnings = attributes.validation_warnings();
1032 assert!(!warnings.is_empty());
1033 }
1034
1035 #[test]
1036 fn test_encode_as_path_attribute_type_selection() {
1037 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1041 400644,
1042 ]))]);
1043 let buf = attrs.encode(AsnLength::Bits32).unwrap();
1044 assert_eq!(buf[1], 2, "attribute type must be AS_PATH (2)");
1045 assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1047
1048 let attrs = Attributes::from_iter(vec![AttributeValue::As4Path(AsPath::from_sequence([
1051 400644,
1052 ]))]);
1053 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1054 assert_eq!(buf[1], 17, "attribute type must be AS4_PATH (17)");
1055 assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1056 }
1057
1058 #[test]
1059 fn test_encode_as_path_on_2octet_session() {
1060 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1061 64496, 64497,
1062 ]))]);
1063 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1064 assert_eq!(buf[1], 2);
1065 assert_eq!(&buf[3..], &[0x02, 0x02, 0xFB, 0xF0, 0xFB, 0xF1]);
1067 }
1068
1069 #[test]
1070 fn test_encode_as_path_rejects_4octet_asn_on_2octet_session() {
1071 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1074 400644,
1075 ]))]);
1076 let err = attrs.encode(AsnLength::Bits16).unwrap_err();
1077 assert_eq!(
1078 err,
1079 EncodingError::ValueTooLarge {
1080 field: "2-octet AS number in AS_PATH segment",
1081 actual: 400644,
1082 max: 65535,
1083 }
1084 );
1085 }
1086
1087 #[test]
1088 fn test_encode_aggregator_attribute_type_selection() {
1089 let attrs = Attributes::from_iter(vec![AttributeValue::As4Aggregator {
1090 asn: Asn::new_32bit(400644),
1091 id: std::net::Ipv4Addr::new(192, 0, 2, 1),
1092 }]);
1093 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1094 assert_eq!(buf[1], 18, "attribute type must be AS4_AGGREGATOR (18)");
1095 assert_eq!(
1096 buf.len(),
1097 3 + 8,
1098 "value must be a 4-octet ASN plus an IPv4 id"
1099 );
1100 }
1101
1102 #[test]
1103 fn test_encode_aggregator_width_follows_session_not_asn_flag() {
1104 let attrs = Attributes::from_iter(vec![AttributeValue::Aggregator {
1108 asn: Asn::new_32bit(258),
1109 id: std::net::Ipv4Addr::new(10, 0, 0, 1),
1110 }]);
1111 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1112 assert_eq!(buf[1], 7, "attribute type must be AGGREGATOR (7)");
1113 assert_eq!(buf.len(), 3 + 6);
1114 assert_eq!(&buf[3..], &[0x01, 0x02, 10, 0, 0, 1]);
1115 }
1116
1117 #[test]
1118 fn test_parse_attributes_maps_as4_wire_types() {
1119 let data = Bytes::from(vec![0xC0, 17, 6, 0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1122 let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1123 .expect("AS4_PATH must parse");
1124 match attributes.get_attr(AttrType::AS4_PATH).unwrap().value {
1125 AttributeValue::As4Path(path) => assert_eq!(
1126 path.to_u32_vec_opt(false).unwrap(),
1127 vec![400644],
1128 "AS4_PATH segments must parse as 4-octet regardless of session length"
1129 ),
1130 other => panic!("expected As4Path variant, got {other:?}"),
1131 }
1132
1133 let data = Bytes::from(vec![0xC0, 18, 8, 0x00, 0x06, 0x1D, 0x04, 192, 0, 2, 1]);
1135 let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1136 .expect("AS4_AGGREGATOR must parse");
1137 match attributes.get_attr(AttrType::AS4_AGGREGATOR).unwrap().value {
1138 AttributeValue::As4Aggregator { asn, .. } => {
1139 assert_eq!(asn, Asn::new_32bit(400644))
1140 }
1141 other => panic!("expected As4Aggregator variant, got {other:?}"),
1142 }
1143 }
1144}