bgpkit_parser/models/mrt/
mod.rs

1//! MRT message and relevant structs.
2
3pub mod bgp4mp;
4pub mod table_dump;
5pub mod table_dump_v2;
6
7pub use bgp4mp::*;
8use num_enum::{IntoPrimitive, TryFromPrimitive};
9use std::fmt::{Display, Formatter};
10pub use table_dump::*;
11pub use table_dump_v2::*;
12
13/// MrtRecord is a wrapper struct that contains a header and a message.
14///
15/// A MRT record is constructed as the following:
16/// ```text
17///  0                   1                   2                   3
18///  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
19/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20/// |                      Header... (variable)                     |
21/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22/// |                      Message... (variable)
23/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24/// ```
25///
26/// See [CommonHeader] for the content in header, and [MrtMessage] for the
27/// message format.
28#[derive(Debug, PartialEq, Clone, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct MrtRecord {
31    pub common_header: CommonHeader,
32    pub message: MrtMessage,
33}
34
35/// MRT common header.
36///
37/// A CommonHeader ([RFC6396 section 2][header-link]) is constructed as the following:
38/// ```text
39///  0                   1                   2                   3
40///  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
41/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42/// |                           Timestamp                           |
43/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44/// |             Type              |            Subtype            |
45/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
46/// |                             Length                            |
47/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48/// ```
49///
50/// Or with extended timestamp:
51/// ```text
52///  0                   1                   2                   3
53///  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
54/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55/// |                           Timestamp                           |
56/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57/// |             Type              |            Subtype            |
58/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59/// |                             Length                            |
60/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61/// |                      Microsecond Timestamp                    |
62/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63/// ```
64///
65/// The headers include the following:
66/// - timestamp: 32 bits
67/// - entry_type: [EntryType] enum
68/// - entry_subtype: entry subtype
69/// - length: length of the message in octets
70/// - (`ET` type only) microsecond_timestamp: microsecond part of the timestamp.
71///   only applicable to the MRT message type with `_ET` suffix, such as
72///   `BGP4MP_ET`
73///
74/// [header-link]: https://datatracker.ietf.org/doc/html/rfc6396#section-2
75#[derive(Debug, Copy, Clone, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77pub struct CommonHeader {
78    pub timestamp: u32,
79    pub microsecond_timestamp: Option<u32>,
80    pub entry_type: EntryType,
81    pub entry_subtype: u16,
82    pub length: u32,
83}
84
85impl PartialEq for CommonHeader {
86    fn eq(&self, other: &Self) -> bool {
87        self.timestamp == other.timestamp
88            && self.microsecond_timestamp == other.microsecond_timestamp
89            && self.entry_type == other.entry_type
90            && self.entry_subtype == other.entry_subtype
91        // && self.length == other.length
92        // relax the length check as it might be different due to incorrect encoding
93    }
94}
95
96impl Display for CommonHeader {
97    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
98        let ts = match self.microsecond_timestamp {
99            Some(us) => format!("{}.{:06}", self.timestamp, us),
100            None => self.timestamp.to_string(),
101        };
102        write!(
103            f,
104            "MRT|{}|{:?}|{}|{}",
105            ts, self.entry_type, self.entry_subtype, self.length
106        )
107    }
108}
109
110impl Display for MrtRecord {
111    /// Formats the MRT record in a debug-friendly format.
112    ///
113    /// The format is: `MRT|<timestamp>|<type>|<subtype>|<message_summary>`
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        let ts = match self.common_header.microsecond_timestamp {
116            Some(us) => format!("{}.{:06}", self.common_header.timestamp, us),
117            None => self.common_header.timestamp.to_string(),
118        };
119        write!(
120            f,
121            "MRT|{}|{:?}|{}|{}",
122            ts, self.common_header.entry_type, self.common_header.entry_subtype, self.message
123        )
124    }
125}
126
127impl Display for MrtMessage {
128    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129        match self {
130            MrtMessage::TableDumpMessage(msg) => {
131                write!(f, "TABLE_DUMP|{}|{}", msg.prefix, msg.peer_ip)
132            }
133            MrtMessage::TableDumpV2Message(msg) => match msg {
134                TableDumpV2Message::PeerIndexTable(pit) => {
135                    write!(f, "PEER_INDEX_TABLE|{}", pit.id_peer_map.len())
136                }
137                TableDumpV2Message::RibAfi(rib) => {
138                    write!(
139                        f,
140                        "RIB|{:?}|{}|{} entries",
141                        rib.rib_type,
142                        rib.prefix,
143                        rib.rib_entries.len()
144                    )
145                }
146                TableDumpV2Message::RibGeneric(rib) => {
147                    write!(
148                        f,
149                        "RIB_GENERIC|AFI {:?}|SAFI {:?}|{} entries",
150                        rib.afi,
151                        rib.safi,
152                        rib.rib_entries.len()
153                    )
154                }
155                TableDumpV2Message::GeoPeerTable(gpt) => {
156                    write!(f, "GEO_PEER_TABLE|{} peers", gpt.geo_peers.len())
157                }
158            },
159            MrtMessage::Bgp4Mp(bgp4mp) => match bgp4mp {
160                Bgp4MpEnum::StateChange(sc) => {
161                    write!(
162                        f,
163                        "STATE_CHANGE|{}|{}|{:?}->{:?}",
164                        sc.peer_ip, sc.peer_asn, sc.old_state, sc.new_state
165                    )
166                }
167                Bgp4MpEnum::Message(msg) => {
168                    let msg_type = match &msg.bgp_message {
169                        crate::models::BgpMessage::Open(_) => "OPEN",
170                        crate::models::BgpMessage::Update(_) => "UPDATE",
171                        crate::models::BgpMessage::Notification(_) => "NOTIFICATION",
172                        crate::models::BgpMessage::KeepAlive => "KEEPALIVE",
173                    };
174                    write!(f, "BGP4MP|{}|{}|{}", msg.peer_ip, msg.peer_asn, msg_type)
175                }
176            },
177        }
178    }
179}
180
181#[derive(Debug, PartialEq, Clone, Eq)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183pub enum MrtMessage {
184    TableDumpMessage(TableDumpMessage),
185    TableDumpV2Message(TableDumpV2Message),
186    Bgp4Mp(Bgp4MpEnum),
187}
188
189/// MRT entry type.
190///
191/// EntryType indicates the type of the current MRT record. Type 0 to 10 are deprecated.
192///
193/// Excerpt from [RFC6396 section 4](https://datatracker.ietf.org/doc/html/rfc6396#section-4):
194/// ```text
195/// The following MRT Types are currently defined for the MRT format.
196/// The MRT Types that contain the "_ET" suffix in their names identify
197/// those types that use an Extended Timestamp MRT Header.  The Subtype
198/// and Message fields in these types remain as defined for the MRT Types
199/// of the same name without the "_ET" suffix.
200///
201///     11   OSPFv2
202///     12   TABLE_DUMP
203///     13   TABLE_DUMP_V2
204///     16   BGP4MP
205///     17   BGP4MP_ET
206///     32   ISIS
207///     33   ISIS_ET
208///     48   OSPFv3
209///     49   OSPFv3_ET
210/// ```
211#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Eq, Hash)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213#[allow(non_camel_case_types)]
214#[repr(u16)]
215pub enum EntryType {
216    // START DEPRECATED
217    NULL = 0,
218    START = 1,
219    DIE = 2,
220    I_AM_DEAD = 3,
221    PEER_DOWN = 4,
222    BGP = 5,
223    RIP = 6,
224    IDRP = 7,
225    RIPNG = 8,
226    BGP4PLUS = 9,
227    BGP4PLUS_01 = 10,
228    // END DEPRECATED
229    OSPFv2 = 11,
230    TABLE_DUMP = 12,
231    TABLE_DUMP_V2 = 13,
232    BGP4MP = 16,
233    BGP4MP_ET = 17,
234    ISIS = 32,
235    ISIS_ET = 33,
236    OSPFv3 = 48,
237    OSPFv3_ET = 49,
238}
239
240#[cfg(test)]
241mod tests {
242
243    #[test]
244    fn test_mrt_record_display() {
245        use super::*;
246        use crate::models::Asn;
247        use std::net::IpAddr;
248        use std::str::FromStr;
249
250        let mrt_record = MrtRecord {
251            common_header: CommonHeader {
252                timestamp: 1609459200,
253                microsecond_timestamp: None,
254                entry_type: EntryType::BGP4MP,
255                entry_subtype: 0,
256                length: 0,
257            },
258            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
259                msg_type: Bgp4MpType::StateChange,
260                peer_asn: Asn::new_32bit(65000),
261                local_asn: Asn::new_32bit(65001),
262                interface_index: 1,
263                peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
264                local_addr: IpAddr::from_str("10.0.0.2").unwrap(),
265                old_state: BgpState::Idle,
266                new_state: BgpState::Connect,
267            })),
268        };
269
270        let display = format!("{}", mrt_record);
271        assert!(display.contains("1609459200"));
272        assert!(display.contains("BGP4MP"));
273        assert!(display.contains("STATE_CHANGE"));
274        assert!(display.contains("10.0.0.1"));
275        assert!(display.contains("65000"));
276    }
277
278    #[test]
279    fn test_common_header_display() {
280        use super::*;
281
282        let header = CommonHeader {
283            timestamp: 1609459200,
284            microsecond_timestamp: Some(500000),
285            entry_type: EntryType::BGP4MP_ET,
286            entry_subtype: 4,
287            length: 128,
288        };
289
290        let display = format!("{}", header);
291        assert!(display.contains("1609459200.500000"));
292        assert!(display.contains("BGP4MP_ET"));
293        assert!(display.contains("128"));
294    }
295
296    #[test]
297    #[cfg(feature = "serde")]
298    fn test_entry_type_serialize_and_deserialize() {
299        use super::*;
300        let types = vec![
301            EntryType::NULL,
302            EntryType::START,
303            EntryType::DIE,
304            EntryType::I_AM_DEAD,
305            EntryType::PEER_DOWN,
306            EntryType::BGP,
307            EntryType::RIP,
308            EntryType::IDRP,
309            EntryType::RIPNG,
310            EntryType::BGP4PLUS,
311            EntryType::BGP4PLUS_01,
312            EntryType::OSPFv2,
313            EntryType::TABLE_DUMP,
314            EntryType::TABLE_DUMP_V2,
315            EntryType::BGP4MP,
316            EntryType::BGP4MP_ET,
317            EntryType::ISIS,
318            EntryType::ISIS_ET,
319            EntryType::OSPFv3,
320            EntryType::OSPFv3_ET,
321        ];
322
323        for entry_type in types {
324            let serialized = serde_json::to_string(&entry_type).unwrap();
325            let deserialized: EntryType = serde_json::from_str(&serialized).unwrap();
326
327            assert_eq!(entry_type, deserialized);
328        }
329    }
330
331    #[test]
332    #[cfg(feature = "serde")]
333    fn test_serialization() {
334        use super::*;
335        use serde_json;
336        use std::net::IpAddr;
337        use std::str::FromStr;
338
339        let mrt_record = MrtRecord {
340            common_header: CommonHeader {
341                timestamp: 0,
342                microsecond_timestamp: None,
343                entry_type: EntryType::BGP4MP,
344                entry_subtype: 0,
345                length: 0,
346            },
347            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::StateChange(Bgp4MpStateChange {
348                msg_type: Bgp4MpType::StateChange,
349                peer_asn: crate::models::Asn::new_32bit(0),
350                local_asn: crate::models::Asn::new_32bit(0),
351                interface_index: 1,
352                peer_ip: IpAddr::from_str("10.0.0.0").unwrap(),
353                local_addr: IpAddr::from_str("10.0.0.0").unwrap(),
354                old_state: BgpState::Idle,
355                new_state: BgpState::Connect,
356            })),
357        };
358
359        let serialized = serde_json::to_string(&mrt_record).unwrap();
360        let deserialized: MrtRecord = serde_json::from_str(&serialized).unwrap();
361        assert_eq!(mrt_record, deserialized);
362    }
363}