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_36_domain_path;
19mod attr_37_sfp;
20mod attr_38_bfd_discriminator;
21mod attr_40_bgp_prefix_sid;
22mod attr_41_bier;
23
24use bytes::{Buf, BufMut, Bytes, BytesMut};
25use log::{debug, warn};
26use std::net::IpAddr;
27
28use crate::models::*;
29
30use crate::encoder::sink::{with_u16_len, with_u8_len};
31use crate::error::{BgpValidationWarning, EncodingError, ParserError};
32use crate::parser::bgp::attributes::attr_01_origin::{encode_origin, parse_origin};
33use crate::parser::bgp::attributes::attr_02_17_as_path::encode_as_path;
34pub(crate) use crate::parser::bgp::attributes::attr_02_17_as_path::parse_as_path;
35use crate::parser::bgp::attributes::attr_03_next_hop::{encode_next_hop, parse_next_hop};
36use crate::parser::bgp::attributes::attr_04_med::{encode_med, parse_med};
37use crate::parser::bgp::attributes::attr_05_local_pref::{encode_local_pref, parse_local_pref};
38use crate::parser::bgp::attributes::attr_07_18_aggregator::{encode_aggregator, parse_aggregator};
39use crate::parser::bgp::attributes::attr_08_communities::{
40 encode_regular_communities, parse_regular_communities,
41};
42use crate::parser::bgp::attributes::attr_09_originator::{
43 encode_originator_id, parse_originator_id,
44};
45use crate::parser::bgp::attributes::attr_10_13_cluster::{encode_clusters, parse_clusters};
46use crate::parser::bgp::attributes::attr_14_15_nlri::encode_nlri;
47pub(crate) use crate::parser::bgp::attributes::attr_14_15_nlri::parse_nlri;
48use crate::parser::bgp::attributes::attr_16_25_extended_communities::{
49 encode_extended_communities, encode_ipv6_extended_communities, parse_extended_community,
50 parse_ipv6_extended_community,
51};
52use crate::parser::bgp::attributes::attr_23_tunnel_encap::{
53 encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute,
54};
55use crate::parser::bgp::attributes::attr_24_traffic_engineering::{
56 encode_traffic_engineering, parse_traffic_engineering,
57};
58use crate::parser::bgp::attributes::attr_26_aigp::{encode_aigp, parse_aigp};
59use crate::parser::bgp::attributes::attr_29_linkstate::{
60 encode_link_state_attribute, parse_link_state_attribute,
61};
62use crate::parser::bgp::attributes::attr_32_large_communities::{
63 encode_large_communities, parse_large_communities,
64};
65use crate::parser::bgp::attributes::attr_35_otc::{
66 encode_only_to_customer, parse_only_to_customer,
67};
68use crate::parser::bgp::attributes::attr_36_domain_path::{encode_domain_path, parse_domain_path};
69use crate::parser::bgp::attributes::attr_37_sfp::{encode_sfp, parse_sfp};
70use crate::parser::bgp::attributes::attr_38_bfd_discriminator::{
71 encode_bfd_discriminator, parse_bfd_discriminator,
72};
73use crate::parser::bgp::attributes::attr_40_bgp_prefix_sid::{
74 encode_bgp_prefix_sid, parse_bgp_prefix_sid,
75};
76use crate::parser::bgp::attributes::attr_41_bier::{encode_bier, parse_bier};
77use crate::parser::ReadUtils;
78
79fn validate_attribute_flags(
81 attr_type: AttrType,
82 flags: AttrFlags,
83 warnings: &mut Vec<BgpValidationWarning>,
84) {
85 let expected_flags = match attr_type {
86 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP => AttrFlags::TRANSITIVE,
88 AttrType::ATOMIC_AGGREGATE => AttrFlags::TRANSITIVE,
90 AttrType::MULTI_EXIT_DISCRIMINATOR
92 | AttrType::ORIGINATOR_ID
93 | AttrType::CLUSTER_LIST
94 | AttrType::MP_REACHABLE_NLRI
95 | AttrType::MP_UNREACHABLE_NLRI
96 | AttrType::TRAFFIC_ENGINEERING => AttrFlags::OPTIONAL,
97 AttrType::AGGREGATOR
99 | AttrType::AS4_AGGREGATOR
100 | AttrType::AS4_PATH
101 | AttrType::COMMUNITIES
102 | AttrType::EXTENDED_COMMUNITIES
103 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
104 | AttrType::LARGE_COMMUNITIES
105 | AttrType::ONLY_TO_CUSTOMER
106 | AttrType::BGP_DOMAIN_PATH => AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE,
107 AttrType::LOCAL_PREFERENCE => AttrFlags::TRANSITIVE,
109 _ => return, };
112
113 let relevant_flags = flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE);
115 if relevant_flags != expected_flags {
116 warnings.push(BgpValidationWarning::AttributeFlagsError {
117 attr_type,
118 expected_flags: expected_flags.bits(),
119 actual_flags: relevant_flags.bits(),
120 });
121 }
122
123 if flags.contains(AttrFlags::PARTIAL) {
125 match attr_type {
126 AttrType::ORIGIN
128 | AttrType::AS_PATH
129 | AttrType::NEXT_HOP
130 | AttrType::LOCAL_PREFERENCE
131 | AttrType::ATOMIC_AGGREGATE
132 | AttrType::MULTI_EXIT_DISCRIMINATOR
133 | AttrType::ORIGINATOR_ID
134 | AttrType::CLUSTER_LIST
135 | AttrType::MP_REACHABLE_NLRI
136 | AttrType::MP_UNREACHABLE_NLRI
137 | AttrType::TRAFFIC_ENGINEERING => {
138 warnings.push(BgpValidationWarning::AttributeFlagsError {
139 attr_type,
140 expected_flags: expected_flags.bits(),
141 actual_flags: flags.bits(),
142 });
143 }
144 _ => {} }
146 }
147}
148
149fn is_raw_retained_attr(attr_type: AttrType) -> bool {
150 matches!(
151 attr_type,
152 AttrType::RESERVED
153 | AttrType::PMSI_TUNNEL
154 | AttrType::PE_DISTINGUISHER_LABELS
155 | AttrType::BGPSEC_PATH
156 | AttrType::ATTR_SET
157 )
158}
159
160fn is_well_known_mandatory(attr_type: AttrType) -> bool {
162 matches!(
163 attr_type,
164 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP | AttrType::LOCAL_PREFERENCE
165 )
166}
167
168pub(crate) struct AttributeValidationState {
169 warnings: Vec<BgpValidationWarning>,
170 attr_mask: [u64; 4],
171}
172
173impl AttributeValidationState {
174 pub(crate) fn new() -> Self {
175 Self {
176 warnings: Vec::new(),
177 attr_mask: [0; 4],
178 }
179 }
180
181 fn has_raw_attr(&self, attr: u8) -> bool {
182 (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
183 }
184
185 pub(crate) fn has_attr(&self, attr_type: AttrType) -> bool {
186 self.has_raw_attr(u8::from(attr_type))
187 }
188
189 fn set_attr(&mut self, attr: u8) {
190 self.attr_mask[(attr / 64) as usize] |= 1u64 << (attr % 64);
191 }
192
193 pub(crate) fn observe_header(
194 &mut self,
195 raw_attr_type: u8,
196 attr_type: AttrType,
197 flags: AttrFlags,
198 length: usize,
199 ) -> bool {
200 if self.has_raw_attr(raw_attr_type) {
201 self.warnings
202 .push(BgpValidationWarning::DuplicateAttribute { attr_type });
203 }
204 self.set_attr(raw_attr_type);
205
206 validate_attribute_flags(attr_type, flags, &mut self.warnings);
207 validate_attribute_length(attr_type, length, &mut self.warnings);
208
209 flags.contains(AttrFlags::PARTIAL)
210 }
211
212 pub(crate) fn observe_parse_error(
213 &mut self,
214 attr_type: AttrType,
215 partial: bool,
216 error: &ParserError,
217 ) {
218 if partial {
219 self.warnings
220 .push(BgpValidationWarning::PartialAttributeError {
221 attr_type,
222 reason: error.to_string(),
223 });
224 debug!("PARTIAL attribute error: {}", error);
225 } else if is_well_known_mandatory(attr_type) {
226 self.warnings
227 .push(BgpValidationWarning::MalformedAttributeList {
228 reason: format!(
229 "Well-known mandatory attribute {} parsing failed: {}",
230 u8::from(attr_type),
231 error
232 ),
233 });
234 debug!(
235 "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
236 error
237 );
238 } else {
239 self.warnings
240 .push(BgpValidationWarning::OptionalAttributeError {
241 attr_type,
242 reason: error.to_string(),
243 });
244 debug!("Optional attribute error, discarding: {}", error);
245 }
246 }
247
248 pub(crate) fn check_mandatory_attributes(
249 &mut self,
250 is_announcement: bool,
251 has_standard_nlri: bool,
252 ) {
253 if !is_announcement {
254 return;
255 }
256
257 if !self.has_attr(AttrType::ORIGIN) {
258 self.warnings
259 .push(BgpValidationWarning::MissingWellKnownAttribute {
260 attr_type: AttrType::ORIGIN,
261 });
262 }
263
264 if !self.has_attr(AttrType::AS_PATH) {
265 self.warnings
266 .push(BgpValidationWarning::MissingWellKnownAttribute {
267 attr_type: AttrType::AS_PATH,
268 });
269 }
270
271 let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
272 if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
273 self.warnings
274 .push(BgpValidationWarning::MissingWellKnownAttribute {
275 attr_type: AttrType::NEXT_HOP,
276 });
277 }
278 }
279
280 pub(crate) fn finish(self) -> (Vec<BgpValidationWarning>, [u64; 4]) {
281 (self.warnings, self.attr_mask)
282 }
283}
284
285fn validate_attribute_length(
287 attr_type: AttrType,
288 length: usize,
289 warnings: &mut Vec<BgpValidationWarning>,
290) {
291 let expected_length = match attr_type {
292 AttrType::ORIGIN => Some(1),
293 AttrType::NEXT_HOP => Some(4), AttrType::MULTI_EXIT_DISCRIMINATOR => Some(4),
295 AttrType::LOCAL_PREFERENCE => Some(4),
296 AttrType::ATOMIC_AGGREGATE => Some(0),
297 AttrType::ORIGINATOR_ID => Some(4),
298 AttrType::ONLY_TO_CUSTOMER => Some(4),
299 AttrType::AS_PATH
301 | AttrType::AS4_PATH
302 | AttrType::AGGREGATOR
303 | AttrType::AS4_AGGREGATOR
304 | AttrType::COMMUNITIES
305 | AttrType::EXTENDED_COMMUNITIES
306 | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
307 | AttrType::LARGE_COMMUNITIES
308 | AttrType::CLUSTER_LIST
309 | AttrType::MP_REACHABLE_NLRI
310 | AttrType::MP_UNREACHABLE_NLRI => None,
311 _ => None, };
313
314 if let Some(expected) = expected_length {
315 if length != expected {
316 warnings.push(BgpValidationWarning::AttributeLengthError {
317 attr_type,
318 expected_length: Some(expected),
319 actual_length: length,
320 });
321 }
322 }
323}
324
325fn is_domain_path_family(afi: u16, safi: u8) -> bool {
328 matches!((afi, safi), (1 | 2, 128) | (25, 70))
329}
330
331fn mp_reach_family(bytes: &[u8]) -> Option<(u16, u8)> {
334 match bytes.len() >= 3 {
335 true => Some((u16::from_be_bytes([bytes[0], bytes[1]]), bytes[2])),
336 false => None,
337 }
338}
339
340#[derive(Debug, Clone, Copy, Default)]
342struct DomainPathFamilies {
343 declared: Option<(u16, u8)>,
345 announced: Option<(u16, u8)>,
347 classic_ipv4: bool,
349}
350
351impl DomainPathFamilies {
352 fn from_attributes(attributes: &[Attribute], declared: Option<(u16, u8)>) -> Self {
357 let announced = attributes
358 .iter()
359 .find_map(|attribute| match &attribute.value {
360 AttributeValue::MpReachNlri(nlri) => Some((nlri.afi as u16, nlri.safi as u8)),
361 _ => None,
362 })
363 .or_else(|| {
364 attributes
365 .iter()
366 .find_map(|attribute| match &attribute.value {
367 AttributeValue::Raw(raw) | AttributeValue::Unknown(raw)
368 if raw.code == u8::from(AttrType::MP_REACHABLE_NLRI) =>
369 {
370 mp_reach_family(&raw.bytes)
371 }
372 _ => None,
373 })
374 });
375
376 DomainPathFamilies {
377 declared,
378 announced,
379 classic_ipv4: false,
380 }
381 }
382
383 fn disallowed(&self) -> Option<String> {
390 let declared = self
391 .declared
392 .filter(|(afi, safi)| !is_domain_path_family(*afi, *safi));
393 if let Some((afi, safi)) = declared {
394 return Some(describe_family(afi, safi, "declared"));
395 }
396
397 if let Some((afi, safi)) = self
398 .announced
399 .filter(|(afi, safi)| !is_domain_path_family(*afi, *safi))
400 {
401 return Some(describe_family(afi, safi, "announced"));
402 }
403
404 if self.classic_ipv4 {
405 return Some("classic IPv4 unicast NLRI (AFI 1, SAFI 1)".to_string());
406 }
407
408 None
409 }
410}
411
412fn describe_family(afi: u16, safi: u8, source: &str) -> String {
413 match (afi, safi) {
414 (_, 70) if afi != 25 => {
416 format!("{source} AFI {afi} routes with SAFI 70 (EVPN requires AFI 25)")
417 }
418 _ => format!("{source} AFI {afi} routes with SAFI {safi}"),
419 }
420}
421
422fn domain_path_family_warning(families: DomainPathFamilies) -> Option<BgpValidationWarning> {
426 families.disallowed().map(|family| {
427 BgpValidationWarning::OptionalAttributeError {
428 attr_type: AttrType::BGP_DOMAIN_PATH,
429 reason: format!(
430 "D-PATH is only valid on IPVPN (AFI 1/2, SAFI 128) or EVPN (AFI 25, SAFI 70) routes, this UPDATE carries {family}: RFC 10039 §4 requires treat-as-withdraw"
431 ),
432 }
433 })
434}
435
436pub fn parse_attributes(
441 mut data: Bytes,
442 asn_len: &AsnLength,
443 add_path: bool,
444 afi: Option<Afi>,
445 safi: Option<Safi>,
446 prefixes: Option<&[NetworkPrefix]>,
447) -> Result<Attributes, ParserError> {
448 let estimated_attrs = (data.remaining() / 3).min(256);
451 let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
452 let mut validation = AttributeValidationState::new();
453 let section = data.clone();
456
457 while data.remaining() >= 3 {
458 let flag = AttrFlags::from_bits_retain(data.read_u8()?);
463 let attr_type = data.read_u8()?;
464 let attr_length = match flag.contains(AttrFlags::EXTENDED) {
465 false => data.read_u8()? as usize,
466 true => data.read_u16()? as usize,
467 };
468
469 debug!(
470 "reading attribute: type -- {:?}, length -- {}",
471 attr_type, attr_length
472 );
473
474 let attr_type = AttrType::from(attr_type);
475
476 let partial = validation.observe_header(attr_type.into(), attr_type, flag, attr_length);
477
478 let bytes_left = data.remaining();
479
480 if data.remaining() < attr_length {
481 warn!(
482 "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
483 attr_type, attr_length, bytes_left
484 );
485 break;
487 }
488
489 data.has_n_remaining(attr_length)?;
491 let value_start = section.len() - data.len();
492 let mut attr_data = data.split_to(attr_length);
493 let raw_code = u8::from(attr_type);
494
495 if let Some(t) = get_deprecated_attr_type(raw_code) {
496 debug!("deprecated attribute type: {} - {}", raw_code, t);
497 attributes.push(Attribute {
498 value: AttributeValue::Deprecated(AttrRaw {
499 code: raw_code,
500 bytes: attr_data,
501 }),
502 flag,
503 });
504 continue;
505 }
506
507 if matches!(attr_type, AttrType::Unknown(_)) {
508 debug!("unknown attribute type: {}", raw_code);
509 attributes.push(Attribute {
510 value: AttributeValue::Unknown(AttrRaw {
511 code: raw_code,
512 bytes: attr_data,
513 }),
514 flag,
515 });
516 continue;
517 }
518
519 if is_raw_retained_attr(attr_type) {
520 debug!("raw-retained attribute type: {}", raw_code);
521 attributes.push(Attribute {
522 value: AttributeValue::Raw(AttrRaw {
523 code: raw_code,
524 bytes: attr_data,
525 }),
526 flag,
527 });
528 continue;
529 }
530
531 let attr = match attr_type {
532 AttrType::ORIGIN => parse_origin(attr_data),
533 AttrType::AS_PATH => parse_as_path(attr_data, asn_len).map(AttributeValue::AsPath),
534 AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
535 AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
536 AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
537 AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
538 AttrType::AGGREGATOR => parse_aggregator(attr_data, asn_len)
539 .map(|(asn, id)| AttributeValue::Aggregator { asn, id }),
540 AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
541 AttrType::CLUSTER_LIST => parse_clusters(attr_data),
542 AttrType::MP_REACHABLE_NLRI => {
543 parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
544 }
545 AttrType::MP_UNREACHABLE_NLRI => {
546 parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
547 }
548 AttrType::AS4_PATH => {
549 parse_as_path(attr_data, &AsnLength::Bits32).map(AttributeValue::As4Path)
550 }
551 AttrType::AS4_AGGREGATOR => parse_aggregator(attr_data, &AsnLength::Bits32)
552 .map(|(asn, id)| AttributeValue::As4Aggregator { asn, id }),
553
554 AttrType::COMMUNITIES => parse_regular_communities(attr_data),
556 AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
557 AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
558 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
559 parse_ipv6_extended_community(attr_data)
560 }
561 AttrType::DEVELOPMENT => {
562 let mut value = vec![];
563 for _i in 0..attr_length {
564 value.push(attr_data.read_u8()?);
565 }
566 Ok(AttributeValue::Development(value))
567 }
568 AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
569 AttrType::AIGP => parse_aigp(attr_data),
570 AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
571 AttrType::TRAFFIC_ENGINEERING => parse_traffic_engineering(attr_data),
572 AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
573 AttrType::BGP_DOMAIN_PATH => parse_domain_path(attr_data),
574 AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data),
575 AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data),
576 AttrType::BGP_PREFIX_SID => parse_bgp_prefix_sid(attr_data),
577 AttrType::BIER => parse_bier(attr_data),
578 _ => Err(ParserError::Unsupported(format!(
579 "unsupported attribute type: {attr_type:?}"
580 ))),
581 };
582
583 match attr {
584 Ok(value) => {
585 assert_eq!(attr_type, value.attr_type());
586 attributes.push(Attribute { value, flag });
587 }
588 Err(e) => {
589 validation.observe_parse_error(attr_type, partial, &e);
590 attributes.push(Attribute {
591 value: AttributeValue::Raw(AttrRaw {
592 code: raw_code,
593 bytes: section.slice(value_start..value_start + attr_length),
594 }),
595 flag,
596 });
597 continue;
598 }
599 };
600 }
601
602 let (mut validation_warnings, attr_mask) = validation.finish();
603 if attributes
604 .iter()
605 .any(|attribute| matches!(attribute.value, AttributeValue::DomainPath(_)))
606 {
607 let declared = afi.zip(safi).map(|(afi, safi)| (afi as u16, safi as u8));
608 let families = DomainPathFamilies::from_attributes(&attributes, declared);
609 if let Some(warning) = domain_path_family_warning(families) {
610 validation_warnings.push(warning);
611 }
612 }
613
614 Ok(Attributes {
615 inner: attributes,
616 validation_warnings,
617 attr_mask,
618 })
619}
620
621impl Attribute {
622 pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
628 buf.put_u8(self.flag.bits());
629 buf.put_u8(self.value.attr_code());
630
631 let write_value = |b: &mut BytesMut| -> Result<(), EncodingError> {
632 match &self.value {
633 AttributeValue::Origin(v) => b.put_slice(&encode_origin(v)),
634 AttributeValue::AsPath(path) => {
636 encode_as_path(path, asn_len, b)?;
637 }
638 AttributeValue::As4Path(path) => {
641 encode_as_path(path, AsnLength::Bits32, b)?;
642 }
643 AttributeValue::NextHop(v) => b.put_slice(&encode_next_hop(v)),
644 AttributeValue::MultiExitDiscriminator(v) => b.put_slice(&encode_med(*v)),
645 AttributeValue::LocalPreference(v) => b.put_slice(&encode_local_pref(*v)),
646 AttributeValue::OnlyToCustomer(v) => {
647 b.put_slice(&encode_only_to_customer(v.into()))
648 }
649 AttributeValue::AtomicAggregate => {}
650 AttributeValue::Aggregator { asn, id } => {
651 b.put_slice(&encode_aggregator(asn, &IpAddr::from(*id), asn_len)?)
652 }
653 AttributeValue::As4Aggregator { asn, id } => b.put_slice(&encode_aggregator(
654 asn,
655 &IpAddr::from(*id),
656 AsnLength::Bits32,
657 )?),
658 AttributeValue::Communities(v) => b.put_slice(&encode_regular_communities(v)),
659 AttributeValue::ExtendedCommunities(v) => {
660 b.put_slice(&encode_extended_communities(v))
661 }
662 AttributeValue::LargeCommunities(v) => b.put_slice(&encode_large_communities(v)),
663 AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
664 b.put_slice(&encode_ipv6_extended_communities(v))
665 }
666 AttributeValue::OriginatorId(v) => {
667 b.put_slice(&encode_originator_id(&IpAddr::from(*v)))
668 }
669 AttributeValue::Clusters(v) => b.put_slice(&encode_clusters(v)),
670 AttributeValue::MpReachNlri(v) => {
671 let add_path = v
673 .labeled_prefixes
674 .as_ref()
675 .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
676 encode_nlri(v, true, add_path, b)?;
677 }
678 AttributeValue::MpUnreachNlri(v) => {
679 encode_nlri(v, false, false, b)?;
681 }
682 AttributeValue::LinkState(v) => encode_link_state_attribute(v, b)?,
683 AttributeValue::TunnelEncapsulation(v) => {
684 encode_tunnel_encapsulation_attribute(v, b)?
685 }
686 AttributeValue::TrafficEngineering(v) => encode_traffic_engineering(v, b)?,
687 AttributeValue::DomainPath(v) => encode_domain_path(v, b)?,
688 AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v, b)?,
689 AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v, b)?,
690 AttributeValue::Bier(v) => encode_bier(v, b)?,
691 AttributeValue::Sfp(v) => encode_sfp(v, b)?,
692 AttributeValue::Development(v) => b.put_slice(v),
693 AttributeValue::Raw(v) => b.put_slice(&v.bytes),
694 AttributeValue::Deprecated(v) => b.put_slice(&v.bytes),
695 AttributeValue::Unknown(v) => b.put_slice(&v.bytes),
696 AttributeValue::Aigp(v) => b.put_slice(&encode_aigp(v)),
697 AttributeValue::AttrSet(_v) => {
698 return Err(EncodingError::unencodable(
699 "ATTR_SET attribute",
700 "encoding not implemented",
701 ));
702 }
703 }
704 Ok(())
705 };
706
707 match self.is_extended() {
708 false => with_u8_len(buf, "BGP attribute value length", write_value),
709 true => with_u16_len(buf, "BGP attribute value length (extended)", write_value),
710 }
711 }
712
713 pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
715 let mut buf = BytesMut::new();
716 self.encode_to(asn_len, &mut buf)?;
717 Ok(buf.freeze())
718 }
719}
720
721impl Attributes {
722 pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
724 for attr in &self.inner {
725 attr.encode_to(asn_len, buf)?;
726 }
727 Ok(())
728 }
729
730 pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
732 let mut buf = BytesMut::new();
733 self.encode_to(asn_len, &mut buf)?;
734 Ok(buf.freeze())
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
743 fn test_unknwon_attribute_type() {
744 let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
745 let asn_len = AsnLength::Bits16;
746 let add_path = false;
747 let afi = None;
748 let safi = None;
749 let prefixes = None;
750 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
751 assert!(attributes.is_ok());
752 let attributes = attributes.unwrap();
753 assert_eq!(attributes.inner.len(), 1);
754 assert_eq!(
755 attributes.inner[0].value.attr_type(),
756 AttrType::Unknown(254)
757 );
758 }
759
760 #[test]
761 fn test_rfc7606_attribute_flags_error() {
762 let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); let asn_len = AsnLength::Bits16;
765 let add_path = false;
766 let afi = None;
767 let safi = None;
768 let prefixes = None;
769
770 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
771
772 assert!(attributes.has_validation_warnings());
774 let warnings = attributes.validation_warnings();
775 assert!(!warnings.is_empty());
777
778 match &warnings[0] {
779 BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
780 assert_eq!(*attr_type, AttrType::ORIGIN);
781 }
782 _ => panic!("Expected AttributeFlagsError warning"),
783 }
784 }
785
786 #[test]
787 fn test_rfc7606_missing_mandatory_attribute() {
788 let data = Bytes::from(vec![
790 0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, ]);
792 let asn_len = AsnLength::Bits16;
793 let add_path = false;
794 let afi = None;
795 let safi = None;
796 let prefixes = None;
797
798 let mut attributes =
799 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
800 attributes.check_mandatory_attributes(true, true);
802
803 assert!(attributes.has_validation_warnings());
805 let warnings = attributes.validation_warnings();
806 assert_eq!(warnings.len(), 3); for warning in warnings {
810 match warning {
811 BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
812 assert!(matches!(
813 attr_type,
814 AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
815 ));
816 }
817 _ => panic!("Expected MissingWellKnownAttribute warning"),
818 }
819 }
820 }
821
822 #[test]
823 fn test_mp_reach_no_next_hop() {
824 let data = Bytes::from(vec![
828 0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, ]);
833 let asn_len = AsnLength::Bits16;
834 let add_path = false;
835 let afi = None;
836 let safi = None;
837 let prefixes = None;
838
839 let mut attributes =
840 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
841 attributes.check_mandatory_attributes(true, false);
843
844 let warnings = attributes.validation_warnings();
846 let has_next_hop_warning = warnings.iter().any(|w| {
847 matches!(
848 w,
849 BgpValidationWarning::MissingWellKnownAttribute {
850 attr_type: AttrType::NEXT_HOP
851 }
852 )
853 });
854 assert!(!has_next_hop_warning);
855 }
856
857 #[test]
858 fn test_pure_withdrawal_no_warnings() {
859 let data = Bytes::from(vec![]);
861 let asn_len = AsnLength::Bits16;
862 let add_path = false;
863 let afi = None;
864 let safi = None;
865 let prefixes = None;
866
867 let mut attributes =
868 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
869 attributes.check_mandatory_attributes(false, false);
871
872 assert!(!attributes.has_validation_warnings());
874
875 let data = Bytes::from(vec![
877 0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, ]);
879 let mut attributes =
880 parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
881 attributes.check_mandatory_attributes(false, false);
882 assert!(!attributes.has_validation_warnings());
883 }
884
885 #[test]
886 fn test_rfc7606_duplicate_attribute() {
887 let data = Bytes::from(vec![
889 0x40, 0x01, 0x01, 0x00, 0x40, 0x01, 0x01, 0x01, ]);
892 let asn_len = AsnLength::Bits16;
893 let add_path = false;
894 let afi = None;
895 let safi = None;
896 let prefixes = None;
897
898 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
899
900 assert!(attributes.has_validation_warnings());
902 let warnings = attributes.validation_warnings();
903
904 let has_duplicate_warning = warnings
906 .iter()
907 .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
908 assert!(has_duplicate_warning);
909 }
910
911 #[test]
912 fn test_attribute_type_boundaries() {
913 let asn_len = AsnLength::Bits16;
914 let add_path = false;
915 let afi = None;
916 let safi = None;
917 let prefixes = None;
918
919 const REQUIRED_ATTRS: &[u8] = &[
921 0x40, 0x01, 0x01, 0x00, 0x40, 0x02, 0x00, 0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, ];
925
926 let mut data = REQUIRED_ATTRS.to_vec();
928 data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); let data = Bytes::from(data);
930
931 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
932
933 assert!(attributes.has_attr(AttrType::DEVELOPMENT));
934 assert!(!attributes.has_validation_warnings());
935
936 let mut data = REQUIRED_ATTRS.to_vec();
938 data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); let data = Bytes::from(data);
940
941 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
942
943 assert!(attributes.inner.iter().any(|attr| {
944 matches!(
945 &attr.value,
946 AttributeValue::Raw(raw)
947 if raw.code == u8::from(AttrType::RESERVED)
948 && raw.bytes == Bytes::from_static(&[0x01])
949 )
950 }));
951 assert!(!attributes.validation_warnings.iter().any(|vw| {
952 matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
953 }));
954 }
955
956 #[test]
957 fn test_raw_retention_for_known_unsupported_attribute() {
958 let data = Bytes::from(vec![0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc]); let attributes =
960 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
961
962 assert_eq!(attributes.inner.len(), 1);
963 match &attributes.inner[0].value {
964 AttributeValue::Raw(raw) => {
965 assert_eq!(raw.code, 22);
966 assert_eq!(raw.attr_type(), AttrType::PMSI_TUNNEL);
967 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb, 0xcc]));
968 }
969 value => panic!("expected Raw, got {value:?}"),
970 }
971 assert_eq!(
972 attributes.encode(AsnLength::Bits16).unwrap(),
973 Bytes::from_static(&[0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc])
974 );
975 }
976
977 #[test]
978 fn test_deprecated_code_13_retained_as_deprecated() {
979 let data = Bytes::from(vec![0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04]);
980 let attributes =
981 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
982
983 assert_eq!(attributes.inner.len(), 1);
984 match &attributes.inner[0].value {
985 AttributeValue::Deprecated(raw) => {
986 assert_eq!(raw.code, 13);
987 assert_eq!(raw.attr_type(), AttrType::Unknown(13));
988 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
989 }
990 value => panic!("expected Deprecated, got {value:?}"),
991 }
992 assert_eq!(
993 attributes.encode(AsnLength::Bits16).unwrap(),
994 Bytes::from_static(&[0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04])
995 );
996 }
997
998 #[test]
999 fn test_malformed_typed_attribute_falls_back_to_raw() {
1000 let data = Bytes::from(vec![0x40, 0x03, 0x03, 0x01, 0x02, 0x03]); let attributes =
1002 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
1003
1004 assert!(attributes.has_validation_warnings());
1005 match &attributes.inner[0].value {
1006 AttributeValue::Raw(raw) => {
1007 assert_eq!(raw.code, 3);
1008 assert_eq!(raw.attr_type(), AttrType::NEXT_HOP);
1009 assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03]));
1010 }
1011 value => panic!("expected Raw fallback, got {value:?}"),
1012 }
1013 assert_eq!(
1014 attributes.encode(AsnLength::Bits16).unwrap(),
1015 Bytes::from_static(&[0x40, 0x03, 0x03, 0x01, 0x02, 0x03])
1016 );
1017 }
1018
1019 #[test]
1020 fn test_malformed_attribute_after_others_keeps_its_own_bytes() {
1021 let data = Bytes::from(
1022 [
1023 &[0x40, 0x01, 0x01, 0x00][..],
1025 &[0x50, 0x03, 0x00, 0x03, 0x0a, 0x0b, 0x0c],
1027 &[0x80, 0x04, 0x04, 0x00, 0x00, 0x00, 0x07],
1029 ]
1030 .concat(),
1031 );
1032 let attributes =
1033 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
1034
1035 assert_eq!(attributes.inner.len(), 3);
1036 assert_eq!(
1037 attributes.inner[0].value,
1038 AttributeValue::Origin(Origin::IGP)
1039 );
1040 match &attributes.inner[1].value {
1041 AttributeValue::Raw(raw) => {
1042 assert_eq!(raw.code, 3);
1043 assert_eq!(raw.bytes, Bytes::from_static(&[0x0a, 0x0b, 0x0c]));
1044 }
1045 value => panic!("expected Raw fallback, got {value:?}"),
1046 }
1047 assert_eq!(
1048 attributes.inner[2].value,
1049 AttributeValue::MultiExitDiscriminator(7)
1050 );
1051 }
1052
1053 #[test]
1054 fn test_all_raw_retained_attribute_codes_parse_and_round_trip() {
1055 let raw_codes = [0, 22, 24, 27, 33, 128];
1056
1057 for code in raw_codes {
1058 let wire = vec![0xc0, code, 0x02, 0xaa, 0xbb];
1059 let attributes = parse_attributes(
1060 Bytes::from(wire.clone()),
1061 &AsnLength::Bits16,
1062 false,
1063 None,
1064 None,
1065 None,
1066 )
1067 .unwrap();
1068 assert_eq!(attributes.inner.len(), 1, "code {code}");
1069 match &attributes.inner[0].value {
1070 AttributeValue::Raw(raw) => {
1071 assert_eq!(raw.code, code);
1072 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
1073 }
1074 value => panic!("expected Raw for code {code}, got {value:?}"),
1075 }
1076 assert!(attributes.has_attr(AttrType::from(code)), "code {code}");
1077 assert_eq!(
1078 attributes.encode(AsnLength::Bits16).unwrap(),
1079 Bytes::from(wire)
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn test_unassigned_attribute_code_retained_as_unknown() {
1086 let wire = vec![0xc0, 0x7f, 0x02, 0xaa, 0xbb];
1087 let attributes = parse_attributes(
1088 Bytes::from(wire.clone()),
1089 &AsnLength::Bits16,
1090 false,
1091 None,
1092 None,
1093 None,
1094 )
1095 .unwrap();
1096
1097 assert_eq!(attributes.inner.len(), 1);
1098 match &attributes.inner[0].value {
1099 AttributeValue::Unknown(raw) => {
1100 assert_eq!(raw.code, 0x7f);
1101 assert_eq!(raw.attr_type(), AttrType::Unknown(0x7f));
1102 assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
1103 }
1104 value => panic!("expected Unknown, got {value:?}"),
1105 }
1106 assert!(attributes.has_attr(AttrType::Unknown(0x7f)));
1107 assert_eq!(
1108 attributes.encode(AsnLength::Bits16).unwrap(),
1109 Bytes::from(wire)
1110 );
1111 }
1112
1113 #[test]
1114 fn test_structured_tlv_attributes_parse_and_round_trip() {
1115 let cases = [
1116 (
1117 vec![0xc0, 0x26, 0x05, 0x01, 0x01, 0x02, 0x03, 0x04],
1118 "BFD Discriminator",
1119 ),
1120 (
1121 vec![0xc0, 0x28, 0x05, 0x7f, 0x00, 0x02, 0xaa, 0xbb],
1122 "BGP Prefix-SID",
1123 ),
1124 (
1125 vec![0xc0, 0x29, 0x06, 0x12, 0x34, 0x00, 0x02, 0xde, 0xad],
1126 "BIER",
1127 ),
1128 (vec![0xc0, 0x25, 0x05, 0x7f, 0x00, 0x02, 0xde, 0xad], "SFP"),
1129 (
1130 vec![
1131 0xc0, 0x24, 0x0f, 0x02, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x03, 0x46, ],
1136 "BGP Domain Path",
1137 ),
1138 ];
1139
1140 for (wire, name) in cases {
1141 let data = Bytes::from(wire.clone());
1142 let attributes =
1143 parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
1144 assert_eq!(attributes.inner.len(), 1, "{name}");
1145 match (name, &attributes.inner[0].value) {
1146 ("BFD Discriminator", AttributeValue::BfdDiscriminator(_))
1147 | ("BGP Prefix-SID", AttributeValue::BgpPrefixSid(_))
1148 | ("BIER", AttributeValue::Bier(_))
1149 | ("SFP", AttributeValue::Sfp(_))
1150 | ("BGP Domain Path", AttributeValue::DomainPath(_)) => {}
1151 (_, value) => panic!("unexpected value for {name}: {value:?}"),
1152 }
1153 assert_eq!(
1154 attributes.encode(AsnLength::Bits16).unwrap(),
1155 Bytes::from(wire),
1156 "{name}"
1157 );
1158 }
1159 }
1160
1161 #[test]
1162 fn test_rfc7606_attribute_length_error() {
1163 let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
1165 let asn_len = AsnLength::Bits16;
1166 let add_path = false;
1167 let afi = None;
1168 let safi = None;
1169 let prefixes = None;
1170
1171 let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
1172
1173 assert!(attributes.has_validation_warnings());
1175 let warnings = attributes.validation_warnings();
1176
1177 let has_length_warning = warnings
1178 .iter()
1179 .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
1180 assert!(has_length_warning);
1181 }
1182
1183 #[test]
1184 fn test_rfc7606_no_session_reset() {
1185 let data = Bytes::from(vec![
1187 0x80, 0x01, 0x02, 0x00, 0x01, 0x40, 0x01, 0x01, 0x00, 0x40, 0xFF, 0x01, 0x00, ]);
1191 let asn_len = AsnLength::Bits16;
1192 let add_path = false;
1193 let afi = None;
1194 let safi = None;
1195 let prefixes = None;
1196
1197 let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
1199 assert!(result.is_ok());
1200
1201 let attributes = result.unwrap();
1202 assert!(attributes.has_validation_warnings());
1203
1204 let warnings = attributes.validation_warnings();
1206 assert!(!warnings.is_empty());
1207 }
1208
1209 #[test]
1210 fn test_encode_as_path_attribute_type_selection() {
1211 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1215 400644,
1216 ]))]);
1217 let buf = attrs.encode(AsnLength::Bits32).unwrap();
1218 assert_eq!(buf[1], 2, "attribute type must be AS_PATH (2)");
1219 assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1221
1222 let attrs = Attributes::from_iter(vec![AttributeValue::As4Path(AsPath::from_sequence([
1225 400644,
1226 ]))]);
1227 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1228 assert_eq!(buf[1], 17, "attribute type must be AS4_PATH (17)");
1229 assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1230 }
1231
1232 #[test]
1233 fn test_encode_as_path_on_2octet_session() {
1234 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1235 64496, 64497,
1236 ]))]);
1237 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1238 assert_eq!(buf[1], 2);
1239 assert_eq!(&buf[3..], &[0x02, 0x02, 0xFB, 0xF0, 0xFB, 0xF1]);
1241 }
1242
1243 #[test]
1244 fn test_encode_as_path_rejects_4octet_asn_on_2octet_session() {
1245 let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1248 400644,
1249 ]))]);
1250 let err = attrs.encode(AsnLength::Bits16).unwrap_err();
1251 assert_eq!(
1252 err,
1253 EncodingError::ValueTooLarge {
1254 field: "2-octet AS number in AS_PATH segment",
1255 actual: 400644,
1256 max: 65535,
1257 }
1258 );
1259 }
1260
1261 #[test]
1262 fn test_encode_aggregator_attribute_type_selection() {
1263 let attrs = Attributes::from_iter(vec![AttributeValue::As4Aggregator {
1264 asn: Asn::new_32bit(400644),
1265 id: std::net::Ipv4Addr::new(192, 0, 2, 1),
1266 }]);
1267 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1268 assert_eq!(buf[1], 18, "attribute type must be AS4_AGGREGATOR (18)");
1269 assert_eq!(
1270 buf.len(),
1271 3 + 8,
1272 "value must be a 4-octet ASN plus an IPv4 id"
1273 );
1274 }
1275
1276 #[test]
1277 fn test_encode_aggregator_width_follows_session_not_asn_flag() {
1278 let attrs = Attributes::from_iter(vec![AttributeValue::Aggregator {
1282 asn: Asn::new_32bit(258),
1283 id: std::net::Ipv4Addr::new(10, 0, 0, 1),
1284 }]);
1285 let buf = attrs.encode(AsnLength::Bits16).unwrap();
1286 assert_eq!(buf[1], 7, "attribute type must be AGGREGATOR (7)");
1287 assert_eq!(buf.len(), 3 + 6);
1288 assert_eq!(&buf[3..], &[0x01, 0x02, 10, 0, 0, 1]);
1289 }
1290
1291 #[test]
1292 fn test_parse_attributes_maps_as4_wire_types() {
1293 let data = Bytes::from(vec![0xC0, 17, 6, 0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1296 let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1297 .expect("AS4_PATH must parse");
1298 match attributes.get_attr(AttrType::AS4_PATH).unwrap().value {
1299 AttributeValue::As4Path(path) => assert_eq!(
1300 path.to_u32_vec_opt(false).unwrap(),
1301 vec![400644],
1302 "AS4_PATH segments must parse as 4-octet regardless of session length"
1303 ),
1304 other => panic!("expected As4Path variant, got {other:?}"),
1305 }
1306
1307 let data = Bytes::from(vec![0xC0, 18, 8, 0x00, 0x06, 0x1D, 0x04, 192, 0, 2, 1]);
1309 let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1310 .expect("AS4_AGGREGATOR must parse");
1311 match attributes.get_attr(AttrType::AS4_AGGREGATOR).unwrap().value {
1312 AttributeValue::As4Aggregator { asn, .. } => {
1313 assert_eq!(asn, Asn::new_32bit(400644))
1314 }
1315 other => panic!("expected As4Aggregator variant, got {other:?}"),
1316 }
1317 }
1318}