Skip to main content

bgpkit_parser/models/bgp/attributes/
mod.rs

1//! BGP attribute structs
2mod aspath;
3mod nlri;
4mod origin;
5
6use crate::models::network::*;
7use bitflags::bitflags;
8use bytes::Bytes;
9use num_enum::{FromPrimitive, IntoPrimitive};
10use std::cmp::Ordering;
11use std::iter::{FromIterator, Map};
12use std::net::IpAddr;
13use std::slice::Iter;
14use std::vec::IntoIter;
15
16use crate::error::BgpValidationWarning;
17use crate::models::*;
18
19pub use aspath::*;
20pub use nlri::*;
21pub use origin::*;
22
23bitflags! {
24    /// The high-order bit (bit 0) of the Attribute Flags octet is the
25    /// Optional bit.  It defines whether the attribute is optional (if
26    /// set to 1) or well-known (if set to 0).
27    ///
28    /// The second high-order bit (bit 1) of the Attribute Flags octet
29    /// is the Transitive bit.  It defines whether an optional
30    /// attribute is transitive (if set to 1) or non-transitive (if set
31    /// to 0).
32    ///
33    /// For well-known attributes, the Transitive bit MUST be set to 1.
34    /// (See Section 5 for a discussion of transitive attributes.)
35    ///
36    /// The third high-order bit (bit 2) of the Attribute Flags octet
37    /// is the Partial bit.  It defines whether the information
38    /// contained in the optional transitive attribute is partial (if
39    /// set to 1) or complete (if set to 0).  For well-known attributes
40    /// and for optional non-transitive attributes, the Partial bit
41    /// MUST be set to 0.
42    ///
43    /// The fourth high-order bit (bit 3) of the Attribute Flags octet
44    /// is the Extended Length bit.  It defines whether the Attribute
45    /// Length is one octet (if set to 0) or two octets (if set to 1).
46    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48    pub struct AttrFlags: u8 {
49        const OPTIONAL   = 0b10000000;
50        const TRANSITIVE = 0b01000000;
51        const PARTIAL    = 0b00100000;
52        const EXTENDED   = 0b00010000;
53    }
54}
55
56/// Attribute types.
57///
58/// All attributes currently defined and not Unassigned or Deprecated are included here.
59/// To see the full list, check out IANA at:
60/// <https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2>
61#[allow(non_camel_case_types)]
62#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, FromPrimitive, IntoPrimitive)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[repr(u8)]
65pub enum AttrType {
66    RESERVED = 0,
67    ORIGIN = 1,
68    AS_PATH = 2,
69    NEXT_HOP = 3,
70    MULTI_EXIT_DISCRIMINATOR = 4,
71    LOCAL_PREFERENCE = 5,
72    ATOMIC_AGGREGATE = 6,
73    AGGREGATOR = 7,
74    COMMUNITIES = 8,
75    /// <https://tools.ietf.org/html/rfc4456>
76    ORIGINATOR_ID = 9,
77    CLUSTER_LIST = 10,
78    /// <https://tools.ietf.org/html/rfc4760>
79    MP_REACHABLE_NLRI = 14,
80    MP_UNREACHABLE_NLRI = 15,
81    /// <https://datatracker.ietf.org/doc/html/rfc4360>
82    EXTENDED_COMMUNITIES = 16,
83    AS4_PATH = 17,
84    AS4_AGGREGATOR = 18,
85    PMSI_TUNNEL = 22,
86    TUNNEL_ENCAPSULATION = 23,
87    TRAFFIC_ENGINEERING = 24,
88    IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
89    AIGP = 26,
90    PE_DISTINGUISHER_LABELS = 27,
91    BGP_LS_ATTRIBUTE = 29,
92    LARGE_COMMUNITIES = 32,
93    BGPSEC_PATH = 33,
94    ONLY_TO_CUSTOMER = 35,
95    SFP_ATTRIBUTE = 37,
96    BFD_DISCRIMINATOR = 38,
97    BGP_PREFIX_SID = 40,
98    BIER = 41,
99    ATTR_SET = 128,
100    /// <https://datatracker.ietf.org/doc/html/rfc2042>
101    DEVELOPMENT = 255,
102
103    /// Catch all for any unknown attribute types
104    #[num_enum(catch_all)]
105    // We have to explicitly assign this variant a number, otherwise the compiler will attempt to
106    // assign it to 256 (previous + 1) and overflow the type.
107    Unknown(u8) = 254,
108}
109
110impl PartialOrd for AttrType {
111    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116impl Ord for AttrType {
117    fn cmp(&self, other: &Self) -> Ordering {
118        u8::from(*self).cmp(&u8::from(*other))
119    }
120}
121
122pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
123    match attr_type {
124        11 => Some("DPA"),
125        12 => Some("ADVERTISER"),
126        13 => Some("RCID_PATH"),
127        19 => Some("SAFI Specific Attribute"),
128        20 => Some("Connector Attribute"),
129        21 => Some("AS_PATHLIMIT"),
130        28 => Some("BGP Entropy Label Capability"),
131        30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
132
133        _ => None,
134    }
135}
136
137/// Convenience wrapper for a list of attributes
138#[derive(PartialEq, Clone, Default, Eq)]
139pub struct Attributes {
140    // Black box type to allow for later changes/optimizations. The most common attributes could be
141    // added as fields to allow for easier lookup.
142    pub(crate) inner: Vec<Attribute>,
143    /// RFC 7606 validation warnings collected during parsing
144    pub(crate) validation_warnings: Vec<BgpValidationWarning>,
145    /// Bitmask of seen attributes to allow O(1) checks. Fits in 4 u64s.
146    pub(crate) attr_mask: [u64; 4],
147}
148
149impl std::fmt::Debug for Attributes {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        f.debug_struct("Attributes")
152            .field("inner", &self.inner)
153            .field("validation_warnings", &self.validation_warnings)
154            .finish()
155    }
156}
157
158impl Attributes {
159    pub fn has_attr(&self, ty: AttrType) -> bool {
160        let attr = u8::from(ty);
161        (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
162    }
163
164    pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
165        self.inner
166            .iter()
167            .find(|x| x.value.attr_type() == ty)
168            .cloned()
169    }
170
171    pub fn add_attr(&mut self, attr: Attribute) {
172        let ty = attr.value.attr_code();
173        self.attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
174        self.inner.push(attr);
175    }
176
177    /// Check for missing well-known mandatory attributes.
178    ///
179    /// RFC 4271 (BGP-4) and RFC 4760 (MP-BGP) define which attributes are mandatory.
180    /// - Pure Withdrawals: NO path attributes are required.
181    /// - Announcements: ORIGIN and AS_PATH are strictly required.
182    /// - NEXT_HOP is required if standard IPv4 NLRI is present or if no MP_REACH_NLRI is present.
183    pub fn check_mandatory_attributes(&mut self, is_announcement: bool, has_standard_nlri: bool) {
184        if !is_announcement {
185            return;
186        }
187
188        // ORIGIN and AS_PATH are universally mandatory for all announcements.
189        if !self.has_attr(AttrType::ORIGIN) {
190            self.validation_warnings
191                .push(BgpValidationWarning::MissingWellKnownAttribute {
192                    attr_type: AttrType::ORIGIN,
193                });
194        }
195        if !self.has_attr(AttrType::AS_PATH) {
196            self.validation_warnings
197                .push(BgpValidationWarning::MissingWellKnownAttribute {
198                    attr_type: AttrType::AS_PATH,
199                });
200        }
201
202        // NEXT_HOP is required if this is an IPv4 announcement (has standard NLRI)
203        // or if we haven't seen MP_REACH_NLRI,
204        // which implies standard NLRI is expected, since is_announcement.
205        let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
206        if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
207            self.validation_warnings
208                .push(BgpValidationWarning::MissingWellKnownAttribute {
209                    attr_type: AttrType::NEXT_HOP,
210                });
211        }
212    }
213
214    /// Add a validation warning to the attributes
215    pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
216        self.validation_warnings.push(warning);
217    }
218
219    /// Get all validation warnings for these attributes
220    pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
221        &self.validation_warnings
222    }
223
224    /// Check if there are any validation warnings
225    pub fn has_validation_warnings(&self) -> bool {
226        !self.validation_warnings.is_empty()
227    }
228
229    /// Get the `ORIGIN` attribute. In the event that this attribute is not present,
230    /// [Origin::INCOMPLETE] will be returned instead.
231    pub fn origin(&self) -> Origin {
232        self.inner
233            .iter()
234            .find_map(|x| match &x.value {
235                AttributeValue::Origin(x) => Some(*x),
236                _ => None,
237            })
238            .unwrap_or(Origin::INCOMPLETE)
239    }
240
241    /// Get the `ORIGINATOR_ID` attribute if present.
242    pub fn origin_id(&self) -> Option<BgpIdentifier> {
243        self.inner.iter().find_map(|x| match &x.value {
244            AttributeValue::OriginatorId(x) => Some(*x),
245            _ => None,
246        })
247    }
248
249    /// Get the `NEXT_HOP` attribute if present.
250    ///
251    /// **Note**: Even when this attribute is not present, the next hop address may still be
252    /// attainable from the `MP_REACH_NLRI` attribute.
253    pub fn next_hop(&self) -> Option<IpAddr> {
254        self.inner.iter().find_map(|x| match &x.value {
255            AttributeValue::NextHop(x) => Some(*x),
256            _ => None,
257        })
258    }
259
260    pub fn multi_exit_discriminator(&self) -> Option<u32> {
261        self.inner.iter().find_map(|x| match &x.value {
262            AttributeValue::MultiExitDiscriminator(x) => Some(*x),
263            _ => None,
264        })
265    }
266
267    pub fn local_preference(&self) -> Option<u32> {
268        self.inner.iter().find_map(|x| match &x.value {
269            AttributeValue::LocalPreference(x) => Some(*x),
270            _ => None,
271        })
272    }
273
274    pub fn only_to_customer(&self) -> Option<Asn> {
275        self.inner.iter().find_map(|x| match &x.value {
276            AttributeValue::OnlyToCustomer(x) => Some(*x),
277            _ => None,
278        })
279    }
280
281    pub fn atomic_aggregate(&self) -> bool {
282        self.inner
283            .iter()
284            .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
285    }
286
287    pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
288        // Begin searching at the end of the attributes to increase the odds of finding an AS4
289        // attribute first.
290        self.inner.iter().rev().find_map(|x| match &x.value {
291            AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
292            _ => None,
293        })
294    }
295
296    pub fn clusters(&self) -> Option<&[u32]> {
297        self.inner.iter().find_map(|x| match &x.value {
298            AttributeValue::Clusters(x) => Some(x.as_ref()),
299            _ => None,
300        })
301    }
302
303    // These implementations are horribly inefficient, but they were super easy to write and use
304    pub fn as_path(&self) -> Option<&AsPath> {
305        // Begin searching at the end of the attributes to increase the odds of finding an AS4
306        // attribute first.
307        self.inner.iter().rev().find_map(|x| match &x.value {
308            AttributeValue::AsPath { path, .. } => Some(path),
309            _ => None,
310        })
311    }
312
313    pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
314        self.inner.iter().find_map(|x| match &x.value {
315            AttributeValue::MpReachNlri(x) => Some(x),
316            _ => None,
317        })
318    }
319
320    pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
321        self.inner.iter().find_map(|x| match &x.value {
322            AttributeValue::MpUnreachNlri(x) => Some(x),
323            _ => None,
324        })
325    }
326
327    pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
328        MetaCommunitiesIter {
329            attributes: &self.inner,
330            index: 0,
331        }
332    }
333
334    /// Get an iterator over the held [AttributeValue]s. If you also need attribute flags, consider
335    /// using [Attributes::into_attributes_iter] instead.
336    pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
337        self.into_iter()
338    }
339
340    /// Get an iterator over the held [Attribute]s. If you do no not need attribute flags, consider
341    /// using [Attributes::iter] instead.
342    pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
343        self.inner.into_iter()
344    }
345}
346
347pub struct MetaCommunitiesIter<'a> {
348    attributes: &'a [Attribute],
349    index: usize,
350}
351
352impl Iterator for MetaCommunitiesIter<'_> {
353    type Item = MetaCommunity;
354
355    fn next(&mut self) -> Option<Self::Item> {
356        loop {
357            match &self.attributes.first()?.value {
358                AttributeValue::Communities(x) if self.index < x.len() => {
359                    self.index += 1;
360                    return Some(MetaCommunity::Plain(x[self.index - 1]));
361                }
362                AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
363                    self.index += 1;
364                    return Some(MetaCommunity::Extended(x[self.index - 1]));
365                }
366                AttributeValue::LargeCommunities(x) if self.index < x.len() => {
367                    self.index += 1;
368                    return Some(MetaCommunity::Large(x[self.index - 1]));
369                }
370                _ => {
371                    self.attributes = &self.attributes[1..];
372                    self.index = 0;
373                }
374            }
375        }
376    }
377}
378
379fn compute_mask(inner: &[Attribute]) -> [u64; 4] {
380    let mut attr_mask = [0; 4];
381    for attr in inner {
382        let ty = attr.value.attr_code();
383        attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
384    }
385    attr_mask
386}
387
388impl FromIterator<Attribute> for Attributes {
389    fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
390        let inner: Vec<Attribute> = iter.into_iter().collect();
391        let attr_mask = compute_mask(&inner);
392        Attributes {
393            inner,
394            validation_warnings: Vec::new(),
395            attr_mask,
396        }
397    }
398}
399
400impl From<Vec<Attribute>> for Attributes {
401    fn from(value: Vec<Attribute>) -> Self {
402        let attr_mask = compute_mask(&value);
403        Attributes {
404            inner: value,
405            validation_warnings: Vec::new(),
406            attr_mask,
407        }
408    }
409}
410
411impl Extend<Attribute> for Attributes {
412    fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
413        for attr in iter {
414            self.add_attr(attr);
415        }
416    }
417}
418
419impl Extend<AttributeValue> for Attributes {
420    fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
421        self.extend(iter.into_iter().map(Attribute::from))
422    }
423}
424
425impl FromIterator<AttributeValue> for Attributes {
426    fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
427        let inner: Vec<Attribute> = iter.into_iter().map(Attribute::from).collect();
428        let attr_mask = compute_mask(&inner);
429        Attributes {
430            inner,
431            validation_warnings: Vec::new(),
432            attr_mask,
433        }
434    }
435}
436
437impl IntoIterator for Attributes {
438    type Item = AttributeValue;
439    type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
440
441    fn into_iter(self) -> Self::IntoIter {
442        self.inner.into_iter().map(|x| x.value)
443    }
444}
445
446impl<'a> IntoIterator for &'a Attributes {
447    type Item = &'a AttributeValue;
448    type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
449
450    fn into_iter(self) -> Self::IntoIter {
451        self.inner.iter().map(|x| &x.value)
452    }
453}
454
455#[cfg(feature = "serde")]
456mod serde_impl {
457    use super::*;
458    use serde::{Deserialize, Deserializer, Serialize, Serializer};
459
460    impl Serialize for Attributes {
461        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462        where
463            S: Serializer,
464        {
465            self.inner.serialize(serializer)
466        }
467    }
468
469    impl<'de> Deserialize<'de> for Attributes {
470        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471        where
472            D: Deserializer<'de>,
473        {
474            let inner = <Vec<Attribute>>::deserialize(deserializer)?;
475            let attr_mask = compute_mask(&inner);
476            Ok(Attributes {
477                inner,
478                validation_warnings: Vec::new(),
479                attr_mask,
480            })
481        }
482    }
483}
484
485/// BGP Attribute struct with attribute value and flag
486#[derive(Debug, PartialEq, Clone, Eq)]
487#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
488pub struct Attribute {
489    pub value: AttributeValue,
490    pub flag: AttrFlags,
491}
492
493impl Attribute {
494    pub const fn is_optional(&self) -> bool {
495        self.flag.contains(AttrFlags::OPTIONAL)
496    }
497
498    pub const fn is_transitive(&self) -> bool {
499        self.flag.contains(AttrFlags::TRANSITIVE)
500    }
501
502    pub const fn is_partial(&self) -> bool {
503        self.flag.contains(AttrFlags::PARTIAL)
504    }
505
506    pub const fn is_extended(&self) -> bool {
507        self.flag.contains(AttrFlags::EXTENDED)
508    }
509}
510
511impl From<AttributeValue> for Attribute {
512    fn from(value: AttributeValue) -> Self {
513        Attribute {
514            flag: value.default_flags(),
515            value,
516        }
517    }
518}
519
520/// AIGP TLV (Type-Length-Value) entry - RFC 7311
521#[derive(Debug, PartialEq, Clone, Eq)]
522#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
523pub struct AigpTlv {
524    pub tlv_type: u8,
525    pub length: u16,
526    pub value: Bytes,
527}
528
529/// AIGP (Accumulated IGP Metric) Attribute - RFC 7311
530///
531/// Type 26, optional non-transitive attribute containing TLVs.
532/// The AIGP TLV (Type=1) contains an 8-octet accumulated metric value.
533#[derive(Debug, PartialEq, Clone, Eq)]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535pub struct Aigp {
536    pub tlvs: Vec<AigpTlv>,
537}
538
539impl Aigp {
540    /// Get the accumulated metric from the first AIGP TLV (Type=1)
541    pub fn accumulated_metric(&self) -> Option<u64> {
542        self.tlvs
543            .iter()
544            .find(|tlv| tlv.tlv_type == 1)
545            .and_then(|tlv| {
546                if tlv.value.len() >= 8 {
547                    Some(u64::from_be_bytes([
548                        tlv.value[0],
549                        tlv.value[1],
550                        tlv.value[2],
551                        tlv.value[3],
552                        tlv.value[4],
553                        tlv.value[5],
554                        tlv.value[6],
555                        tlv.value[7],
556                    ]))
557                } else {
558                    None
559                }
560            })
561    }
562}
563
564/// Raw TLV with 1-octet type and 1-octet value length.
565#[derive(Debug, PartialEq, Clone, Eq)]
566#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
567pub struct RawTlv8 {
568    pub tlv_type: u8,
569    pub value: Bytes,
570}
571
572/// Raw TLV with 1-octet type and 2-octet value length.
573#[derive(Debug, PartialEq, Clone, Eq)]
574#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
575pub struct RawTlv8Ext {
576    pub tlv_type: u8,
577    pub value: Bytes,
578}
579
580/// Raw TLV with 2-octet type and 2-octet value length.
581#[derive(Debug, PartialEq, Clone, Eq)]
582#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
583pub struct RawTlv16 {
584    pub tlv_type: u16,
585    pub value: Bytes,
586}
587
588/// BFD Discriminator Attribute - RFC 9026
589#[derive(Debug, PartialEq, Clone, Eq)]
590#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
591pub struct BfdDiscriminatorAttribute {
592    pub mode: u8,
593    pub discriminator: u32,
594    pub tlvs: Vec<RawTlv8>,
595}
596
597/// BGP Prefix-SID Attribute - RFC 8669
598#[derive(Debug, PartialEq, Clone, Eq)]
599#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
600pub struct BgpPrefixSidAttribute {
601    pub tlvs: Vec<RawTlv8Ext>,
602}
603
604/// BIER Attribute - RFC 9793
605#[derive(Debug, PartialEq, Clone, Eq)]
606#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
607pub struct BierAttribute {
608    pub tlvs: Vec<RawTlv16>,
609}
610
611/// SFP Attribute - RFC 9015
612#[derive(Debug, PartialEq, Clone, Eq)]
613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
614pub struct SfpAttribute {
615    pub tlvs: Vec<RawTlv8Ext>,
616}
617
618/// BGP Traffic Engineering Attribute - RFC 5543
619#[derive(Debug, Clone)]
620#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
621pub struct TrafficEngineering {
622    /// Switching Capability code defined by GMPLS.
623    pub switching_capability: u8,
624
625    /// Interface encoding code defined by GMPLS.
626    pub encoding: u8,
627
628    /// Reserved wire value.
629    ///
630    /// RFC 5543 says originators should set this to zero and receivers
631    /// must ignore it.
632    pub reserved: u16,
633
634    /// Maximum LSP bandwidth for priorities 0 through 7, in bytes per second.
635    pub max_lsp_bandwidth: [f32; 8],
636
637    /// Switching-capability-specific information retained as raw bytes.
638    pub switching_capability_specific: Bytes,
639}
640
641impl PartialEq for TrafficEngineering {
642    fn eq(&self, other: &Self) -> bool {
643        self.switching_capability == other.switching_capability
644            && self.encoding == other.encoding
645            && self.reserved == other.reserved
646            && self
647                .max_lsp_bandwidth
648                .iter()
649                .zip(other.max_lsp_bandwidth.iter())
650                .all(|(left, right)| left.to_bits() == right.to_bits())
651            && self.switching_capability_specific == other.switching_capability_specific
652    }
653}
654
655impl Eq for TrafficEngineering {}
656
657/// ATTR_SET Attribute - RFC 6368
658///
659/// Used in BGP/MPLS IP VPNs to transparently carry customer BGP path attributes
660/// through the VPN core. Acts as a stack where attributes are "pushed" at the
661/// PE ingress and "popped" at the PE egress.
662///
663/// Type 128, optional transitive attribute.
664#[derive(Debug, PartialEq, Clone, Eq)]
665#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
666pub struct AttrSet {
667    /// Origin AS number (customer network AS)
668    pub origin_as: Asn,
669    /// Nested path attributes
670    pub attributes: Attributes,
671}
672
673/// The `AttributeValue` enum represents different kinds of Attribute values.
674#[derive(Debug, PartialEq, Clone, Eq)]
675#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
676pub enum AttributeValue {
677    Origin(Origin),
678    AsPath {
679        path: AsPath,
680        is_as4: bool,
681    },
682    NextHop(IpAddr),
683    MultiExitDiscriminator(u32),
684    LocalPreference(u32),
685    OnlyToCustomer(Asn),
686    AtomicAggregate,
687    Aggregator {
688        asn: Asn,
689        id: BgpIdentifier,
690        is_as4: bool,
691    },
692    Communities(Vec<Community>),
693    ExtendedCommunities(Vec<ExtendedCommunity>),
694    Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
695    LargeCommunities(Vec<LargeCommunity>),
696    OriginatorId(BgpIdentifier),
697    Clusters(Vec<u32>),
698    MpReachNlri(Nlri),
699    MpUnreachNlri(Nlri),
700    /// BGP Link-State attribute - RFC 7752
701    LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
702    /// BGP Tunnel Encapsulation attribute - RFC 9012
703    TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
704    /// BGP Traffic Engineering attribute - RFC 5543
705    TrafficEngineering(TrafficEngineering),
706    /// BFD Discriminator attribute - RFC 9026
707    BfdDiscriminator(BfdDiscriminatorAttribute),
708    /// BGP Prefix-SID attribute - RFC 8669
709    BgpPrefixSid(BgpPrefixSidAttribute),
710    /// BIER attribute - RFC 9793
711    Bier(BierAttribute),
712    /// SFP attribute - RFC 9015
713    Sfp(SfpAttribute),
714    Development(Vec<u8>),
715    Raw(AttrRaw),
716    Deprecated(AttrRaw),
717    Unknown(AttrRaw),
718    /// AIGP (Accumulated IGP Metric) attribute - RFC 7311
719    Aigp(Aigp),
720    /// ATTR_SET attribute - RFC 6368
721    AttrSet(AttrSet),
722}
723
724impl From<Origin> for AttributeValue {
725    fn from(value: Origin) -> Self {
726        AttributeValue::Origin(value)
727    }
728}
729
730/// Defaults to using `AS_PATH` (as opposed to `AS4_PATH`) when choosing attribute type.
731impl From<AsPath> for AttributeValue {
732    fn from(path: AsPath) -> Self {
733        AttributeValue::AsPath {
734            path,
735            is_as4: false,
736        }
737    }
738}
739
740/// Category of an attribute.
741///
742/// <https://datatracker.ietf.org/doc/html/rfc4271#section-5>
743#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
744#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
745pub enum AttributeCategory {
746    WellKnownMandatory,
747    WellKnownDiscretionary,
748    OptionalTransitive,
749    OptionalNonTransitive,
750}
751
752impl AttributeValue {
753    pub fn attr_type(&self) -> AttrType {
754        match self {
755            AttributeValue::Origin(_) => AttrType::ORIGIN,
756            AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
757            AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
758            AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
759            AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
760            AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
761            AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
762            AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
763            AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
764            AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
765            AttributeValue::Communities(_) => AttrType::COMMUNITIES,
766            AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
767            AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
768                AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
769            }
770            AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
771            AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
772            AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
773            AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
774            AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
775            AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
776            AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
777            AttributeValue::TrafficEngineering(_) => AttrType::TRAFFIC_ENGINEERING,
778            AttributeValue::BfdDiscriminator(_) => AttrType::BFD_DISCRIMINATOR,
779            AttributeValue::BgpPrefixSid(_) => AttrType::BGP_PREFIX_SID,
780            AttributeValue::Bier(_) => AttrType::BIER,
781            AttributeValue::Sfp(_) => AttrType::SFP_ATTRIBUTE,
782            AttributeValue::Development(_) => AttrType::DEVELOPMENT,
783            AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
784                x.attr_type()
785            }
786            AttributeValue::Aigp(_) => AttrType::AIGP,
787            AttributeValue::AttrSet(_) => AttrType::ATTR_SET,
788        }
789    }
790
791    pub fn attr_code(&self) -> u8 {
792        match self {
793            AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
794                x.code
795            }
796            _ => self.attr_type().into(),
797        }
798    }
799
800    pub fn attr_category(&self) -> Option<AttributeCategory> {
801        use AttributeCategory::*;
802
803        match self {
804            AttributeValue::Origin(_) => Some(WellKnownMandatory),
805            AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
806            AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
807            AttributeValue::NextHop(_) => Some(WellKnownMandatory),
808            AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
809            // If we receive this attribute we must be in IBGP so it is required
810            AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
811            AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
812            AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
813            AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
814            AttributeValue::Communities(_) => Some(OptionalTransitive),
815            AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
816            AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
817            AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
818            AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
819            AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
820            AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
821            AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
822            AttributeValue::TrafficEngineering(_) => Some(OptionalNonTransitive),
823            AttributeValue::Aigp(_) => Some(OptionalNonTransitive),
824            AttributeValue::BfdDiscriminator(_) => Some(OptionalTransitive),
825            AttributeValue::BgpPrefixSid(_) => Some(OptionalTransitive),
826            AttributeValue::Bier(_) => Some(OptionalTransitive),
827            AttributeValue::Sfp(_) => Some(OptionalTransitive),
828            AttributeValue::AttrSet(_) => Some(OptionalTransitive),
829            _ => None,
830        }
831    }
832
833    /// Get flags based on the attribute type. The [AttrFlags::EXTENDED] is not taken into account
834    /// when determining the correct flags.
835    pub fn default_flags(&self) -> AttrFlags {
836        match self.attr_category() {
837            None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
838            Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
839            Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
840            Some(AttributeCategory::OptionalTransitive) => {
841                AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
842            }
843            Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
844        }
845    }
846}
847
848#[derive(Debug, PartialEq, Clone, Eq)]
849#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
850pub struct AttrRaw {
851    pub code: u8,
852    pub bytes: Bytes,
853}
854
855impl AttrRaw {
856    /// Map the raw wire code back to an `AttrType`.
857    ///
858    /// For `Raw` variants (known-but-unparsed codes like `PMSI_TUNNEL`),
859    /// this returns the concrete `AttrType` variant (e.g. `AttrType::PMSI_TUNNEL`).
860    /// For `Deprecated` and `Unknown` variants, this returns `AttrType::Unknown(code)`.
861    pub fn attr_type(&self) -> AttrType {
862        AttrType::from(self.code)
863    }
864}
865
866#[cfg(test)]
867mod tests {
868    use super::*;
869    use std::net::Ipv4Addr;
870    use std::str::FromStr;
871
872    #[test]
873    fn test_attr_type() {
874        let attr_value = AttributeValue::Origin(Origin::IGP);
875        assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
876    }
877
878    #[test]
879    fn test_attr_category() {
880        let attr_value = AttributeValue::Origin(Origin::IGP);
881        let category = attr_value.attr_category().unwrap();
882        assert_eq!(category, AttributeCategory::WellKnownMandatory);
883    }
884
885    #[test]
886    fn test_default_flags() {
887        let attr_value = AttributeValue::Origin(Origin::IGP);
888        let flags = attr_value.default_flags();
889        assert_eq!(flags, AttrFlags::TRANSITIVE);
890    }
891
892    #[test]
893    fn test_from_iter_attribute_value_uses_default_flags() {
894        let attributes = Attributes::from_iter(vec![
895            AttributeValue::Origin(Origin::IGP),
896            AttributeValue::AsPath {
897                path: AsPath::new(),
898                is_as4: false,
899            },
900        ]);
901
902        assert_eq!(
903            attributes.get_attr(AttrType::ORIGIN).unwrap().flag,
904            AttrFlags::TRANSITIVE
905        );
906        assert_eq!(
907            attributes.get_attr(AttrType::AS_PATH).unwrap().flag,
908            AttrFlags::TRANSITIVE
909        );
910    }
911
912    #[test]
913    fn test_get_attr() {
914        let attribute = Attribute {
915            value: AttributeValue::Origin(Origin::IGP),
916            flag: AttrFlags::TRANSITIVE,
917        };
918
919        let mut attributes = Attributes::default();
920        attributes.add_attr(attribute.clone());
921
922        assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
923    }
924
925    #[test]
926    fn test_has_attr() {
927        let attribute = Attribute {
928            value: AttributeValue::Origin(Origin::IGP),
929            flag: AttrFlags::TRANSITIVE,
930        };
931
932        let mut attributes = Attributes::default();
933        attributes.add_attr(attribute);
934
935        assert!(attributes.has_attr(AttrType::ORIGIN));
936    }
937
938    #[test]
939    fn test_getting_all_attributes() {
940        let mut attributes = Attributes::default();
941        attributes.add_attr(Attribute {
942            value: AttributeValue::Origin(Origin::IGP),
943            flag: AttrFlags::TRANSITIVE,
944        });
945        attributes.add_attr(Attribute {
946            value: AttributeValue::AsPath {
947                path: AsPath::new(),
948                is_as4: false,
949            },
950            flag: AttrFlags::TRANSITIVE,
951        });
952        attributes.add_attr(Attribute {
953            value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
954            flag: AttrFlags::TRANSITIVE,
955        });
956        attributes.add_attr(Attribute {
957            value: AttributeValue::MultiExitDiscriminator(1),
958            flag: AttrFlags::TRANSITIVE,
959        });
960
961        attributes.add_attr(Attribute {
962            value: AttributeValue::LocalPreference(1),
963            flag: AttrFlags::TRANSITIVE,
964        });
965        attributes.add_attr(Attribute {
966            value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
967            flag: AttrFlags::TRANSITIVE,
968        });
969        attributes.add_attr(Attribute {
970            value: AttributeValue::AtomicAggregate,
971            flag: AttrFlags::TRANSITIVE,
972        });
973        attributes.add_attr(Attribute {
974            value: AttributeValue::Clusters(vec![1, 2, 3]),
975            flag: AttrFlags::TRANSITIVE,
976        });
977        attributes.add_attr(Attribute {
978            value: AttributeValue::Aggregator {
979                asn: Asn::new_32bit(1),
980                id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
981                is_as4: false,
982            },
983            flag: AttrFlags::TRANSITIVE,
984        });
985        attributes.add_attr(Attribute {
986            value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
987            flag: AttrFlags::TRANSITIVE,
988        });
989
990        assert_eq!(attributes.origin(), Origin::IGP);
991        assert_eq!(attributes.as_path(), Some(&AsPath::new()));
992        assert_eq!(
993            attributes.next_hop(),
994            Some(IpAddr::from_str("10.0.0.0").unwrap())
995        );
996        assert_eq!(attributes.multi_exit_discriminator(), Some(1));
997        assert_eq!(attributes.local_preference(), Some(1));
998        assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
999        assert!(attributes.atomic_aggregate());
1000        assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
1001        assert_eq!(
1002            attributes.aggregator(),
1003            Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
1004        );
1005        assert_eq!(
1006            attributes.origin_id(),
1007            Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
1008        );
1009
1010        let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
1011        assert!(aspath_attr.is_transitive());
1012        assert!(!aspath_attr.is_extended());
1013        assert!(!aspath_attr.is_partial());
1014        assert!(!aspath_attr.is_optional());
1015
1016        for attr in attributes.iter() {
1017            println!("{attr:?}");
1018        }
1019    }
1020
1021    #[test]
1022    fn test_from() {
1023        let origin = Origin::IGP;
1024        let attr_value = AttributeValue::from(origin);
1025        assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
1026
1027        let aspath = AsPath::new();
1028        let attr_value = AttributeValue::from(aspath);
1029        assert_eq!(
1030            attr_value,
1031            AttributeValue::AsPath {
1032                path: AsPath::new(),
1033                is_as4: false
1034            }
1035        );
1036    }
1037
1038    #[test]
1039    fn test_well_known_mandatory_attrs() {
1040        let origin_attr = AttributeValue::Origin(Origin::IGP);
1041        assert_eq!(
1042            origin_attr.attr_category(),
1043            Some(AttributeCategory::WellKnownMandatory)
1044        );
1045        let as_path_attr = AttributeValue::AsPath {
1046            path: AsPath::new(),
1047            is_as4: false,
1048        };
1049        assert_eq!(
1050            as_path_attr.attr_category(),
1051            Some(AttributeCategory::WellKnownMandatory)
1052        );
1053        let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
1054        assert_eq!(
1055            next_hop_attr.attr_category(),
1056            Some(AttributeCategory::WellKnownMandatory)
1057        );
1058        let local_preference_attr = AttributeValue::LocalPreference(1);
1059        assert_eq!(
1060            local_preference_attr.attr_category(),
1061            Some(AttributeCategory::WellKnownMandatory)
1062        );
1063    }
1064
1065    #[test]
1066    fn test_well_known_discretionary_attrs() {
1067        let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
1068        assert_eq!(
1069            atomic_aggregate_attr.attr_category(),
1070            Some(AttributeCategory::WellKnownDiscretionary)
1071        );
1072    }
1073
1074    #[test]
1075    fn test_optional_transitive_attrs() {
1076        let as_path_attr = AttributeValue::AsPath {
1077            path: AsPath::new(),
1078            is_as4: true,
1079        };
1080        assert_eq!(
1081            as_path_attr.attr_category(),
1082            Some(AttributeCategory::OptionalTransitive)
1083        );
1084        let aggregator_attr = AttributeValue::Aggregator {
1085            asn: Asn::new_32bit(1),
1086            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1087            is_as4: false,
1088        };
1089        assert_eq!(
1090            aggregator_attr.attr_category(),
1091            Some(AttributeCategory::OptionalTransitive)
1092        );
1093        let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
1094        assert_eq!(
1095            only_to_customer_attr.attr_category(),
1096            Some(AttributeCategory::OptionalTransitive)
1097        );
1098        let communities_attr =
1099            AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
1100        assert_eq!(
1101            communities_attr.attr_category(),
1102            Some(AttributeCategory::OptionalTransitive)
1103        );
1104        let extended_communities_attr =
1105            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
1106        assert_eq!(
1107            extended_communities_attr.attr_category(),
1108            Some(AttributeCategory::OptionalTransitive)
1109        );
1110        let large_communities_attr =
1111            AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
1112        assert_eq!(
1113            large_communities_attr.attr_category(),
1114            Some(AttributeCategory::OptionalTransitive)
1115        );
1116        let aggregator_attr = AttributeValue::Aggregator {
1117            asn: Asn::new_32bit(1),
1118            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1119            is_as4: true,
1120        };
1121        assert_eq!(
1122            aggregator_attr.attr_category(),
1123            Some(AttributeCategory::OptionalTransitive)
1124        );
1125    }
1126
1127    #[test]
1128    fn test_new_attribute_attr_categories() {
1129        // BFD Discriminator (RFC 9026): Optional Transitive
1130        assert_eq!(
1131            AttributeValue::BfdDiscriminator(BfdDiscriminatorAttribute {
1132                mode: 1,
1133                discriminator: 0,
1134                tlvs: vec![],
1135            })
1136            .attr_category(),
1137            Some(AttributeCategory::OptionalTransitive)
1138        );
1139        // BGP Prefix-SID (RFC 8669): Optional Transitive
1140        assert_eq!(
1141            AttributeValue::BgpPrefixSid(BgpPrefixSidAttribute { tlvs: vec![] }).attr_category(),
1142            Some(AttributeCategory::OptionalTransitive)
1143        );
1144        // BIER (RFC 9793): Optional Transitive
1145        assert_eq!(
1146            AttributeValue::Bier(BierAttribute { tlvs: vec![] }).attr_category(),
1147            Some(AttributeCategory::OptionalTransitive)
1148        );
1149        // SFP (RFC 9015): Optional Transitive
1150        assert_eq!(
1151            AttributeValue::Sfp(SfpAttribute { tlvs: vec![] }).attr_category(),
1152            Some(AttributeCategory::OptionalTransitive)
1153        );
1154    }
1155
1156    #[test]
1157    fn test_optional_non_transitive_attrs() {
1158        let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
1159        assert_eq!(
1160            multi_exit_discriminator_attr.attr_category(),
1161            Some(AttributeCategory::OptionalNonTransitive)
1162        );
1163        let originator_id_attr =
1164            AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
1165        assert_eq!(
1166            originator_id_attr.attr_category(),
1167            Some(AttributeCategory::OptionalNonTransitive)
1168        );
1169        let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
1170        assert_eq!(
1171            clusters_attr.attr_category(),
1172            Some(AttributeCategory::OptionalNonTransitive)
1173        );
1174        let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
1175            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1176        ));
1177        assert_eq!(
1178            mp_unreach_nlri_attr.attr_category(),
1179            Some(AttributeCategory::OptionalNonTransitive)
1180        );
1181
1182        let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1183            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1184        ));
1185        assert_eq!(
1186            mp_reach_nlri_attr.attr_category(),
1187            Some(AttributeCategory::OptionalNonTransitive)
1188        );
1189
1190        let traffic_engineering_attr = AttributeValue::TrafficEngineering(TrafficEngineering {
1191            switching_capability: 1,
1192            encoding: 1,
1193            reserved: 0,
1194            max_lsp_bandwidth: [0.0; 8],
1195            switching_capability_specific: Bytes::new(),
1196        });
1197
1198        assert_eq!(
1199            traffic_engineering_attr.attr_category(),
1200            Some(AttributeCategory::OptionalNonTransitive)
1201        );
1202
1203        assert_eq!(
1204            traffic_engineering_attr.default_flags(),
1205            AttrFlags::OPTIONAL
1206        );
1207    }
1208
1209    #[test]
1210    #[cfg(feature = "serde")]
1211    fn test_serde() {
1212        let attributes = Attributes::from_iter(vec![
1213            Attribute {
1214                value: AttributeValue::Origin(Origin::IGP),
1215                flag: AttrFlags::TRANSITIVE,
1216            },
1217            Attribute {
1218                value: AttributeValue::AsPath {
1219                    path: AsPath::new(),
1220                    is_as4: false,
1221                },
1222                flag: AttrFlags::TRANSITIVE,
1223            },
1224        ]);
1225
1226        let serialized = serde_json::to_string(&attributes).unwrap();
1227        let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
1228
1229        assert_eq!(attributes, deserialized);
1230    }
1231}