bgpkit_parser/models/bgp/
community.rs

1use crate::models::Asn;
2use num_enum::{FromPrimitive, IntoPrimitive};
3use std::fmt::{Display, Formatter};
4use std::net::{Ipv4Addr, Ipv6Addr};
5
6#[derive(Debug, PartialEq, Copy, Clone, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(untagged))]
9pub enum MetaCommunity {
10    Plain(Community),
11    Extended(ExtendedCommunity),
12    Ipv6Extended(Ipv6AddrExtCommunity),
13    Large(LargeCommunity),
14}
15
16#[derive(Debug, PartialEq, Copy, Clone, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub enum Community {
19    NoExport,
20    NoAdvertise,
21    NoExportSubConfed,
22    Custom(Asn, u16),
23}
24
25/// Large community structure as defined in [RFC8092](https://datatracker.ietf.org/doc/html/rfc8092)
26///
27/// ## Display
28///
29/// Large community is displayed as `GLOBAL_ADMINISTRATOR:LOCAL_DATA_1:LOCAL_DATA_2`
30#[derive(Debug, PartialEq, Clone, Copy, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct LargeCommunity {
33    pub global_admin: u32,
34    pub local_data: [u32; 2],
35}
36
37impl LargeCommunity {
38    pub fn new(global_admin: u32, local_data: [u32; 2]) -> LargeCommunity {
39        LargeCommunity {
40            global_admin,
41            local_data,
42        }
43    }
44}
45
46/// Type definitions of extended communities
47#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Copy, Clone)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49#[repr(u8)]
50pub enum ExtendedCommunityType {
51    // transitive types
52    TransitiveTwoOctetAs = 0x00,
53    TransitiveIpv4Addr = 0x01,
54    TransitiveFourOctetAs = 0x02,
55    TransitiveOpaque = 0x03,
56
57    // non-transitive types
58    NonTransitiveTwoOctetAs = 0x40,
59    NonTransitiveIpv4Addr = 0x41,
60    NonTransitiveFourOctetAs = 0x42,
61    NonTransitiveOpaque = 0x43,
62    // the rest are either draft or experimental
63    #[num_enum(catch_all)]
64    Unknown(u8),
65}
66
67/// Extended Communities.
68///
69/// ## Overview  
70///
71/// It is a 8-octet data that has flexible definition based on the types:
72/// <https://datatracker.ietf.org/doc/html/rfc4360>
73///
74/// For more up-to-date definitions, see [IANA' website](https://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml).
75///
76/// ```text
77///    Each Extended Community is encoded as an 8-octet quantity, as
78///    follows:
79///
80///       - Type Field  : 1 or 2 octets
81///       - Value Field : Remaining octets
82///
83///        0                   1                   2                   3
84///        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
85///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
86///       |  Type high    |  Type low(*)  |                               |
87///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+          Value                |
88///       |                                                               |
89///       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
90///
91///       (*) Present for Extended types only, used for the Value field
92///           otherwise.
93/// ```
94#[derive(Debug, PartialEq, Clone, Copy, Eq)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub enum ExtendedCommunity {
97    TransitiveTwoOctetAs(TwoOctetAsExtCommunity),
98    TransitiveIpv4Addr(Ipv4AddrExtCommunity),
99    TransitiveFourOctetAs(FourOctetAsExtCommunity),
100    TransitiveOpaque(OpaqueExtCommunity),
101    NonTransitiveTwoOctetAs(TwoOctetAsExtCommunity),
102    NonTransitiveIpv4Addr(Ipv4AddrExtCommunity),
103    NonTransitiveFourOctetAs(FourOctetAsExtCommunity),
104    NonTransitiveOpaque(OpaqueExtCommunity),
105    Raw([u8; 8]),
106}
107
108impl ExtendedCommunity {
109    pub const fn community_type(&self) -> ExtendedCommunityType {
110        use ExtendedCommunityType::*;
111        match self {
112            ExtendedCommunity::TransitiveTwoOctetAs(_) => TransitiveTwoOctetAs,
113            ExtendedCommunity::TransitiveIpv4Addr(_) => TransitiveIpv4Addr,
114            ExtendedCommunity::TransitiveFourOctetAs(_) => TransitiveFourOctetAs,
115            ExtendedCommunity::TransitiveOpaque(_) => TransitiveOpaque,
116            ExtendedCommunity::NonTransitiveTwoOctetAs(_) => NonTransitiveTwoOctetAs,
117            ExtendedCommunity::NonTransitiveIpv4Addr(_) => NonTransitiveIpv4Addr,
118            ExtendedCommunity::NonTransitiveFourOctetAs(_) => NonTransitiveFourOctetAs,
119            ExtendedCommunity::NonTransitiveOpaque(_) => NonTransitiveOpaque,
120            ExtendedCommunity::Raw(buffer) => Unknown(buffer[0]),
121        }
122    }
123}
124
125#[derive(Debug, PartialEq, Clone, Copy, Eq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127pub struct Ipv6AddrExtCommunity {
128    pub community_type: ExtendedCommunityType,
129    pub subtype: u8,
130    // 16 octets
131    pub global_admin: Ipv6Addr,
132    // 2 octets
133    pub local_admin: [u8; 2],
134}
135
136/// Two-Octet AS Specific Extended Community
137///
138/// <https://datatracker.ietf.org/doc/html/rfc4360#section-3.1>
139#[derive(Debug, PartialEq, Clone, Copy, Eq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141pub struct TwoOctetAsExtCommunity {
142    pub subtype: u8,
143    // 2 octet
144    pub global_admin: Asn,
145    // 4 octet
146    pub local_admin: [u8; 4],
147}
148
149/// Four-Octet AS Specific Extended Community
150///
151/// <https://datatracker.ietf.org/doc/html/rfc5668#section-2>
152#[derive(Debug, PartialEq, Clone, Copy, Eq)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154pub struct FourOctetAsExtCommunity {
155    pub subtype: u8,
156    // 4 octet
157    pub global_admin: Asn,
158    // 2 octet
159    pub local_admin: [u8; 2],
160}
161
162/// IPv4 Address Specific Extended Community
163///
164/// <https://datatracker.ietf.org/doc/html/rfc4360#section-3.2>
165#[derive(Debug, PartialEq, Clone, Copy, Eq)]
166#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
167pub struct Ipv4AddrExtCommunity {
168    pub subtype: u8,
169    // 4 octet
170    pub global_admin: Ipv4Addr,
171    // 2 octet
172    pub local_admin: [u8; 2],
173}
174
175/// Opaque Extended Community
176///
177/// <https://datatracker.ietf.org/doc/html/rfc4360#section-3.3>
178#[derive(Debug, PartialEq, Clone, Copy, Eq)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180pub struct OpaqueExtCommunity {
181    pub subtype: u8,
182    // 6 octet
183    pub value: [u8; 6],
184}
185
186/////////////
187// DISPLAY //
188/////////////
189
190struct ToHexString<'a>(&'a [u8]);
191
192impl Display for ToHexString<'_> {
193    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194        for byte in self.0 {
195            write!(f, "{:02X}", byte)?;
196        }
197        Ok(())
198    }
199}
200
201impl Display for Community {
202    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203        match self {
204            Community::NoExport => write!(f, "no-export"),
205            Community::NoAdvertise => write!(f, "no-advertise"),
206            Community::NoExportSubConfed => write!(f, "no-export-sub-confed"),
207            Community::Custom(asn, value) => write!(f, "{}:{}", asn, value),
208        }
209    }
210}
211
212impl Display for LargeCommunity {
213    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
214        write!(
215            f,
216            "{}:{}:{}",
217            self.global_admin, self.local_data[0], self.local_data[1]
218        )
219    }
220}
221
222impl Display for ExtendedCommunity {
223    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
224        let ec_type = u8::from(self.community_type());
225        match self {
226            ExtendedCommunity::TransitiveTwoOctetAs(ec)
227            | ExtendedCommunity::NonTransitiveTwoOctetAs(ec) => {
228                write!(
229                    f,
230                    "{}:{}:{}:{}",
231                    ec_type,
232                    ec.subtype,
233                    ec.global_admin,
234                    ToHexString(&ec.local_admin)
235                )
236            }
237            ExtendedCommunity::TransitiveIpv4Addr(ec)
238            | ExtendedCommunity::NonTransitiveIpv4Addr(ec) => {
239                write!(
240                    f,
241                    "{}:{}:{}:{}",
242                    ec_type,
243                    ec.subtype,
244                    ec.global_admin,
245                    ToHexString(&ec.local_admin)
246                )
247            }
248            ExtendedCommunity::TransitiveFourOctetAs(ec)
249            | ExtendedCommunity::NonTransitiveFourOctetAs(ec) => {
250                write!(
251                    f,
252                    "{}:{}:{}:{}",
253                    ec_type,
254                    ec.subtype,
255                    ec.global_admin,
256                    ToHexString(&ec.local_admin)
257                )
258            }
259            ExtendedCommunity::TransitiveOpaque(ec)
260            | ExtendedCommunity::NonTransitiveOpaque(ec) => {
261                write!(f, "{}:{}:{}", ec_type, ec.subtype, ToHexString(&ec.value))
262            }
263            ExtendedCommunity::Raw(ec) => {
264                write!(f, "{}", ToHexString(ec))
265            }
266        }
267    }
268}
269
270impl Display for Ipv6AddrExtCommunity {
271    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272        write!(
273            f,
274            "{}:{}:{}:{}",
275            u8::from(self.community_type),
276            self.subtype,
277            self.global_admin,
278            ToHexString(&self.local_admin)
279        )
280    }
281}
282
283impl Display for MetaCommunity {
284    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
285        match self {
286            MetaCommunity::Plain(c) => write!(f, "{}", c),
287            MetaCommunity::Extended(c) => write!(f, "{}", c),
288            MetaCommunity::Large(c) => write!(f, "{}", c),
289            MetaCommunity::Ipv6Extended(c) => write!(f, "{}", c),
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_large_community_new() {
300        let global_admin = 56;
301        let local_data = [3, 4];
302        let large_comm = LargeCommunity::new(global_admin, local_data);
303        assert_eq!(large_comm.global_admin, global_admin);
304        assert_eq!(large_comm.local_data, local_data);
305    }
306
307    #[test]
308    fn test_extended_community_community_type() {
309        let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
310            subtype: 0,
311            global_admin: Asn::new_32bit(0),
312            local_admin: [0; 4],
313        };
314        let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
315        assert_eq!(
316            extended_community.community_type(),
317            ExtendedCommunityType::TransitiveTwoOctetAs
318        );
319    }
320
321    #[test]
322    fn test_display_community() {
323        assert_eq!(format!("{}", Community::NoExport), "no-export");
324        assert_eq!(format!("{}", Community::NoAdvertise), "no-advertise");
325        assert_eq!(
326            format!("{}", Community::NoExportSubConfed),
327            "no-export-sub-confed"
328        );
329        assert_eq!(
330            format!("{}", Community::Custom(Asn::new_32bit(64512), 100)),
331            "64512:100"
332        );
333    }
334
335    #[test]
336    fn test_display_large_community() {
337        let large_community = LargeCommunity::new(1, [2, 3]);
338        assert_eq!(format!("{}", large_community), "1:2:3");
339    }
340
341    #[test]
342    fn test_display_extended_community() {
343        let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
344            subtype: 0,
345            global_admin: Asn::new_32bit(0),
346            local_admin: [0; 4],
347        };
348        let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
349        assert_eq!(format!("{}", extended_community), "0:0:0:00000000");
350
351        let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
352            subtype: 0,
353            global_admin: Asn::new_32bit(0),
354            local_admin: [0; 4],
355        };
356        let extended_community = ExtendedCommunity::NonTransitiveTwoOctetAs(two_octet_as_ext_comm);
357        assert_eq!(format!("{}", extended_community), "64:0:0:00000000");
358
359        let ipv4_ext_comm = Ipv4AddrExtCommunity {
360            subtype: 1,
361            global_admin: "192.168.1.1".parse().unwrap(),
362            local_admin: [5, 6],
363        };
364        let extended_community = ExtendedCommunity::TransitiveIpv4Addr(ipv4_ext_comm);
365        assert_eq!(format!("{}", extended_community), "1:1:192.168.1.1:0506");
366
367        let ipv4_ext_comm = Ipv4AddrExtCommunity {
368            subtype: 1,
369            global_admin: "192.168.1.1".parse().unwrap(),
370            local_admin: [5, 6],
371        };
372        let extended_community = ExtendedCommunity::NonTransitiveIpv4Addr(ipv4_ext_comm);
373        assert_eq!(format!("{}", extended_community), "65:1:192.168.1.1:0506");
374
375        let four_octet_as_ext_comm = FourOctetAsExtCommunity {
376            subtype: 2,
377            global_admin: Asn::new_32bit(64512),
378            local_admin: [7, 8],
379        };
380        let extended_community = ExtendedCommunity::TransitiveFourOctetAs(four_octet_as_ext_comm);
381        assert_eq!(format!("{}", extended_community), "2:2:64512:0708");
382
383        let four_octet_as_ext_comm = FourOctetAsExtCommunity {
384            subtype: 2,
385            global_admin: Asn::new_32bit(64512),
386            local_admin: [7, 8],
387        };
388        let extended_community =
389            ExtendedCommunity::NonTransitiveFourOctetAs(four_octet_as_ext_comm);
390        assert_eq!(format!("{}", extended_community), "66:2:64512:0708");
391
392        let opaque_ext_comm = OpaqueExtCommunity {
393            subtype: 3,
394            value: [9, 10, 11, 12, 13, 14],
395        };
396        let extended_community = ExtendedCommunity::TransitiveOpaque(opaque_ext_comm);
397        assert_eq!(format!("{}", extended_community), "3:3:090A0B0C0D0E");
398
399        let opaque_ext_comm = OpaqueExtCommunity {
400            subtype: 3,
401            value: [9, 10, 11, 12, 13, 14],
402        };
403        let extended_community = ExtendedCommunity::NonTransitiveOpaque(opaque_ext_comm);
404        assert_eq!(format!("{}", extended_community), "67:3:090A0B0C0D0E");
405
406        let raw_ext_comm = [0, 1, 2, 3, 4, 5, 6, 7];
407        let extended_community = ExtendedCommunity::Raw(raw_ext_comm);
408        assert_eq!(format!("{}", extended_community), "0001020304050607");
409    }
410
411    #[test]
412    fn test_display_ipv6_addr_ext_community() {
413        let ipv6_addr_ext_comm = Ipv6AddrExtCommunity {
414            community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
415            subtype: 0,
416            global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
417            local_admin: [0, 1],
418        };
419        assert_eq!(
420            format!("{}", ipv6_addr_ext_comm),
421            "0:0:2001:db8::8a2e:370:7334:0001"
422        );
423    }
424
425    #[test]
426    fn test_display_meta_community() {
427        let large_community = LargeCommunity::new(1, [2, 3]);
428        let meta_community = MetaCommunity::Large(large_community);
429        assert_eq!(format!("{}", meta_community), "1:2:3");
430    }
431
432    #[test]
433    #[cfg(feature = "serde")]
434    fn test_serde() {
435        let meta_community = MetaCommunity::Large(LargeCommunity::new(1, [2, 3]));
436        let serialized = serde_json::to_string(&meta_community).unwrap();
437        let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
438        assert_eq!(meta_community, deserialized);
439
440        let meta_community = MetaCommunity::Extended(ExtendedCommunity::TransitiveTwoOctetAs(
441            TwoOctetAsExtCommunity {
442                subtype: 0,
443                global_admin: Asn::new_32bit(0),
444                local_admin: [0; 4],
445            },
446        ));
447        let serialized = serde_json::to_string(&meta_community).unwrap();
448        let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
449        assert_eq!(meta_community, deserialized);
450
451        let meta_community = MetaCommunity::Plain(Community::NoExport);
452        let serialized = serde_json::to_string(&meta_community).unwrap();
453        let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
454        assert_eq!(meta_community, deserialized);
455
456        let meta_community = MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
457            community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
458            subtype: 0,
459            global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
460            local_admin: [0, 1],
461        });
462        let serialized = serde_json::to_string(&meta_community).unwrap();
463        let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
464        assert_eq!(meta_community, deserialized);
465    }
466}