bgpkit_parser/models/mrt/
table_dump_v2.rs

1//! MRT table dump version 2 structs
2use crate::models::*;
3use bitflags::bitflags;
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5use std::collections::HashMap;
6use std::net::{IpAddr, Ipv4Addr};
7use std::str::FromStr;
8
9/// TableDump message version 2 enum
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum TableDumpV2Message {
13    PeerIndexTable(PeerIndexTable),
14    RibAfi(RibAfiEntries),
15    /// Currently unsupported
16    RibGeneric(RibGenericEntries),
17    /// RFC 6397: Geo-location peer table
18    GeoPeerTable(GeoPeerTable),
19}
20
21impl TableDumpV2Message {
22    pub const fn dump_type(&self) -> TableDumpV2Type {
23        match self {
24            TableDumpV2Message::PeerIndexTable(_) => TableDumpV2Type::PeerIndexTable,
25            TableDumpV2Message::RibAfi(x) => x.rib_type,
26            TableDumpV2Message::RibGeneric(_) => TableDumpV2Type::RibGeneric,
27            TableDumpV2Message::GeoPeerTable(_) => TableDumpV2Type::GeoPeerTable,
28        }
29    }
30}
31
32/// TableDump version 2 subtypes.
33///
34/// <https://www.iana.org/assignments/mrt/mrt.xhtml#subtype-codes>
35#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[repr(u16)]
38pub enum TableDumpV2Type {
39    PeerIndexTable = 1,
40    RibIpv4Unicast = 2,
41    RibIpv4Multicast = 3,
42    RibIpv6Unicast = 4,
43    RibIpv6Multicast = 5,
44    RibGeneric = 6,
45    GeoPeerTable = 7,
46    RibIpv4UnicastAddPath = 8,
47    RibIpv4MulticastAddPath = 9,
48    RibIpv6UnicastAddPath = 10,
49    RibIpv6MulticastAddPath = 11,
50    RibGenericAddPath = 12,
51}
52
53/// AFI/SAFI-Specific RIB Subtypes.
54///
55/// ```text
56///    The AFI/SAFI-specific RIB Subtypes consist of the RIB_IPV4_UNICAST,
57///    RIB_IPV4_MULTICAST, RIB_IPV6_UNICAST, and RIB_IPV6_MULTICAST
58///    Subtypes.  These specific RIB table entries are given their own MRT
59///    TABLE_DUMP_V2 subtypes as they are the most common type of RIB table
60///    instances, and providing specific MRT subtypes for them permits more
61///    compact encodings.  These subtypes permit a single MRT record to
62///    encode multiple RIB table entries for a single prefix.  The Prefix
63///    Length and Prefix fields are encoded in the same manner as the BGP
64///    NLRI encoding for IPv4 and IPv6 prefixes.  Namely, the Prefix field
65///    contains address prefixes followed by enough trailing bits to make
66///    the end of the field fall on an octet boundary.  The value of
67///    trailing bits is irrelevant.
68///
69///         0                   1                   2                   3
70///         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
71///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72///        |                         Sequence Number                       |
73///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74///        | Prefix Length |
75///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76///        |                        Prefix (variable)                      |
77///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78///        |         Entry Count           |  RIB Entries (variable)
79///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80/// ```
81#[derive(Debug, Clone, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct RibAfiEntries {
84    pub rib_type: TableDumpV2Type,
85    pub sequence_number: u32,
86    pub prefix: NetworkPrefix,
87    pub rib_entries: Vec<RibEntry>,
88}
89
90/// RIB generic entries subtype.
91///
92/// ```text
93/// The RIB_GENERIC header is shown below.  It is used to cover RIB
94/// entries that do not fall under the common case entries defined above.
95/// It consists of an AFI, Subsequent AFI (SAFI), and a single NLRI
96/// entry.  The NLRI information is specific to the AFI and SAFI values.
97/// An implementation that does not recognize particular AFI and SAFI
98/// values SHOULD discard the remainder of the MRT record.
99///         0                   1                   2                   3
100///         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
101///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
102///        |                         Sequence Number                       |
103///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104///        |    Address Family Identifier  |Subsequent AFI |
105///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
106///        |     Network Layer Reachability Information (variable)         |
107///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108///        |         Entry Count           |  RIB Entries (variable)
109///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
110/// ```
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct RibGenericEntries {
114    pub sequence_number: u32,
115    pub afi: Afi,
116    pub safi: Safi,
117    pub nlri: NetworkPrefix,
118    pub rib_entries: Vec<RibEntry>,
119}
120
121/// RIB entry.
122///
123/// ```text
124///    The RIB Entries are repeated Entry Count times.  These entries share
125///    a common format as shown below.  They include a Peer Index from the
126///    PEER_INDEX_TABLE MRT record, an originated time for the RIB Entry,
127///    and the BGP path attribute length and attributes.  All AS numbers in
128///    the AS_PATH attribute MUST be encoded as 4-byte AS numbers.
129///
130///         0                   1                   2                   3
131///         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
132///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
133///        |         Peer Index            |
134///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135///        |                         Originated Time                       |
136///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
137///        |                         Optional Path ID                      |
138///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
139///        |      Attribute Length         |
140///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
141///        |                    BGP Attributes... (variable)
142///        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
143/// ```
144#[derive(Debug, Clone, PartialEq, Eq)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct RibEntry {
147    pub peer_index: u16,
148    pub originated_time: u32,
149    pub path_id: Option<u32>,
150    pub attributes: Attributes,
151}
152
153/// peer index table.
154///
155/// ```text
156///    An initial PEER_INDEX_TABLE MRT record provides the BGP ID of the
157///    collector, an OPTIONAL view name, and a list of indexed peers.
158///    Following the PEER_INDEX_TABLE MRT record, a series of MRT records is
159///    used to encode RIB table entries.  This series of MRT records uses
160///    subtypes 2-6 and is separate from the PEER_INDEX_TABLE MRT record
161///    itself and includes full MRT record headers.  The RIB entry MRT
162///    records MUST immediately follow the PEER_INDEX_TABLE MRT record.
163/// ```
164#[derive(Debug, Clone, PartialEq, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct PeerIndexTable {
167    pub collector_bgp_id: BgpIdentifier,
168    pub view_name: String,
169    pub id_peer_map: HashMap<u16, Peer>,
170    pub peer_ip_id_map: HashMap<IpAddr, u16>,
171}
172
173impl Default for PeerIndexTable {
174    fn default() -> Self {
175        PeerIndexTable {
176            collector_bgp_id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
177            view_name: "".to_string(),
178            id_peer_map: HashMap::new(),
179            peer_ip_id_map: HashMap::new(),
180        }
181    }
182}
183
184bitflags! {
185    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
186    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187    pub struct PeerType: u8 {
188        const AS_SIZE_32BIT = 0x2;
189        const ADDRESS_FAMILY_IPV6 = 0x1;
190    }
191}
192
193/// Geo-location peer entry - RFC 6397
194#[derive(Debug, Clone, Copy)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct GeoPeer {
197    pub peer: Peer,
198    pub peer_latitude: f32,
199    pub peer_longitude: f32,
200}
201
202impl PartialEq for GeoPeer {
203    fn eq(&self, other: &Self) -> bool {
204        self.peer == other.peer
205            && self.peer_latitude.to_bits() == other.peer_latitude.to_bits()
206            && self.peer_longitude.to_bits() == other.peer_longitude.to_bits()
207    }
208}
209
210impl Eq for GeoPeer {}
211
212impl GeoPeer {
213    pub fn new(peer: Peer, latitude: f32, longitude: f32) -> Self {
214        Self {
215            peer,
216            peer_latitude: latitude,
217            peer_longitude: longitude,
218        }
219    }
220}
221
222/// RFC 6397: Geo-location peer table
223#[derive(Debug, Clone)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub struct GeoPeerTable {
226    pub collector_bgp_id: BgpIdentifier,
227    pub view_name: String,
228    pub collector_latitude: f32,
229    pub collector_longitude: f32,
230    pub geo_peers: Vec<GeoPeer>,
231}
232
233impl PartialEq for GeoPeerTable {
234    fn eq(&self, other: &Self) -> bool {
235        self.collector_bgp_id == other.collector_bgp_id
236            && self.view_name == other.view_name
237            && self.collector_latitude.to_bits() == other.collector_latitude.to_bits()
238            && self.collector_longitude.to_bits() == other.collector_longitude.to_bits()
239            && self.geo_peers == other.geo_peers
240    }
241}
242
243impl Eq for GeoPeerTable {}
244
245impl GeoPeerTable {
246    pub fn new(
247        collector_bgp_id: BgpIdentifier,
248        view_name: String,
249        collector_latitude: f32,
250        collector_longitude: f32,
251    ) -> Self {
252        Self {
253            collector_bgp_id,
254            view_name,
255            collector_latitude,
256            collector_longitude,
257            geo_peers: Vec::new(),
258        }
259    }
260
261    pub fn add_geo_peer(&mut self, geo_peer: GeoPeer) {
262        self.geo_peers.push(geo_peer);
263    }
264}
265
266/// Peer struct.
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
269pub struct Peer {
270    pub peer_type: PeerType,
271    pub peer_bgp_id: BgpIdentifier,
272    pub peer_ip: IpAddr,
273    pub peer_asn: Asn,
274}
275
276impl Peer {
277    pub fn new(peer_bgp_id: BgpIdentifier, peer_ip: IpAddr, peer_asn: Asn) -> Self {
278        let mut peer_type = PeerType::empty();
279
280        if peer_asn.is_four_byte() {
281            peer_type.insert(PeerType::AS_SIZE_32BIT);
282        }
283
284        if peer_ip.is_ipv6() {
285            peer_type.insert(PeerType::ADDRESS_FAMILY_IPV6);
286        }
287
288        Peer {
289            peer_type,
290            peer_bgp_id,
291            peer_ip,
292            peer_asn,
293        }
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    // Create a helper function to initialize Peer structure
302    fn create_peer() -> Peer {
303        let bgp_id = Ipv4Addr::from_str("1.1.1.1").unwrap();
304        let peer_ip: IpAddr = Ipv4Addr::from_str("2.2.2.2").unwrap().into();
305        // Assuming Asn::new(u32) is defined.
306        let asn = Asn::new_32bit(65000);
307        Peer::new(bgp_id, peer_ip, asn)
308    }
309
310    #[test]
311    fn test_peer_new() {
312        let peer = create_peer();
313        assert_eq!(peer.peer_type, PeerType::AS_SIZE_32BIT);
314        assert_eq!(peer.peer_bgp_id, Ipv4Addr::from_str("1.1.1.1").unwrap());
315        assert_eq!(
316            peer.peer_ip,
317            IpAddr::V4(Ipv4Addr::from_str("2.2.2.2").unwrap())
318        );
319        assert_eq!(peer.peer_asn, Asn::new_32bit(65000));
320    }
321
322    #[test]
323    fn test_peer_new_variations() {
324        // Test IPv4 peer with 16-bit AS (neither flag set)
325        let peer_ipv4_16bit = Peer::new(
326            Ipv4Addr::from_str("10.0.0.1").unwrap(),
327            IpAddr::V4(Ipv4Addr::from_str("10.0.0.2").unwrap()),
328            Asn::new_16bit(65001),
329        );
330        assert_eq!(peer_ipv4_16bit.peer_type, PeerType::empty());
331
332        // Test IPv6 peer with 16-bit AS (only IPv6 flag set)
333        let peer_ipv6_16bit = Peer::new(
334            Ipv4Addr::from_str("10.0.0.1").unwrap(),
335            IpAddr::V6(std::net::Ipv6Addr::from_str("2001:db8::1").unwrap()),
336            Asn::new_16bit(65002),
337        );
338        assert_eq!(peer_ipv6_16bit.peer_type, PeerType::ADDRESS_FAMILY_IPV6);
339
340        // Test IPv6 peer with 32-bit AS (both flags set)
341        let peer_ipv6_32bit = Peer::new(
342            Ipv4Addr::from_str("10.0.0.1").unwrap(),
343            IpAddr::V6(std::net::Ipv6Addr::from_str("2001:db8::2").unwrap()),
344            Asn::new_32bit(65003),
345        );
346        assert_eq!(
347            peer_ipv6_32bit.peer_type,
348            PeerType::AS_SIZE_32BIT | PeerType::ADDRESS_FAMILY_IPV6
349        );
350    }
351
352    #[test]
353    fn test_default_peer_index_table() {
354        let peer_index_table = PeerIndexTable::default();
355        assert_eq!(
356            peer_index_table.collector_bgp_id,
357            Ipv4Addr::from_str("0.0.0.0").unwrap()
358        );
359        assert_eq!(peer_index_table.view_name, "".to_string());
360        assert_eq!(peer_index_table.id_peer_map, HashMap::new());
361        assert_eq!(peer_index_table.peer_ip_id_map, HashMap::new());
362    }
363
364    #[test]
365    fn test_peer_type_flags() {
366        let mut peer_type = PeerType::empty();
367        assert_eq!(peer_type, PeerType::empty());
368
369        peer_type.insert(PeerType::AS_SIZE_32BIT);
370        assert_eq!(peer_type, PeerType::AS_SIZE_32BIT);
371
372        peer_type.insert(PeerType::ADDRESS_FAMILY_IPV6);
373        assert_eq!(
374            peer_type,
375            PeerType::AS_SIZE_32BIT | PeerType::ADDRESS_FAMILY_IPV6
376        );
377
378        peer_type.remove(PeerType::AS_SIZE_32BIT);
379        assert_eq!(peer_type, PeerType::ADDRESS_FAMILY_IPV6);
380
381        peer_type.remove(PeerType::ADDRESS_FAMILY_IPV6);
382        assert_eq!(peer_type, PeerType::empty());
383    }
384
385    #[test]
386    fn test_dump_type() {
387        let peer_index_table = TableDumpV2Message::PeerIndexTable(PeerIndexTable::default());
388        assert_eq!(
389            peer_index_table.dump_type(),
390            TableDumpV2Type::PeerIndexTable
391        );
392
393        let rib_afi = TableDumpV2Message::RibAfi(RibAfiEntries {
394            rib_type: TableDumpV2Type::RibIpv4Unicast,
395            sequence_number: 1,
396            prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
397            rib_entries: vec![],
398        });
399        assert_eq!(rib_afi.dump_type(), TableDumpV2Type::RibIpv4Unicast);
400
401        let rib_generic = TableDumpV2Message::RibGeneric(RibGenericEntries {
402            sequence_number: 1,
403            afi: Afi::Ipv4,
404            safi: Safi::Unicast,
405            nlri: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
406            rib_entries: vec![],
407        });
408        assert_eq!(rib_generic.dump_type(), TableDumpV2Type::RibGeneric);
409    }
410
411    #[test]
412    #[cfg(feature = "serde")]
413    fn test_serialization() {
414        let peer_index_table = TableDumpV2Message::PeerIndexTable(PeerIndexTable::default());
415        let serialized = serde_json::to_string(&peer_index_table).unwrap();
416        let deserialized: TableDumpV2Message = serde_json::from_str(&serialized).unwrap();
417        assert_eq!(deserialized, peer_index_table);
418
419        let rib_entry = RibEntry {
420            peer_index: 1,
421            originated_time: 1,
422            path_id: None,
423            attributes: Attributes::default(),
424        };
425        let rib_afi = TableDumpV2Message::RibAfi(RibAfiEntries {
426            rib_type: TableDumpV2Type::RibIpv4Unicast,
427            sequence_number: 1,
428            prefix: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
429            rib_entries: vec![rib_entry],
430        });
431        let serialized = serde_json::to_string(&rib_afi).unwrap();
432        let deserialized: TableDumpV2Message = serde_json::from_str(&serialized).unwrap();
433        assert_eq!(deserialized, rib_afi);
434
435        let rib_generic = TableDumpV2Message::RibGeneric(RibGenericEntries {
436            sequence_number: 1,
437            afi: Afi::Ipv4,
438            safi: Safi::Unicast,
439            nlri: NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
440            rib_entries: vec![],
441        });
442        let serialized = serde_json::to_string(&rib_generic).unwrap();
443        let deserialized: TableDumpV2Message = serde_json::from_str(&serialized).unwrap();
444        assert_eq!(deserialized, rib_generic);
445    }
446
447    #[test]
448    fn test_geo_peer() {
449        let peer = create_peer();
450        let geo_peer = GeoPeer::new(peer, 40.7128, -74.0060);
451
452        assert_eq!(geo_peer.peer, peer);
453        assert_eq!(geo_peer.peer_latitude, 40.7128);
454        assert_eq!(geo_peer.peer_longitude, -74.0060);
455
456        // Test with NaN coordinates
457        let private_geo_peer = GeoPeer::new(peer, f32::NAN, f32::NAN);
458        assert!(private_geo_peer.peer_latitude.is_nan());
459        assert!(private_geo_peer.peer_longitude.is_nan());
460    }
461
462    #[test]
463    fn test_geo_peer_table() {
464        let collector_bgp_id = Ipv4Addr::from_str("10.0.0.1").unwrap();
465        let mut geo_table = GeoPeerTable::new(
466            collector_bgp_id,
467            "test-view".to_string(),
468            51.5074, // London latitude
469            -0.1278, // London longitude
470        );
471
472        assert!(!geo_table.collector_latitude.is_nan());
473        assert!(!geo_table.collector_longitude.is_nan());
474        assert_eq!(geo_table.collector_latitude, 51.5074);
475        assert_eq!(geo_table.collector_longitude, -0.1278);
476
477        // Add a peer with valid location
478        let peer1 = create_peer();
479        let geo_peer1 = GeoPeer::new(peer1, 40.7128, -74.0060); // New York
480        geo_table.add_geo_peer(geo_peer1);
481
482        // Add a peer with private location (NaN)
483        let peer2 = Peer::new(
484            Ipv4Addr::from_str("2.2.2.2").unwrap(),
485            Ipv4Addr::from_str("3.3.3.3").unwrap().into(),
486            Asn::new_32bit(65001),
487        );
488        let geo_peer2 = GeoPeer::new(peer2, f32::NAN, f32::NAN);
489        geo_table.add_geo_peer(geo_peer2);
490
491        assert_eq!(geo_table.geo_peers.len(), 2);
492
493        // Check that first peer has valid location, second doesn't
494        assert!(!geo_table.geo_peers[0].peer_latitude.is_nan());
495        assert!(!geo_table.geo_peers[0].peer_longitude.is_nan());
496        assert!(geo_table.geo_peers[1].peer_latitude.is_nan());
497        assert!(geo_table.geo_peers[1].peer_longitude.is_nan());
498
499        // Test with private collector location
500        let private_geo_table = GeoPeerTable::new(
501            collector_bgp_id,
502            "private-view".to_string(),
503            f32::NAN,
504            f32::NAN,
505        );
506        assert!(private_geo_table.collector_latitude.is_nan());
507        assert!(private_geo_table.collector_longitude.is_nan());
508    }
509}