bgp_models/bgp/
attributes.rs

1//! BGP attribute structs
2use crate::bgp::{Community, ExtendedCommunity, LargeCommunity};
3use crate::network::*;
4use itertools::Itertools;
5use serde::{Serialize, Serializer};
6use std::fmt::{Display, Formatter};
7use std::net::IpAddr;
8
9/// The high-order bit (bit 0) of the Attribute Flags octet is the
10/// Optional bit.  It defines whether the attribute is optional (if
11/// set to 1) or well-known (if set to 0).
12///
13/// The second high-order bit (bit 1) of the Attribute Flags octet
14/// is the Transitive bit.  It defines whether an optional
15/// attribute is transitive (if set to 1) or non-transitive (if set
16/// to 0).
17///
18/// For well-known attributes, the Transitive bit MUST be set to 1.
19/// (See Section 5 for a discussion of transitive attributes.)
20///
21/// The third high-order bit (bit 2) of the Attribute Flags octet
22/// is the Partial bit.  It defines whether the information
23/// contained in the optional transitive attribute is partial (if
24/// set to 1) or complete (if set to 0).  For well-known attributes
25/// and for optional non-transitive attributes, the Partial bit
26/// MUST be set to 0.
27///
28/// The fourth high-order bit (bit 3) of the Attribute Flags octet
29/// is the Extended Length bit.  It defines whether the Attribute
30/// Length is one octet (if set to 0) or two octets (if set to 1).
31pub enum AttributeFlagsBit {
32    /// 128 = 0b10000000
33    OptionalBit = 0b10000000,
34    /// 64 = 0b01000000
35    TransitiveBit = 0b01000000,
36    /// 32 = 0b00100000
37    PartialBit = 0b00100000,
38    /// 16 = 0b00010000
39    ExtendedLengthBit = 0b00010000,
40}
41
42/// Attribute types.
43///
44/// All attributes currently defined and not Unassigned or Deprecated are included here.
45/// To see the full list, check out IANA at:
46/// <https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2>
47#[allow(non_camel_case_types)]
48#[derive(Debug, Primitive, PartialEq, Eq, Hash, Copy, Clone, Serialize)]
49pub enum AttrType {
50    RESERVED = 0,
51    ORIGIN = 1,
52    AS_PATH = 2,
53    NEXT_HOP = 3,
54    MULTI_EXIT_DISCRIMINATOR = 4,
55    LOCAL_PREFERENCE = 5,
56    ATOMIC_AGGREGATE = 6,
57    AGGREGATOR = 7,
58    COMMUNITIES = 8,
59    /// <https://tools.ietf.org/html/rfc4456>
60    ORIGINATOR_ID = 9,
61    CLUSTER_LIST = 10,
62    /// <https://tools.ietf.org/html/rfc4760>
63    CLUSTER_ID = 13,
64    MP_REACHABLE_NLRI = 14,
65    MP_UNREACHABLE_NLRI = 15,
66    /// <https://datatracker.ietf.org/doc/html/rfc4360>
67    EXTENDED_COMMUNITIES = 16,
68    AS4_PATH = 17,
69    AS4_AGGREGATOR = 18,
70    PMSI_TUNNEL = 22,
71    TUNNEL_ENCAPSULATION = 23,
72    TRAFFIC_ENGINEERING = 24,
73    IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
74    AIGP = 26,
75    PE_DISTINGUISHER_LABELS = 27,
76    BGP_LS_ATTRIBUTE = 29,
77    LARGE_COMMUNITIES = 32,
78    BGPSEC_PATH = 33,
79    ONLY_TO_CUSTOMER = 35,
80    SFP_ATTRIBUTE = 37,
81    BFD_DISCRIMINATOR = 38,
82    BGP_PREFIX_SID = 40,
83    ATTR_SET = 128,
84    /// <https://datatracker.ietf.org/doc/html/rfc2042>
85    DEVELOPMENT = 255,
86}
87
88pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
89    match attr_type {
90        11 => Some("DPA"),
91        12 => Some("ADVERTISER"),
92        13 => Some("RCID_PATH"),
93        19 => Some("SAFI Specific Attribute"),
94        20 => Some("Connector Attribute"),
95        21 => Some("AS_PATHLIMIT"),
96        28 => Some("BGP Entropy Label Capability"),
97        30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
98
99        _ => None,
100    }
101}
102
103#[allow(non_camel_case_types)]
104#[derive(Debug, Primitive, PartialEq, Eq, Hash, Copy, Clone)]
105pub enum Origin {
106    IGP = 0,
107    EGP = 1,
108    INCOMPLETE = 2,
109}
110
111#[allow(non_camel_case_types)]
112#[derive(Debug, Primitive, PartialEq, Eq, Hash, Copy, Clone)]
113pub enum AtomicAggregate {
114    NAG = 0,
115    AG = 1,
116}
117
118/// BGP Attribute struct with attribute value and flag
119#[derive(Debug, PartialEq, Clone, Serialize, Eq)]
120pub struct Attribute {
121    pub attr_type: AttrType,
122    pub value: AttributeValue,
123    pub flag: u8,
124}
125
126/// The `AttributeValue` enum represents different kinds of Attribute values.
127#[derive(Debug, PartialEq, Clone, Serialize, Eq)]
128pub enum AttributeValue {
129    Origin(Origin),
130    AsPath(AsPath),
131    As4Path(AsPath),
132    NextHop(IpAddr),
133    MultiExitDiscriminator(u32),
134    LocalPreference(u32),
135    OnlyToCustomer(u32),
136    AtomicAggregate(AtomicAggregate),
137    Aggregator(Asn, IpAddr),
138    Communities(Vec<Community>),
139    ExtendedCommunities(Vec<ExtendedCommunity>),
140    LargeCommunities(Vec<LargeCommunity>),
141    OriginatorId(IpAddr),
142    Clusters(Vec<IpAddr>),
143    MpReachNlri(Nlri),
144    MpUnreachNlri(Nlri),
145    Development(Vec<u8>),
146}
147
148/////////////
149// AS PATH //
150/////////////
151
152/// Enum of AS path segment.
153#[derive(Debug, PartialEq, Clone, Eq)]
154pub enum AsPathSegment {
155    AsSequence(Vec<Asn>),
156    AsSet(Vec<Asn>),
157    ConfedSequence(Vec<Asn>),
158    ConfedSet(Vec<Asn>),
159}
160
161impl AsPathSegment {
162    pub fn count_asns(&self) -> usize {
163        match self {
164            AsPathSegment::AsSequence(v) => v.len(),
165            AsPathSegment::AsSet(_) => 1,
166            AsPathSegment::ConfedSequence(_) | AsPathSegment::ConfedSet(_) => 0,
167        }
168    }
169}
170
171#[derive(Debug, PartialEq, Clone, Eq)]
172pub struct AsPath {
173    pub segments: Vec<AsPathSegment>,
174}
175
176impl Default for AsPath {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182impl AsPath {
183    pub fn new() -> AsPath {
184        AsPath { segments: vec![] }
185    }
186
187    pub fn from_segments(segments: Vec<AsPathSegment>) -> AsPath {
188        AsPath { segments }
189    }
190
191    pub fn add_segment(&mut self, segment: AsPathSegment) {
192        self.segments.push(segment);
193    }
194
195    pub fn segments(&self) -> &Vec<AsPathSegment> {
196        &self.segments
197    }
198
199    pub fn count_asns(&self) -> usize {
200        self.segments.iter().map(AsPathSegment::count_asns).sum()
201    }
202
203    /// Construct AsPath from AS_PATH and AS4_PATH
204    ///
205    /// https://datatracker.ietf.org/doc/html/rfc6793#section-4.2.3
206    ///    If the number of AS numbers in the AS_PATH attribute is less than the
207    ///    number of AS numbers in the AS4_PATH attribute, then the AS4_PATH
208    ///    attribute SHALL be ignored, and the AS_PATH attribute SHALL be taken
209    ///    as the AS path information.
210    ///
211    ///    If the number of AS numbers in the AS_PATH attribute is larger than
212    ///    or equal to the number of AS numbers in the AS4_PATH attribute, then
213    ///    the AS path information SHALL be constructed by taking as many AS
214    ///    numbers and path segments as necessary from the leading part of the
215    ///    AS_PATH attribute, and then prepending them to the AS4_PATH attribute
216    ///    so that the AS path information has a number of AS numbers identical
217    ///    to that of the AS_PATH attribute.  Note that a valid
218    ///    AS_CONFED_SEQUENCE or AS_CONFED_SET path segment SHALL be prepended
219    ///    if it is either the leading path segment or is adjacent to a path
220    ///    segment that is prepended.
221    pub fn merge_aspath_as4path(aspath: &AsPath, as4path: &AsPath) -> Option<AsPath> {
222        if aspath.count_asns() < as4path.count_asns() {
223            return Some(aspath.clone());
224        }
225
226        let mut as4iter = as4path.segments.iter();
227        let mut as4seg = as4iter.next();
228        let mut new_segs: Vec<AsPathSegment> = vec![];
229        if as4seg.is_none() {
230            new_segs.extend(aspath.segments.clone());
231            return Some(AsPath { segments: new_segs });
232        }
233
234        for seg in &aspath.segments {
235            let as4seg_unwrapped = as4seg.unwrap();
236            if let (AsPathSegment::AsSequence(seq), AsPathSegment::AsSequence(seq4)) =
237                (seg, as4seg_unwrapped)
238            {
239                let diff_len = seq.len() - seq4.len();
240                let mut new_seq: Vec<Asn> = vec![];
241                new_seq.extend(seq.iter().take(diff_len));
242                new_seq.extend(seq4);
243                new_segs.push(AsPathSegment::AsSequence(new_seq));
244            } else {
245                new_segs.push(as4seg_unwrapped.clone());
246            }
247            as4seg = as4iter.next();
248        }
249
250        Some(AsPath { segments: new_segs })
251    }
252
253    pub fn get_origin(&self) -> Option<Vec<Asn>> {
254        if let Some(seg) = self.segments.last() {
255            match seg {
256                AsPathSegment::AsSequence(v) => v.last().map(|n| vec![*n]),
257                AsPathSegment::AsSet(v) => Some(v.clone()),
258                AsPathSegment::ConfedSequence(_) | AsPathSegment::ConfedSet(_) => None,
259            }
260        } else {
261            None
262        }
263    }
264
265    pub fn to_u32_vec(&self) -> Option<Vec<u32>> {
266        if !self
267            .segments
268            .iter()
269            .all(|seg| matches!(seg, AsPathSegment::AsSequence(_v)))
270        {
271            // as path contains AS set or confederated sequence/set
272            return None;
273        }
274        let mut path = vec![];
275        for s in &self.segments {
276            if let AsPathSegment::AsSequence(seg) = s {
277                for asn in seg {
278                    path.push(asn.asn);
279                }
280            } else {
281                // this won't happen
282                return None;
283            }
284        }
285        Some(path)
286    }
287}
288
289//////////
290// NLRI //
291//////////
292
293#[derive(Debug, PartialEq, Clone, Serialize, Eq)]
294pub struct Nlri {
295    pub afi: Afi,
296    pub safi: Safi,
297    pub next_hop: Option<NextHopAddress>,
298    pub prefixes: Vec<NetworkPrefix>,
299}
300
301#[derive(Debug, PartialEq, Clone, Serialize)]
302pub struct MpReachableNlri {
303    afi: Afi,
304    safi: Safi,
305    next_hop: NextHopAddress,
306    prefixes: Vec<NetworkPrefix>,
307}
308
309impl MpReachableNlri {
310    pub fn new(
311        afi: Afi,
312        safi: Safi,
313        next_hop: NextHopAddress,
314        prefixes: Vec<NetworkPrefix>,
315    ) -> MpReachableNlri {
316        MpReachableNlri {
317            afi,
318            safi,
319            next_hop,
320            prefixes,
321        }
322    }
323}
324
325#[derive(Debug, PartialEq, Copy, Clone)]
326pub struct MpReachableNlriV2 {
327    next_hop: NextHopAddress,
328}
329
330#[derive(Debug, PartialEq, Clone)]
331pub struct MpUnreachableNlri {
332    afi: Afi,
333    safi: Safi,
334    prefixes: Vec<NetworkPrefix>,
335}
336
337impl MpUnreachableNlri {
338    pub fn new(afi: Afi, safi: Safi, prefixes: Vec<NetworkPrefix>) -> MpUnreachableNlri {
339        MpUnreachableNlri {
340            afi,
341            safi,
342            prefixes,
343        }
344    }
345}
346
347///////////////////
348// DISPLAY IMPLS //
349///////////////////
350
351impl Display for Origin {
352    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353        let s = match self {
354            Origin::IGP => "IGP",
355            Origin::EGP => "EGP",
356            Origin::INCOMPLETE => "INCOMPLETE",
357        };
358        write!(f, "{}", s)
359    }
360}
361
362impl Display for AtomicAggregate {
363    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
364        write!(
365            f,
366            "{}",
367            match self {
368                AtomicAggregate::NAG => {
369                    "NAG"
370                }
371                AtomicAggregate::AG => {
372                    "AG"
373                }
374            }
375        )
376    }
377}
378
379impl Display for NextHopAddress {
380    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
381        write!(
382            f,
383            "{}",
384            match self {
385                NextHopAddress::Ipv4(v) => {
386                    v.to_string()
387                }
388                NextHopAddress::Ipv6(v) => {
389                    v.to_string()
390                }
391                NextHopAddress::Ipv6LinkLocal(v1, _v2) => {
392                    v1.to_string()
393                }
394            }
395        )
396    }
397}
398
399impl Display for AsPath {
400    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
401        write!(
402            f,
403            "{}",
404            self.segments()
405                .iter()
406                .map(|seg| match seg {
407                    AsPathSegment::AsSequence(v) | AsPathSegment::ConfedSequence(v) =>
408                        v.iter().join(" "),
409                    AsPathSegment::AsSet(v) | AsPathSegment::ConfedSet(v) => {
410                        format!("{{{}}}", v.iter().join(","))
411                    }
412                })
413                .join(" ")
414        )
415    }
416}
417
418///////////////
419// SERIALIZE //
420///////////////
421
422impl Serialize for AsPath {
423    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
424    where
425        S: Serializer,
426    {
427        serializer.serialize_str(self.to_string().as_str())
428    }
429}
430
431impl Serialize for Origin {
432    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
433    where
434        S: Serializer,
435    {
436        serializer.serialize_str(self.to_string().as_str())
437    }
438}
439
440impl Serialize for AtomicAggregate {
441    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
442    where
443        S: Serializer,
444    {
445        serializer.serialize_str(self.to_string().as_str())
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use crate::bgp::attributes::{AsPath, AsPathSegment};
452
453    #[test]
454    fn test_aspath_as4path_merge() {
455        let aspath = AsPath {
456            segments: vec![AsPathSegment::AsSequence(
457                [1, 2, 3, 5].map(|i| i.into()).to_vec(),
458            )],
459        };
460        let as4path = AsPath {
461            segments: vec![AsPathSegment::AsSequence(
462                [2, 3, 7].map(|i| i.into()).to_vec(),
463            )],
464        };
465        let newpath = AsPath::merge_aspath_as4path(&aspath, &as4path).unwrap();
466        assert_eq!(
467            newpath.segments[0],
468            AsPathSegment::AsSequence([1, 2, 3, 7].map(|i| { i.into() }).to_vec())
469        );
470    }
471
472    #[test]
473    fn test_get_origin() {
474        let aspath = AsPath {
475            segments: vec![AsPathSegment::AsSequence(
476                [1, 2, 3, 5].map(|i| i.into()).to_vec(),
477            )],
478        };
479        let origins = aspath.get_origin();
480        assert!(origins.is_some());
481        assert_eq!(origins.unwrap(), vec![5]);
482
483        let aspath = AsPath {
484            segments: vec![
485                AsPathSegment::AsSequence([1, 2, 3, 5].map(|i| i.into()).to_vec()),
486                AsPathSegment::AsSet([7, 8].map(|i| i.into()).to_vec()),
487            ],
488        };
489        let origins = aspath.get_origin();
490        assert!(origins.is_some());
491        assert_eq!(origins.unwrap(), vec![7, 8]);
492    }
493
494    #[test]
495    fn test_aspath_to_vec() {
496        let as4path = AsPath {
497            segments: vec![AsPathSegment::AsSequence(
498                [2, 3, 4].map(|i| i.into()).to_vec(),
499            )],
500        };
501        assert_eq!(as4path.to_u32_vec(), Some(vec![2, 3, 4]));
502
503        let as4path = AsPath {
504            segments: vec![
505                AsPathSegment::AsSequence([2, 3, 4].map(|i| i.into()).to_vec()),
506                AsPathSegment::AsSequence([5, 6, 7].map(|i| i.into()).to_vec()),
507            ],
508        };
509        assert_eq!(as4path.to_u32_vec(), Some(vec![2, 3, 4, 5, 6, 7]));
510
511        let as4path = AsPath {
512            segments: vec![AsPathSegment::AsSet([2, 3, 4].map(|i| i.into()).to_vec())],
513        };
514        assert_eq!(as4path.to_u32_vec(), None);
515    }
516}