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