bgpkit_parser/parser/bmp/messages/
headers.rs

1use crate::models::*;
2use crate::parser::bmp::error::ParserBmpError;
3use crate::parser::ReadUtils;
4use bitflags::bitflags;
5use bytes::{Buf, Bytes};
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7use std::convert::TryFrom;
8use std::hash::{Hash, Hasher};
9use std::net::{IpAddr, Ipv4Addr};
10
11/// BMP message type enum.
12///
13/// ```text
14///    o  Message Type (1 byte): This identifies the type of the BMP
15///       message.  A BMP implementation MUST ignore unrecognized message
16///       types upon receipt.
17///
18///       *  Type = 0: Route Monitoring
19///       *  Type = 1: Statistics Report
20///       *  Type = 2: Peer Down Notification
21///       *  Type = 3: Peer Up Notification
22///       *  Type = 4: Initiation Message
23///       *  Type = 5: Termination Message
24///       *  Type = 6: Route Mirroring Message
25/// ```
26#[derive(Debug, Clone, TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Copy)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[repr(u8)]
29pub enum BmpMsgType {
30    RouteMonitoring = 0,
31    StatisticsReport = 1,
32    PeerDownNotification = 2,
33    PeerUpNotification = 3,
34    InitiationMessage = 4,
35    TerminationMessage = 5,
36    RouteMirroringMessage = 6,
37}
38
39/// BMP Common Header
40///
41/// <https://www.rfc-editor.org/rfc/rfc7854#section-4.1>
42/// ```text
43///       0                   1                   2                   3
44///       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
45///      +-+-+-+-+-+-+-+-+
46///      |    Version    |
47///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48///      |                        Message Length                         |
49///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50///      |   Msg. Type   |
51///      +---------------+
52/// ```
53#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct BmpCommonHeader {
56    pub version: u8,
57    pub msg_len: u32,
58    pub msg_type: BmpMsgType,
59}
60
61pub fn parse_bmp_common_header(data: &mut Bytes) -> Result<BmpCommonHeader, ParserBmpError> {
62    let version = data.read_u8()?;
63    if version != 3 {
64        // has to be 3 per rfc7854
65        return Err(ParserBmpError::CorruptedBmpMessage);
66    }
67
68    let msg_len = data.read_u32()?;
69
70    let msg_type = BmpMsgType::try_from(data.read_u8()?)?;
71    Ok(BmpCommonHeader {
72        version,
73        msg_len,
74        msg_type,
75    })
76}
77
78/// BMP Per-peer Header
79///
80/// Features:
81/// * 42 bytes total size
82/// * Hash and PartialEq implemented without considering the timestamp
83///   * i.e., two headers are equal if all fields except the timestamp are equal
84/// * implements Copy and Clone
85///
86/// <https://www.rfc-editor.org/rfc/rfc7854#section-4.2>
87///
88/// ```text
89///       0                   1                   2                   3
90///       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
91///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
92///      |   Peer Type   |  Peer Flags   |
93///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
94///      |         Peer Distinguisher (present based on peer type)       |
95///      |                                                               |
96///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97///      |                 Peer Address (16 bytes)                       |
98///      ~                                                               ~
99///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
100///      |                           Peer AS                             |
101///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
102///      |                         Peer BGP ID                           |
103///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
104///      |                    Timestamp (seconds)                        |
105///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
106///      |                  Timestamp (microseconds)                     |
107///      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
108/// ```
109#[derive(Debug, Copy, Clone)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111pub struct BmpPerPeerHeader {
112    pub peer_type: BmpPeerType,
113    pub peer_flags: PerPeerFlags,
114    pub peer_distinguisher: u64,
115    pub peer_ip: IpAddr,
116    pub peer_asn: Asn,
117    pub peer_bgp_id: BgpIdentifier,
118    pub timestamp: f64,
119}
120
121impl Default for BmpPerPeerHeader {
122    fn default() -> Self {
123        BmpPerPeerHeader {
124            peer_type: BmpPeerType::Global,
125            peer_flags: PerPeerFlags::PeerFlags(PeerFlags::empty()),
126            peer_distinguisher: 0,
127            peer_ip: IpAddr::V4(Ipv4Addr::from(0)),
128            peer_asn: Default::default(),
129            peer_bgp_id: Ipv4Addr::from(0),
130            timestamp: 0.0,
131        }
132    }
133}
134
135impl PartialEq for BmpPerPeerHeader {
136    fn eq(&self, other: &Self) -> bool {
137        self.peer_type == other.peer_type
138            && self.peer_flags == other.peer_flags
139            && self.peer_distinguisher == other.peer_distinguisher
140            && self.peer_ip == other.peer_ip
141            && self.peer_asn == other.peer_asn
142            && self.peer_bgp_id == other.peer_bgp_id
143            && self.timestamp == other.timestamp
144    }
145}
146
147impl Eq for BmpPerPeerHeader {}
148
149impl Hash for BmpPerPeerHeader {
150    fn hash<H: Hasher>(&self, state: &mut H) {
151        self.peer_type.hash(state);
152        self.peer_flags.hash(state);
153        self.peer_distinguisher.hash(state);
154        self.peer_ip.hash(state);
155        self.peer_asn.hash(state);
156        self.peer_bgp_id.hash(state);
157        self.timestamp.to_bits().hash(state);
158    }
159}
160
161impl BmpPerPeerHeader {
162    /// Returns the AFI of the peer IP address
163    #[inline]
164    pub fn afi(&self) -> Afi {
165        Afi::from(self.peer_ip)
166    }
167
168    /// Strip the timestamp from the header.
169    ///
170    /// This is useful when comparing two headers where the timestamp is not important.
171    pub fn strip_timestamp(&self) -> BmpPerPeerHeader {
172        BmpPerPeerHeader {
173            timestamp: 0.0,
174            ..*self
175        }
176    }
177
178    /// Returns the ASN length based on the peer flags
179    pub fn asn_length(&self) -> AsnLength {
180        match self.peer_flags {
181            PerPeerFlags::PeerFlags(f) => f.asn_length(),
182            PerPeerFlags::LocalRibPeerFlags(_) => AsnLength::Bits32,
183        }
184    }
185}
186
187/// Peer type
188///
189/// - RFC7854: https://datatracker.ietf.org/doc/html/rfc7854#section-4.2
190/// - RFC9069: https://datatracker.ietf.org/doc/html/rfc9069
191#[derive(Debug, Copy, TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Clone)]
192#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
193#[repr(u8)]
194pub enum BmpPeerType {
195    Global = 0,
196    RD = 1,
197    Local = 2,
198    LocalRib = 3,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub enum PerPeerFlags {
204    PeerFlags(PeerFlags),
205    LocalRibPeerFlags(LocalRibPeerFlags),
206}
207
208bitflags! {
209    /// BMP per-peer header flags
210    ///
211    /// RFC section at
212    /// - RFC 7854: https://www.rfc-editor.org/rfc/rfc7854#section-4.2.
213    /// - RFC 8671: https://www.rfc-editor.org/rfc/rfc8671#section-4
214    ///
215    /// RFC 8671 extended the flags definition by adding one additional flag to indicate whenther
216    /// the messages are Adj-RIB-in or Adj-RIB-out.
217    ///
218    /// ```text
219    ///  0 1 2 3 4 5 6 7
220    /// +-+-+-+-+-+-+-+-+
221    /// |V|L|A|O| Resv  |
222    /// +-+-+-+-+-+-+-+-+
223    /// ```
224    /// When the O flag is set to 1, the following fields in the per-peer header are redefined:
225    /// - Peer Address: The remote IP address associated with the TCP session over which the encapsulated Protocol Data Unit (PDU) is sent.
226    /// - Peer AS: The Autonomous System number of the peer to which the encapsulated PDU is sent.
227    /// - Peer BGP ID: The BGP Identifier of the peer to which the encapsulated PDU is sent.
228    /// - Timestamp: The time when the encapsulated routes were advertised (one may also think of
229    ///   this as the time when they were installed in the Adj-RIB-Out), expressed in seconds and
230    ///   microseconds since midnight (zero hour), January 1, 1970 (UTC). If zero, the time is
231    ///   unavailable. Precision of the timestamp is implementation-dependent.
232    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
233    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
234    pub struct PeerFlags: u8 {
235        const ADDRESS_FAMILY_IPV6 = 0b1000_0000;
236        const IS_POST_POLICY = 0b0100_0000;
237        const AS_SIZE_16BIT = 0b0010_0000;
238        const IS_ADJ_RIB_OUT = 0b0001_0000;
239    }
240}
241
242impl PeerFlags {
243    /// Returns the address family for the `Peer` object.
244    ///
245    /// # Returns
246    /// - `Afi::Ipv6` if the `PeerFlags` contains the `ADDRESS_FAMILY_IPV6` flag.
247    /// - `Afi::Ipv4` otherwise.
248    pub const fn address_family(&self) -> Afi {
249        if self.contains(PeerFlags::ADDRESS_FAMILY_IPV6) {
250            return Afi::Ipv6;
251        }
252
253        Afi::Ipv4
254    }
255
256    /// Determines the length of the ASN (Abstract Syntax Notation) based on peer flags.
257    ///
258    /// # Returns
259    ///
260    /// - `AsnLength::Bits16` if the `PeerFlags` contains the `AS_SIZE_16BIT` flag.
261    /// - `AsnLength::Bits32` otherwise.
262    pub const fn asn_length(&self) -> AsnLength {
263        if self.contains(PeerFlags::AS_SIZE_16BIT) {
264            return AsnLength::Bits16;
265        }
266
267        AsnLength::Bits32
268    }
269
270    /// Returns true if the peer streams Adj-RIB-out BMP messages
271    pub const fn is_adj_rib_out(&self) -> bool {
272        self.contains(PeerFlags::IS_ADJ_RIB_OUT)
273    }
274
275    /// Returns true if the peer streams post-policy BMP messages
276    pub const fn is_post_policy(&self) -> bool {
277        self.contains(PeerFlags::IS_POST_POLICY)
278    }
279}
280
281bitflags! {
282    /// BMP local RIB per-peer header flags
283    ///
284    /// RFC section at
285    /// - RFC 9069: https://datatracker.ietf.org/doc/html/rfc9069#section-4.2
286    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
287    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288    pub struct LocalRibPeerFlags: u8 {
289        const IS_FILTERED = 0b1000_0000;
290    }
291}
292
293impl LocalRibPeerFlags {
294    pub const fn is_filtered(&self) -> bool {
295        self.contains(LocalRibPeerFlags::IS_FILTERED)
296    }
297}
298
299/// Parses a BMP per-peer header from the provided byte data.
300///
301/// # Arguments
302///
303/// * `data` - A mutable reference to the byte data representing the per-peer header.
304///
305/// # Returns
306///
307/// * `Ok(BmpPerPeerHeader)` - If the parsing is successful, returns the parsed per-peer header.
308/// * `Err(ParserBmpError)` - If an error occurs during parsing, returns the corresponding error.
309///
310pub fn parse_per_peer_header(data: &mut Bytes) -> Result<BmpPerPeerHeader, ParserBmpError> {
311    let peer_type = BmpPeerType::try_from(data.read_u8()?)?;
312
313    match peer_type {
314        BmpPeerType::Global | BmpPeerType::RD | BmpPeerType::Local => {
315            let peer_flags = PeerFlags::from_bits_retain(data.read_u8()?);
316
317            let peer_distinguisher = data.read_u64()?;
318            let peer_ip = match peer_flags.address_family() {
319                Afi::Ipv4 => {
320                    data.advance(12);
321                    IpAddr::V4(data.read_ipv4_address()?)
322                }
323                Afi::Ipv6 => IpAddr::V6(data.read_ipv6_address()?),
324            };
325
326            let peer_asn = match peer_flags.asn_length() {
327                AsnLength::Bits16 => {
328                    data.advance(2);
329                    Asn::new_16bit(data.read_u16()?)
330                }
331                AsnLength::Bits32 => Asn::new_32bit(data.read_u32()?),
332            };
333
334            let peer_bgp_id = data.read_ipv4_address()?;
335
336            let t_sec = data.read_u32()?;
337            let t_usec = data.read_u32()?;
338            let timestamp = t_sec as f64 + (t_usec as f64) / 1_000_000.0;
339
340            Ok(BmpPerPeerHeader {
341                peer_type,
342                peer_flags: PerPeerFlags::PeerFlags(peer_flags),
343                peer_distinguisher,
344                peer_ip,
345                peer_asn,
346                peer_bgp_id,
347                timestamp,
348            })
349        }
350        BmpPeerType::LocalRib => {
351            let local_rib_peer_flags = LocalRibPeerFlags::from_bits_retain(data.read_u8()?);
352
353            let peer_distinguisher = data.read_u64()?;
354            // zero-filled peer_ip address field
355            let peer_ip = IpAddr::V4(Ipv4Addr::from(0));
356            data.advance(16);
357
358            let peer_asn = Asn::new_32bit(data.read_u32()?);
359
360            let peer_bgp_id = data.read_ipv4_address()?;
361
362            let t_sec = data.read_u32()?;
363            let t_usec = data.read_u32()?;
364            let timestamp = t_sec as f64 + (t_usec as f64) / 1_000_000.0;
365
366            Ok(BmpPerPeerHeader {
367                peer_type,
368                peer_flags: PerPeerFlags::LocalRibPeerFlags(local_rib_peer_flags),
369                peer_distinguisher,
370                peer_ip,
371                peer_asn,
372                peer_bgp_id,
373                timestamp,
374            })
375        }
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_header_error() {
385        let mut data = Bytes::from(vec![0, 0, 0, 0, 0]);
386        assert!(parse_bmp_common_header(&mut data).is_err(),);
387        assert_eq!(
388            parse_bmp_common_header(&mut data).unwrap_err(),
389            ParserBmpError::CorruptedBmpMessage
390        );
391    }
392
393    #[test]
394    fn test_bmp_per_peer_header_basics() {
395        // test AFI checking
396        let per_peer_header = BmpPerPeerHeader {
397            peer_type: BmpPeerType::Global,
398            peer_flags: PerPeerFlags::LocalRibPeerFlags(LocalRibPeerFlags::empty()),
399            peer_distinguisher: 0,
400            peer_ip: IpAddr::V4(Ipv4Addr::from(0)),
401            peer_asn: Default::default(),
402            peer_bgp_id: Ipv4Addr::from(0),
403            timestamp: 0.0,
404        };
405        assert_eq!(per_peer_header.afi(), Afi::Ipv4);
406
407        // check ASN length
408        assert_eq!(per_peer_header.asn_length(), AsnLength::Bits32);
409    }
410
411    #[test]
412    fn test_peer_flags() {
413        let mut flags = PeerFlags::empty();
414        assert_eq!(flags.address_family(), Afi::Ipv4);
415        assert_eq!(flags.asn_length(), AsnLength::Bits32);
416        assert!(!flags.is_adj_rib_out());
417        assert!(!flags.is_post_policy());
418
419        flags |= PeerFlags::ADDRESS_FAMILY_IPV6;
420        assert_eq!(flags.address_family(), Afi::Ipv6);
421
422        flags |= PeerFlags::AS_SIZE_16BIT;
423        assert_eq!(flags.asn_length(), AsnLength::Bits16);
424
425        flags |= PeerFlags::IS_ADJ_RIB_OUT;
426        assert!(flags.is_adj_rib_out());
427
428        flags |= PeerFlags::IS_POST_POLICY;
429        assert!(flags.is_post_policy());
430    }
431
432    #[test]
433    fn test_local_rib_peer_flags() {
434        let mut flags = LocalRibPeerFlags::empty();
435        assert!(!flags.is_filtered());
436
437        flags |= LocalRibPeerFlags::IS_FILTERED;
438        assert!(flags.is_filtered());
439    }
440
441    #[test]
442    fn test_parsing_local_rib_per_peer_header() {
443        let input_data = vec![
444            3, // PeerType::LocalRib
445            0, // LocalRibPeerFlags is empty
446            0, 0, 0, 0, 0, 0, 0, 1, // Peer Distinguisher
447            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // advance 16 bytes
448            0, 0, 0, 1, // Peer ASN
449            192, 168, 1, 1, // Peer BGP ID
450            0, 0, 0, 10, // Timestamp (seconds)
451            0, 0, 0, 100, // Timestamp (microseconds)
452        ];
453
454        let mut bytes = Bytes::from(input_data);
455        let header =
456            parse_per_peer_header(&mut bytes).expect("Failed to parse local rib per peer header");
457
458        assert_eq!(header.peer_type, BmpPeerType::LocalRib);
459        assert_eq!(
460            header.peer_flags,
461            PerPeerFlags::LocalRibPeerFlags(LocalRibPeerFlags::empty())
462        );
463        assert_eq!(header.peer_asn, Asn::new_32bit(1));
464        assert_eq!(header.peer_bgp_id, Ipv4Addr::new(192, 168, 1, 1));
465        assert_eq!(header.timestamp, 10.0001);
466    }
467
468    #[test]
469    #[allow(clippy::field_reassign_with_default)]
470    fn test_equality_hash() {
471        let header1 = BmpPerPeerHeader::default();
472
473        let mut header2 = BmpPerPeerHeader::default();
474        header2.timestamp = 1.0;
475
476        assert_ne!(header1, header2);
477        assert_eq!(header1.strip_timestamp(), header2.strip_timestamp());
478
479        let mut hashmap = std::collections::HashMap::new();
480        hashmap.insert(header1.strip_timestamp(), 1);
481        assert_eq!(hashmap.get(&header2.strip_timestamp()), Some(&1));
482    }
483}