Skip to main content

bgpkit_parser/models/bgp/
mod.rs

1//! BGP messages and relevant structs.
2
3pub mod attributes;
4pub mod capabilities;
5pub mod community;
6pub mod elem;
7pub mod error;
8pub mod flowspec;
9pub mod linkstate;
10pub mod role;
11pub mod tunnel_encap;
12
13pub use attributes::*;
14pub use community::*;
15pub use elem::*;
16pub use error::*;
17pub use flowspec::*;
18pub use linkstate::*;
19pub use role::*;
20pub use tunnel_encap::*;
21
22use crate::models::network::*;
23use capabilities::{
24    AddPathCapability, BgpCapabilityType, BgpExtendedMessageCapability, BgpRoleCapability,
25    ExtendedNextHopCapability, FourOctetAsCapability, GracefulRestartCapability,
26    MultiprotocolExtensionsCapability, RouteRefreshCapability,
27};
28use num_enum::{IntoPrimitive, TryFromPrimitive};
29use std::net::Ipv4Addr;
30
31pub type BgpIdentifier = Ipv4Addr;
32
33#[allow(non_camel_case_types)]
34#[derive(Debug, TryFromPrimitive, IntoPrimitive, Copy, Clone, PartialEq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[repr(u8)]
37pub enum BgpMessageType {
38    OPEN = 1,
39    UPDATE = 2,
40    NOTIFICATION = 3,
41    KEEPALIVE = 4,
42    ROUTE_REFRESH = 5,
43}
44
45// https://tools.ietf.org/html/rfc4271#section-4
46#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub enum BgpMessage {
49    Open(BgpOpenMessage),
50    Update(BgpUpdateMessage),
51    Notification(BgpNotificationMessage),
52    KeepAlive,
53    RouteRefresh(BgpRouteRefreshMessage),
54}
55
56impl BgpMessage {
57    pub const fn msg_type(&self) -> BgpMessageType {
58        match self {
59            BgpMessage::Open(_) => BgpMessageType::OPEN,
60            BgpMessage::Update(_) => BgpMessageType::UPDATE,
61            BgpMessage::Notification(_) => BgpMessageType::NOTIFICATION,
62            BgpMessage::KeepAlive => BgpMessageType::KEEPALIVE,
63            BgpMessage::RouteRefresh(_) => BgpMessageType::ROUTE_REFRESH,
64        }
65    }
66}
67
68/// BGP ROUTE-REFRESH Message - RFC 2918
69///
70/// ```text
71///  0                   1                   2                   3
72///  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
73///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74///  |          AFI                  |    Res./Subt. |     SAFI      |
75///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76/// ```
77///
78/// AFI and SAFI are kept as raw integers so refreshes for address families
79/// outside the [Afi]/[Safi] enums still parse and re-encode byte-for-byte.
80#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct BgpRouteRefreshMessage {
83    pub afi: u16,
84    /// Reserved in RFC 2918; message subtype in RFC 7313
85    /// (0 = normal route refresh, 1 = BoRR, 2 = EoRR).
86    pub subtype: u8,
87    pub safi: u8,
88    /// Trailing bytes such as ORF entries (RFC 5291), kept raw.
89    pub data: Vec<u8>,
90}
91
92impl BgpRouteRefreshMessage {
93    /// The AFI as a typed [Afi], if it is a known address family.
94    pub fn afi(&self) -> Option<Afi> {
95        Afi::try_from(self.afi).ok()
96    }
97
98    /// The SAFI as a typed [Safi], if it is a known subsequent address family.
99    pub fn safi(&self) -> Option<Safi> {
100        Safi::try_from(self.safi).ok()
101    }
102
103    /// RFC 7313 Section 5 validation findings for this message.
104    ///
105    /// A subtype other than 0-2 MUST be ignored (and SHOULD be logged) by a
106    /// live speaker, and a BoRR/EoRR message (subtype 1 or 2) whose body is
107    /// not exactly 4 bytes is an "Invalid Message Length" error. Both rules
108    /// only apply when the Enhanced Route Refresh capability was negotiated,
109    /// which MRT data cannot show, so the parser reports them as warnings
110    /// while retaining the message.
111    pub fn validation_warnings(&self) -> Vec<crate::error::BgpValidationWarning> {
112        use crate::error::BgpValidationWarning;
113        let mut warnings = Vec::new();
114        match self.subtype {
115            0 => {}
116            1 | 2 => {
117                if !self.data.is_empty() {
118                    warnings.push(BgpValidationWarning::InvalidRouteRefreshLength {
119                        subtype: self.subtype,
120                        length: 4 + self.data.len(),
121                    });
122                }
123            }
124            subtype => {
125                warnings.push(BgpValidationWarning::UnknownRouteRefreshSubtype { subtype });
126            }
127        }
128        warnings
129    }
130}
131
132/// BGP Open Message
133///
134/// ```text
135///  0                   1                   2                   3
136///  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
137///  +-+-+-+-+-+-+-+-+
138///  |    Version    |
139///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
140///  |     My Autonomous System      |
141///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
142///  |           Hold Time           |
143///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
144///  |                         BGP Identifier                        |
145///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
146///  | Opt Parm Len  |
147///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
148///  |                                                               |
149///  |             Optional Parameters (variable)                    |
150///  |                                                               |
151///  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
152/// ```
153#[derive(Debug, Clone, PartialEq, Eq)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155pub struct BgpOpenMessage {
156    pub version: u8,
157    pub asn: Asn,
158    pub hold_time: u16,
159    pub bgp_identifier: BgpIdentifier,
160    pub extended_length: bool,
161    pub opt_params: Vec<OptParam>,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct OptParam {
167    pub param_type: u8,
168    pub param_value: ParamValue,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub enum ParamValue {
174    Raw(Vec<u8>),
175    Capacities(Vec<Capability>),
176}
177
178/// BGP Capability.
179///
180/// - RFC3392: <https://datatracker.ietf.org/doc/html/rfc3392>
181/// - Capability codes: <https://www.iana.org/assignments/capability-codes/capability-codes.xhtml#capability-codes-2>
182#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct Capability {
185    pub ty: BgpCapabilityType,
186    pub value: CapabilityValue,
187}
188
189/// Parsed BGP capability values
190#[derive(Debug, Clone, PartialEq, Eq)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub enum CapabilityValue {
193    /// Raw unparsed capability data
194    Raw(Vec<u8>),
195    /// Multiprotocol Extensions capability - RFC 2858, Section 7
196    MultiprotocolExtensions(MultiprotocolExtensionsCapability),
197    /// Route Refresh capability - RFC 2918
198    RouteRefresh(RouteRefreshCapability),
199    /// Extended Next Hop capability - RFC 8950, Section 3
200    ExtendedNextHop(ExtendedNextHopCapability),
201    /// Graceful Restart capability - RFC 4724
202    GracefulRestart(GracefulRestartCapability),
203    /// 4-octet AS number capability - RFC 6793
204    FourOctetAs(FourOctetAsCapability),
205    /// ADD-PATH capability - RFC 7911
206    AddPath(AddPathCapability),
207    /// BGP Role capability - RFC 9234
208    BgpRole(BgpRoleCapability),
209    /// BGP Extended Message capability - RFC 8654
210    BgpExtendedMessage(BgpExtendedMessageCapability),
211}
212
213#[derive(Debug, Clone, PartialEq, Default, Eq)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215/// BGP Update Message.
216///
217/// Corresponding RFC section: <https://datatracker.ietf.org/doc/html/rfc4271#section-4.3>
218pub struct BgpUpdateMessage {
219    /// Withdrawn prefixes in this update message.
220    ///
221    /// **IMPORTANT:** Do **not** access this field directly in order to get all withdrawn prefixes.
222    /// Some withdrawn prefixes may be present in the [`AttributeValue::MpUnreachNlri`] attribute,
223    /// and will **not** be included here. Accessing this field directly may cause you to miss
224    /// IPv6 or multi-protocol prefixes.
225    ///
226    /// Instead, use [`Elementor::bgp_update_to_elems`](crate::Elementor::bgp_update_to_elems) to reliably extract all withdrawn prefixes from the update,
227    /// or combine this field with prefixes found in the `MpUnreachNlri` attribute manually.
228    ///
229    /// See
230    /// * RFC4271 Section 4.3: <https://datatracker.ietf.org/doc/html/rfc4271#section-4.3>
231    /// * RFC4760 Section 4: <https://datatracker.ietf.org/doc/html/rfc4760#section-4>
232    pub withdrawn_prefixes: Vec<NetworkPrefix>,
233
234    /// BGP path attributes.
235    pub attributes: Attributes,
236
237    /// Network prefixes that are being advertised in this update message.
238    ///
239    /// **IMPORTANT:** Do **not** access this field directly in order to get all announced prefixes.
240    /// Some advertised prefixes may be present in the [`AttributeValue::MpReachNlri`] attribute,
241    /// and will **not** be included here. Accessing this field directly may cause you to miss
242    /// IPv6 or multi-protocol prefixes.
243    ///
244    /// Instead, use [`Elementor::bgp_update_to_elems`](crate::Elementor::bgp_update_to_elems) to reliably extract all announced prefixes from the update,
245    /// or combine this field with prefixes found in the `MpReachNlri` attribute manually.
246    ///
247    /// See
248    ///
249    /// * RFC4271 Section 4.3: <https://datatracker.ietf.org/doc/html/rfc4271#section-4.3>
250    /// * RFC4760 Section 3: <https://datatracker.ietf.org/doc/html/rfc4760#section-3>
251    pub announced_prefixes: Vec<NetworkPrefix>,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256pub struct BgpNotificationMessage {
257    pub error: BgpError,
258    pub data: Vec<u8>,
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use crate::error::BgpValidationWarning;
265
266    fn route_refresh(subtype: u8, data: Vec<u8>) -> BgpRouteRefreshMessage {
267        BgpRouteRefreshMessage {
268            afi: 1,
269            subtype,
270            safi: 1,
271            data,
272        }
273    }
274
275    #[test]
276    fn test_route_refresh_validation_warnings() {
277        // Normal refreshes carry no findings, with or without ORF data
278        assert!(route_refresh(0, vec![]).validation_warnings().is_empty());
279        assert!(route_refresh(0, vec![0x01])
280            .validation_warnings()
281            .is_empty());
282
283        // BoRR/EoRR with an exactly 4-byte body are clean
284        assert!(route_refresh(1, vec![]).validation_warnings().is_empty());
285        assert!(route_refresh(2, vec![]).validation_warnings().is_empty());
286
287        // BoRR/EoRR with trailing bytes violate the RFC 7313 length rule
288        assert_eq!(
289            route_refresh(2, vec![0xDE, 0xAD, 0xBE]).validation_warnings(),
290            vec![BgpValidationWarning::InvalidRouteRefreshLength {
291                subtype: 2,
292                length: 7
293            }]
294        );
295
296        // Subtypes outside 0-2 are unknown
297        assert_eq!(
298            route_refresh(3, vec![]).validation_warnings(),
299            vec![BgpValidationWarning::UnknownRouteRefreshSubtype { subtype: 3 }]
300        );
301    }
302
303    #[test]
304    fn test_message_type() {
305        let open = BgpMessage::Open(BgpOpenMessage {
306            version: 4,
307            asn: Asn::new_32bit(1),
308            hold_time: 180,
309            bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
310            extended_length: false,
311            opt_params: vec![],
312        });
313        assert_eq!(open.msg_type(), BgpMessageType::OPEN);
314
315        let update = BgpMessage::Update(BgpUpdateMessage::default());
316        assert_eq!(update.msg_type(), BgpMessageType::UPDATE);
317
318        let notification = BgpMessage::Notification(BgpNotificationMessage {
319            error: BgpError::Unknown(0, 0),
320            data: vec![],
321        });
322        assert_eq!(notification.msg_type(), BgpMessageType::NOTIFICATION);
323
324        let keepalive = BgpMessage::KeepAlive;
325        assert_eq!(keepalive.msg_type(), BgpMessageType::KEEPALIVE);
326    }
327
328    #[test]
329    #[cfg(feature = "serde")]
330    fn test_serde() {
331        let open = BgpMessage::Open(BgpOpenMessage {
332            version: 4,
333            asn: Asn::new_32bit(1),
334            hold_time: 180,
335            bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
336            extended_length: false,
337            opt_params: vec![],
338        });
339        let serialized = serde_json::to_string(&open).unwrap();
340        let deserialized: BgpMessage = serde_json::from_str(&serialized).unwrap();
341        assert_eq!(open, deserialized);
342
343        let update = BgpMessage::Update(BgpUpdateMessage::default());
344        let serialized = serde_json::to_string(&update).unwrap();
345        let deserialized: BgpMessage = serde_json::from_str(&serialized).unwrap();
346        assert_eq!(update, deserialized);
347
348        let notification = BgpMessage::Notification(BgpNotificationMessage {
349            error: BgpError::Unknown(0, 0),
350            data: vec![],
351        });
352        let serialized = serde_json::to_string(&notification).unwrap();
353        let deserialized: BgpMessage = serde_json::from_str(&serialized).unwrap();
354        assert_eq!(notification, deserialized);
355
356        let keepalive = BgpMessage::KeepAlive;
357        let serialized = serde_json::to_string(&keepalive).unwrap();
358        let deserialized: BgpMessage = serde_json::from_str(&serialized).unwrap();
359        assert_eq!(keepalive, deserialized);
360    }
361}