Skip to main content

netgauze_bmp_pkt/
lib.rs

1// Copyright (C) 2022-present The NetGauze Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#[cfg(feature = "fuzz")]
17use chrono::TimeZone;
18use chrono::{DateTime, Utc};
19use std::hash::{Hash, Hasher};
20use std::net::{IpAddr, Ipv4Addr};
21use std::ops::Deref;
22
23use netgauze_bgp_pkt::nlri::RouteDistinguisher;
24
25use crate::iana::{BmpMessageType, BmpPeerTypeCode, BmpVersion};
26
27use serde::{Deserialize, Serialize};
28
29#[cfg(feature = "codec")]
30pub mod codec;
31pub mod iana;
32pub mod v3;
33pub mod v4;
34#[cfg(feature = "serde")]
35pub mod wire;
36
37/// ```text
38///  0                   1                   2                   3
39///  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
40/// +-+-+-+-+-+-+-+-+
41/// |    Version    |
42/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
43/// |                        Message Length                         |
44/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45/// |   Msg. Type   |
46/// +---------------+
47/// ```
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
50pub enum BmpMessage {
51    V3(v3::BmpMessageValue),
52    V4(v4::BmpMessageValue),
53}
54
55impl BmpMessage {
56    /// Returns the BMP Version from the BMP Common Header
57    /// as [BmpVersion] because there is no IANA registry for BMP Versions
58    pub fn get_version(&self) -> BmpVersion {
59        match self {
60            BmpMessage::V3(_) => BmpVersion::Version3,
61            BmpMessage::V4(_) => BmpVersion::Version4,
62        }
63    }
64
65    /// Returns the BMP Message Type ([BmpMessageType]) from the BMP Common
66    /// Header
67    pub fn get_type(&self) -> BmpMessageType {
68        match &self {
69            BmpMessage::V3(value) => value.get_type(),
70            BmpMessage::V4(value) => value.get_type(),
71        }
72    }
73}
74
75///  The per-peer header follows the common header for most BMP messages.
76///  The rest of the data in a BMP message is dependent on the Message
77///  Type field in the common header.
78///
79/// ```text
80///   0                   1                   2                   3
81///   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
82///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
83///  |   Peer Type   |  Peer Flags   |
84///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
85///  |         Peer Distinguisher (present based on peer type)       |
86///  |                                                               |
87///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88///  |                 Peer Address (16 bytes)                       |
89///  ~                                                               ~
90///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91///  |                           Peer AS                             |
92///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93///  |                         Peer BGP ID                           |
94///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
95///  |                    Timestamp (seconds)                        |
96///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
97///  |                  Timestamp (microseconds)                     |
98///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
99/// ```
100#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
101#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
102pub struct PeerHeader {
103    peer_type: BmpPeerType,
104    rd: Option<RouteDistinguisher>,
105    #[cfg_attr(feature = "fuzz", arbitrary(with = arbitrary_ext::arbitrary_option(crate::arbitrary_ip)))]
106    address: Option<IpAddr>,
107    peer_as: u32,
108    bgp_id: Ipv4Addr,
109    #[cfg_attr(feature = "fuzz", arbitrary(with = arbitrary_ext::arbitrary_option(crate::arbitrary_datetime)))]
110    timestamp: Option<DateTime<Utc>>,
111}
112
113impl PeerHeader {
114    pub const fn new(
115        peer_type: BmpPeerType,
116        rd: Option<RouteDistinguisher>,
117        address: Option<IpAddr>,
118        peer_as: u32,
119        bgp_id: Ipv4Addr,
120        timestamp: Option<DateTime<Utc>>,
121    ) -> Self {
122        Self {
123            peer_type,
124            rd,
125            address,
126            peer_as,
127            bgp_id,
128            timestamp,
129        }
130    }
131
132    pub const fn peer_type(&self) -> BmpPeerType {
133        self.peer_type
134    }
135
136    pub const fn rd(&self) -> Option<RouteDistinguisher> {
137        self.rd
138    }
139
140    pub const fn address(&self) -> Option<IpAddr> {
141        self.address
142    }
143
144    pub const fn peer_as(&self) -> u32 {
145        self.peer_as
146    }
147
148    pub const fn bgp_id(&self) -> Ipv4Addr {
149        self.bgp_id
150    }
151
152    pub const fn timestamp(&self) -> Option<&DateTime<Utc>> {
153        self.timestamp.as_ref()
154    }
155
156    pub const fn is_asn4(&self) -> bool {
157        match self.peer_type {
158            BmpPeerType::GlobalInstancePeer { asn2, .. } => !asn2,
159            BmpPeerType::RdInstancePeer { asn2, .. } => !asn2,
160            BmpPeerType::LocalInstancePeer { asn2, .. } => !asn2,
161            BmpPeerType::LocRibInstancePeer { .. } => true,
162            BmpPeerType::Experimental251 { .. } => true,
163            BmpPeerType::Experimental252 { .. } => true,
164            BmpPeerType::Experimental253 { .. } => true,
165            BmpPeerType::Experimental254 { .. } => true,
166        }
167    }
168}
169
170/// Identifies the type of peer, along with the type specific flags
171/// Flags:
172///  - ipv6: The V flag indicates that the Peer address is an IPv6 address. For
173///    IPv4 peers, this is set to `false`.
174///  - `post_policy`: The L flag, if set to `true`, indicates that the message
175///    reflects the post-policy Adj-RIB-In (i.e., its path attributes reflect
176///    the application of inbound policy). It is set to `false` if the message
177///    reflects the pre-policy Adj-RIB-In. Locally sourced routes also carry an
178///    L flag of `true`. This flag has no significance when used with route
179///    mirroring messages.
180///  - asn2: The A flag, if set to `true`, indicates that the message is
181///    formatted using the legacy 2-byte `AS_PATH` format. If set to `false`,
182///    the message is formatted using the 4-byte `AS_PATH` format
183///    [RFC6793](https://datatracker.ietf.org/doc/html/rfc6793).
184///    A BMP speaker MAY choose to propagate the `AS_PATH`
185///    information as received from its peer, or it MAY choose to
186///    reformat all `AS_PATH` information into a 4-byte format
187///    regardless of how it was received from the peer. In the latter
188///    case, `AS4_PATH` or `AS4_AGGREGATOR` path attributes SHOULD NOT be
189///    sent in the BMP UPDATE message. This flag has no significance
190///    when used with route mirroring messages.
191///  - filtered: The F flag indicates that the Loc-RIB is filtered. This MUST be
192///    set when a filter is applied to Loc-RIB routes sent to the BMP collector.
193#[derive(Debug, Hash, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
194#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
195pub enum BmpPeerType {
196    GlobalInstancePeer {
197        ipv6: bool,
198        post_policy: bool,
199        asn2: bool,
200        adj_rib_out: bool,
201    },
202    RdInstancePeer {
203        ipv6: bool,
204        post_policy: bool,
205        asn2: bool,
206        adj_rib_out: bool,
207    },
208    LocalInstancePeer {
209        ipv6: bool,
210        post_policy: bool,
211        asn2: bool,
212        adj_rib_out: bool,
213    },
214    LocRibInstancePeer {
215        filtered: bool,
216    },
217    Experimental251 {
218        flags: u8,
219    },
220    Experimental252 {
221        flags: u8,
222    },
223    Experimental253 {
224        flags: u8,
225    },
226    Experimental254 {
227        flags: u8,
228    },
229}
230
231impl BmpPeerType {
232    /// Get the IANA Code for the peer type
233    pub const fn get_type(&self) -> BmpPeerTypeCode {
234        match self {
235            Self::GlobalInstancePeer { .. } => BmpPeerTypeCode::GlobalInstancePeer,
236            Self::RdInstancePeer { .. } => BmpPeerTypeCode::RdInstancePeer,
237            Self::LocalInstancePeer { .. } => BmpPeerTypeCode::LocalInstancePeer,
238            Self::LocRibInstancePeer { .. } => BmpPeerTypeCode::LocRibInstancePeer,
239            Self::Experimental251 { .. } => BmpPeerTypeCode::Experimental251,
240            Self::Experimental252 { .. } => BmpPeerTypeCode::Experimental252,
241            Self::Experimental253 { .. } => BmpPeerTypeCode::Experimental253,
242            Self::Experimental254 { .. } => BmpPeerTypeCode::Experimental254,
243        }
244    }
245}
246
247/// A non-negative integer that monotonically increases
248/// until it reaches a maximum value, when it wraps around and starts
249/// increasing again from 0.
250#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
251#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
252pub struct CounterU32(u32);
253
254impl CounterU32 {
255    pub const fn new(value: u32) -> Self {
256        Self(value)
257    }
258
259    pub const fn value(&self) -> u32 {
260        self.0
261    }
262}
263
264impl Deref for CounterU32 {
265    type Target = u32;
266
267    fn deref(&self) -> &Self::Target {
268        &self.0
269    }
270}
271
272/// Non-negative integer that may increase or decrease,
273/// but shall never exceed a maximum value, nor fall below a minimum one.
274/// The maximum value cannot be greater than 2^64-1 (18446744073709551615
275/// decimal), and the minimum value cannot be smaller than 0. The value
276/// has its maximum value whenever the information being modeled is
277/// greater than or equal to its maximum value, and has its minimum value
278/// whenever the information being modeled is smaller than or equal to
279/// its minimum value. If the information being modeled subsequently
280/// decreases below the maximum value (or increases above the minimum
281/// value), the 64-bit Gauge also decreases (or increases).
282#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
283#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
284pub struct GaugeU64(u64);
285
286impl GaugeU64 {
287    pub const fn new(value: u64) -> Self {
288        Self(value)
289    }
290
291    pub const fn value(&self) -> u64 {
292        self.0
293    }
294}
295
296impl Deref for GaugeU64 {
297    type Target = u64;
298
299    fn deref(&self) -> &Self::Target {
300        &self.0
301    }
302}
303
304// Custom function to generate arbitrary ipv4 addresses
305#[cfg(feature = "fuzz")]
306fn arbitrary_ipv4(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Ipv4Addr> {
307    let value = u.int_in_range(0..=u32::MAX)?;
308    Ok(Ipv4Addr::from(value))
309}
310
311/// PeerKey is used to identify a BMP peer. This key is unique only
312/// to the BMP session.
313#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
314#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
315pub struct PeerKey {
316    #[cfg_attr(feature = "fuzz", arbitrary(with = arbitrary_ext::arbitrary_option(crate::arbitrary_ip)))]
317    peer_address: Option<IpAddr>,
318    peer_type: BmpPeerType,
319    rd: Option<RouteDistinguisher>,
320    asn: u32,
321    #[cfg_attr(feature = "fuzz", arbitrary(with = arbitrary_ipv4))]
322    bgp_id: Ipv4Addr,
323}
324
325impl Hash for PeerKey {
326    fn hash<H: Hasher>(&self, state: &mut H) {
327        self.peer_address.hash(state);
328        self.peer_type.get_type().hash(state);
329        self.rd.hash(state);
330        self.asn.hash(state);
331        self.bgp_id.hash(state);
332    }
333}
334impl PartialEq<Self> for PeerKey {
335    fn eq(&self, other: &Self) -> bool {
336        self.peer_address.eq(&other.peer_address)
337            && std::mem::discriminant(&self.peer_type) == std::mem::discriminant(&other.peer_type)
338            && self.rd == other.rd
339            && self.asn == other.asn
340            && self.bgp_id == other.bgp_id
341    }
342}
343
344impl Eq for PeerKey {}
345
346impl PeerKey {
347    pub const fn new(
348        peer_address: Option<IpAddr>,
349        peer_type: BmpPeerType,
350        rd: Option<RouteDistinguisher>,
351        asn: u32,
352        bgp_id: Ipv4Addr,
353    ) -> Self {
354        Self {
355            peer_address,
356            peer_type,
357            rd,
358            asn,
359            bgp_id,
360        }
361    }
362
363    pub const fn from_peer_header(header: &PeerHeader) -> Self {
364        Self::new(
365            header.address,
366            header.peer_type,
367            header.rd,
368            header.peer_as,
369            header.bgp_id,
370        )
371    }
372
373    pub const fn peer_address(&self) -> Option<IpAddr> {
374        self.peer_address
375    }
376    pub const fn peer_type(&self) -> BmpPeerType {
377        self.peer_type
378    }
379    pub const fn rd(&self) -> Option<RouteDistinguisher> {
380        self.rd
381    }
382    pub const fn asn(&self) -> u32 {
383        self.asn
384    }
385    pub const fn bgp_id(&self) -> Ipv4Addr {
386        self.bgp_id
387    }
388}
389
390// Custom function to generate arbitrary ipv6 addresses
391#[cfg(feature = "fuzz")]
392fn arbitrary_ipv6(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<std::net::Ipv6Addr> {
393    let value = u.int_in_range(0..=u128::MAX)?;
394    Ok(std::net::Ipv6Addr::from(value))
395}
396
397// Custom function to generate arbitrary IPv4 and IPv6 addresses
398#[cfg(feature = "fuzz")]
399fn arbitrary_ip(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<IpAddr> {
400    let ipv4 = arbitrary_ipv4(u)?;
401    let ipv6 = arbitrary_ipv6(u)?;
402    let choices = [IpAddr::V4(ipv4), IpAddr::V6(ipv6)];
403    let addr = u.choose(&choices)?;
404    Ok(*addr)
405}
406
407#[cfg(feature = "fuzz")]
408fn arbitrary_datetime(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<DateTime<Utc>> {
409    loop {
410        let seconds = u.int_in_range(0..=i64::MAX)?;
411        if let chrono::LocalResult::Single(tt) = Utc.timestamp_opt(seconds, 0) {
412            return Ok(tt);
413        }
414    }
415}