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.has_n_remaining(12)?;
322                    data.advance(12);
323                    IpAddr::V4(data.read_ipv4_address()?)
324                }
325                Afi::Ipv6 => IpAddr::V6(data.read_ipv6_address()?),
326                Afi::LinkState => {
327                    // Link-State doesn't use traditional IP addresses for peer identification
328                    // Use IPv4 zero address as placeholder
329                    data.has_n_remaining(12)?;
330                    data.advance(12);
331                    IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))
332                }
333            };
334
335            let peer_asn = match peer_flags.asn_length() {
336                AsnLength::Bits16 => {
337                    data.has_n_remaining(2)?;
338                    data.advance(2);
339                    Asn::new_16bit(data.read_u16()?)
340                }
341                AsnLength::Bits32 => Asn::new_32bit(data.read_u32()?),
342            };
343
344            let peer_bgp_id = data.read_ipv4_address()?;
345
346            let t_sec = data.read_u32()?;
347            let t_usec = data.read_u32()?;
348            let timestamp = t_sec as f64 + (t_usec as f64) / 1_000_000.0;
349
350            Ok(BmpPerPeerHeader {
351                peer_type,
352                peer_flags: PerPeerFlags::PeerFlags(peer_flags),
353                peer_distinguisher,
354                peer_ip,
355                peer_asn,
356                peer_bgp_id,
357                timestamp,
358            })
359        }
360        BmpPeerType::LocalRib => {
361            let local_rib_peer_flags = LocalRibPeerFlags::from_bits_retain(data.read_u8()?);
362
363            let peer_distinguisher = data.read_u64()?;
364
365            // RFC 9069: Peer Address MUST be zero-filled for Local RIB
366            let peer_addr_bytes: [u8; 16] = {
367                let mut bytes = [0u8; 16];
368                data.has_n_remaining(16)?;
369                data.copy_to_slice(&mut bytes);
370                bytes
371            };
372
373            // Validate that peer address is zero-filled as required by RFC 9069
374            if peer_addr_bytes != [0u8; 16] {
375                warn!("RFC 9069 violation: Local RIB peer address MUST be zero-filled, but found non-zero bytes");
376            }
377
378            // Local RIB peer address is always zero-filled per RFC 9069
379            let peer_ip = IpAddr::V4(Ipv4Addr::from(0));
380
381            // RFC 9069: MUST use 4-byte ASN encoding for Local RIB
382            let peer_asn = Asn::new_32bit(data.read_u32()?);
383
384            let peer_bgp_id = data.read_ipv4_address()?;
385
386            // RFC 9069: Validate that peer BGP ID is non-zero for meaningful instances
387            if peer_bgp_id == Ipv4Addr::from(0) && peer_distinguisher != 0 {
388                warn!("RFC 9069: Local RIB peer BGP ID should be set to VRF instance router-id for non-global instances");
389            }
390
391            let t_sec = data.read_u32()?;
392            let t_usec = data.read_u32()?;
393            let timestamp = t_sec as f64 + (t_usec as f64) / 1_000_000.0;
394
395            Ok(BmpPerPeerHeader {
396                peer_type,
397                peer_flags: PerPeerFlags::LocalRibPeerFlags(local_rib_peer_flags),
398                peer_distinguisher,
399                peer_ip,
400                peer_asn,
401                peer_bgp_id,
402                timestamp,
403            })
404        }
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    #[test]
413    fn test_header_error() {
414        let mut data = Bytes::from(vec![0, 0, 0, 0, 0]);
415        assert!(parse_bmp_common_header(&mut data).is_err(),);
416        assert_eq!(
417            parse_bmp_common_header(&mut data).unwrap_err(),
418            ParserBmpError::CorruptedBmpMessage
419        );
420    }
421
422    #[test]
423    fn test_bmp_per_peer_header_basics() {
424        // test AFI checking
425        let per_peer_header = BmpPerPeerHeader {
426            peer_type: BmpPeerType::Global,
427            peer_flags: PerPeerFlags::LocalRibPeerFlags(LocalRibPeerFlags::empty()),
428            peer_distinguisher: 0,
429            peer_ip: IpAddr::V4(Ipv4Addr::from(0)),
430            peer_asn: Default::default(),
431            peer_bgp_id: Ipv4Addr::from(0),
432            timestamp: 0.0,
433        };
434        assert_eq!(per_peer_header.afi(), Afi::Ipv4);
435
436        // check ASN length
437        assert_eq!(per_peer_header.asn_length(), AsnLength::Bits32);
438    }
439
440    #[test]
441    fn test_peer_flags() {
442        let mut flags = PeerFlags::empty();
443        assert_eq!(flags.address_family(), Afi::Ipv4);
444        assert_eq!(flags.asn_length(), AsnLength::Bits32);
445        assert!(!flags.is_adj_rib_out());
446        assert!(!flags.is_post_policy());
447
448        flags |= PeerFlags::ADDRESS_FAMILY_IPV6;
449        assert_eq!(flags.address_family(), Afi::Ipv6);
450
451        flags |= PeerFlags::AS_SIZE_16BIT;
452        assert_eq!(flags.asn_length(), AsnLength::Bits16);
453
454        flags |= PeerFlags::IS_ADJ_RIB_OUT;
455        assert!(flags.is_adj_rib_out());
456
457        flags |= PeerFlags::IS_POST_POLICY;
458        assert!(flags.is_post_policy());
459    }
460
461    #[test]
462    fn test_local_rib_peer_flags() {
463        let mut flags = LocalRibPeerFlags::empty();
464        assert!(!flags.is_filtered());
465
466        flags |= LocalRibPeerFlags::IS_FILTERED;
467        assert!(flags.is_filtered());
468    }
469
470    #[test]
471    fn test_parsing_local_rib_per_peer_header() {
472        let input_data = vec![
473            3, // PeerType::LocalRib
474            0, // LocalRibPeerFlags is empty
475            0, 0, 0, 0, 0, 0, 0, 1, // Peer Distinguisher
476            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // advance 16 bytes
477            0, 0, 0, 1, // Peer ASN
478            192, 168, 1, 1, // Peer BGP ID
479            0, 0, 0, 10, // Timestamp (seconds)
480            0, 0, 0, 100, // Timestamp (microseconds)
481        ];
482
483        let mut bytes = Bytes::from(input_data);
484        let header =
485            parse_per_peer_header(&mut bytes).expect("Failed to parse local rib per peer header");
486
487        assert_eq!(header.peer_type, BmpPeerType::LocalRib);
488        assert_eq!(
489            header.peer_flags,
490            PerPeerFlags::LocalRibPeerFlags(LocalRibPeerFlags::empty())
491        );
492        assert_eq!(header.peer_asn, Asn::new_32bit(1));
493        assert_eq!(header.peer_bgp_id, Ipv4Addr::new(192, 168, 1, 1));
494        assert_eq!(header.timestamp, 10.0001);
495    }
496
497    #[test]
498    #[allow(clippy::field_reassign_with_default)]
499    fn test_equality_hash() {
500        let header1 = BmpPerPeerHeader::default();
501
502        let mut header2 = BmpPerPeerHeader::default();
503        header2.timestamp = 1.0;
504
505        assert_ne!(header1, header2);
506        assert_eq!(header1.strip_timestamp(), header2.strip_timestamp());
507
508        let mut hashmap = std::collections::HashMap::new();
509        hashmap.insert(header1.strip_timestamp(), 1);
510        assert_eq!(hashmap.get(&header2.strip_timestamp()), Some(&1));
511    }
512}