bgp_rs/
open.rs

1//! The `open` mod provides structs and implementation for OPEN messages
2//! - Open Attributes
3//! - Optional Parameters
4//!   - Parsing as Capabilities for comparison between two OPEN messages
5//!
6
7use std::collections::{HashMap, HashSet};
8use std::convert::TryFrom;
9use std::io::{Error, ErrorKind, Read, Write};
10
11use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
12
13use crate::*;
14
15/// Represents a BGP Open message.
16#[derive(Clone, Debug)]
17pub struct Open {
18    /// Indicates the protocol version number of the message. The current BGP version number is 4.
19    pub version: u8,
20
21    /// Indicates the Autonomous System number of the sender.
22    pub peer_asn: u16,
23
24    /// Indicates the number of seconds the sender proposes for the value of the Hold Timer.
25    pub hold_timer: u16,
26
27    /// Indicates the BGP Identifier of the sender.
28    pub identifier: u32,
29
30    /// Optional Parameters
31    pub parameters: Vec<OpenParameter>,
32}
33
34impl Open {
35    /// Parse Open message (version, ASN, parameters, etc...)
36    pub fn parse(stream: &mut impl Read) -> Result<Open, Error> {
37        let version = stream.read_u8()?;
38        let peer_asn = stream.read_u16::<BigEndian>()?;
39        let hold_timer = stream.read_u16::<BigEndian>()?;
40        let identifier = stream.read_u32::<BigEndian>()?;
41        let mut length = stream.read_u8()? as i32;
42
43        let mut parameters: Vec<OpenParameter> = Vec::with_capacity(length as usize);
44
45        while length > 0 {
46            let (bytes_read, parameter) = OpenParameter::parse(stream)?;
47            parameters.push(parameter);
48            length -= bytes_read as i32;
49        }
50        if length != 0 {
51            return Err(Error::new(
52                ErrorKind::InvalidData,
53                "Open length does not match options length",
54            ));
55        }
56
57        Ok(Open {
58            version,
59            peer_asn,
60            hold_timer,
61            identifier,
62            parameters,
63        })
64    }
65
66    /// Encode message to bytes
67    pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> {
68        buf.write_u8(self.version)?;
69        buf.write_u16::<BigEndian>(self.peer_asn)?;
70        buf.write_u16::<BigEndian>(self.hold_timer)?;
71        buf.write_u32::<BigEndian>(self.identifier)?;
72
73        let mut parameter_buf: Vec<u8> = Vec::with_capacity(4);
74        for p in self.parameters.iter() {
75            p.encode(&mut parameter_buf)?;
76        }
77        if parameter_buf.len() > std::u8::MAX as usize {
78            return Err(Error::new(
79                ErrorKind::Other,
80                format!(
81                    "Cannot encode parameters with length {}",
82                    parameter_buf.len()
83                ),
84            ));
85        }
86        buf.write_u8(parameter_buf.len() as u8)?;
87        buf.write_all(&parameter_buf)
88    }
89}
90
91/// The direction which an ADD-PATH capabilty indicates a peer can provide additional paths.
92#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
93#[repr(u8)]
94pub enum AddPathDirection {
95    /// Indiates a peer can recieve additional paths.
96    ReceivePaths = 1,
97
98    /// Indiates a peer can send additional paths.
99    SendPaths = 2,
100
101    /// Indiates a peer can both send and receive additional paths.
102    SendReceivePaths = 3,
103}
104
105impl TryFrom<u8> for AddPathDirection {
106    type Error = Error;
107
108    fn try_from(value: u8) -> Result<Self, Self::Error> {
109        match value {
110            1 => Ok(AddPathDirection::ReceivePaths),
111            2 => Ok(AddPathDirection::SendPaths),
112            3 => Ok(AddPathDirection::SendReceivePaths),
113            _ => {
114                let msg = format!(
115                    "Number {} does not represent a valid ADD-PATH direction.",
116                    value
117                );
118                Err(std::io::Error::new(std::io::ErrorKind::Other, msg))
119            }
120        }
121    }
122}
123
124/// Represents a known capability held in an OpenParameter
125#[derive(Clone, Debug)]
126pub enum OpenCapability {
127    /// 1 - Indicates the speaker is willing to exchange multiple protocols over this session.
128    MultiProtocol((AFI, SAFI)),
129    /// 2 - Indicates the speaker supports route refresh.
130    RouteRefresh,
131    /// 3 - Support for Outbound Route Filtering of specified AFI/SAFIs
132    OutboundRouteFiltering(HashSet<(AFI, SAFI, u8, AddPathDirection)>),
133    /// 65 - Indicates the speaker supports 4 byte ASNs and includes the ASN of the speaker.
134    FourByteASN(u32),
135    /// 69 - Indicates the speaker supports sending/receiving multiple paths for a given prefix.
136    AddPath(Vec<(AFI, SAFI, AddPathDirection)>),
137    /// Unknown (or unsupported) capability
138    Unknown {
139        /// The type of the capability.
140        cap_code: u8,
141
142        /// The length of the data that this capability holds in bytes.
143        cap_length: u8,
144
145        /// The value that is set for this capability.
146        value: Vec<u8>,
147    },
148}
149
150impl OpenCapability {
151    fn parse(stream: &mut impl Read) -> Result<(u16, OpenCapability), Error> {
152        let cap_code = stream.read_u8()?;
153        let cap_length = stream.read_u8()?;
154
155        Ok((
156            2 + (cap_length as u16),
157            match cap_code {
158                // MP_BGP
159                1 => {
160                    if cap_length != 4 {
161                        return Err(Error::new(
162                            ErrorKind::InvalidData,
163                            "Multi-Protocol capability must be 4 bytes in length",
164                        ));
165                    }
166                    let afi = AFI::try_from(stream.read_u16::<BigEndian>()?)?;
167                    let _ = stream.read_u8()?;
168                    let safi = SAFI::try_from(stream.read_u8()?)?;
169                    OpenCapability::MultiProtocol((afi, safi))
170                }
171                // ROUTE_REFRESH
172                2 => {
173                    if cap_length != 0 {
174                        return Err(Error::new(
175                            ErrorKind::InvalidData,
176                            "Route-Refresh capability must be 0 bytes in length",
177                        ));
178                    }
179                    OpenCapability::RouteRefresh
180                }
181                // OUTBOUND_ROUTE_FILTERING
182                3 => {
183                    if cap_length < 5 || (cap_length - 5) % 2 != 0 {
184                        return Err(Error::new(
185                            ErrorKind::InvalidData,
186                            "Outbound Route Filtering capability has an invalid length",
187                        ));
188                    }
189                    let afi = AFI::try_from(stream.read_u16::<BigEndian>()?)?;
190                    let _ = stream.read_u8()?;
191                    let safi = SAFI::try_from(stream.read_u8()?)?;
192                    let count = stream.read_u8()?;
193                    let mut types: HashSet<(AFI, SAFI, u8, AddPathDirection)> = HashSet::new();
194                    for _ in 0..count {
195                        types.insert((
196                            afi,
197                            safi,
198                            stream.read_u8()?,
199                            AddPathDirection::try_from(stream.read_u8()?)?,
200                        ));
201                    }
202                    OpenCapability::OutboundRouteFiltering(types)
203                }
204                // 4_BYTE_ASN
205                65 => {
206                    if cap_length != 4 {
207                        return Err(Error::new(
208                            ErrorKind::InvalidData,
209                            "4-byte ASN capability must be 4 bytes in length",
210                        ));
211                    }
212                    OpenCapability::FourByteASN(stream.read_u32::<BigEndian>()?)
213                }
214                69 => {
215                    if cap_length % 4 != 0 {
216                        return Err(Error::new(
217                            ErrorKind::InvalidData,
218                            "ADD-PATH capability length must be divisble by 4",
219                        ));
220                    }
221                    let mut add_paths = Vec::with_capacity(cap_length as usize / 4);
222                    for _ in 0..(cap_length / 4) {
223                        add_paths.push((
224                            AFI::try_from(stream.read_u16::<BigEndian>()?)?,
225                            SAFI::try_from(stream.read_u8()?)?,
226                            AddPathDirection::try_from(stream.read_u8()?)?,
227                        ));
228                    }
229                    OpenCapability::AddPath(add_paths)
230                }
231                _ => {
232                    let mut value = vec![0; cap_length as usize];
233                    stream.read_exact(&mut value)?;
234                    OpenCapability::Unknown {
235                        cap_code,
236                        cap_length,
237                        value,
238                    }
239                }
240            },
241        ))
242    }
243
244    fn encode(&self, buf: &mut impl Write) -> Result<(), Error> {
245        let mut cap_buf: Vec<u8> = Vec::with_capacity(20);
246        match self {
247            OpenCapability::MultiProtocol((afi, safi)) => {
248                cap_buf.write_u8(1)?; // Capability Type
249                cap_buf.write_u8(4)?; // Capability Length
250                cap_buf.write_u16::<BigEndian>(*afi as u16)?;
251                cap_buf.write_u8(0)?; // Reserved
252                cap_buf.write_u8(*safi as u8)?;
253            }
254            OpenCapability::RouteRefresh => {
255                cap_buf.write_u8(2)?; // Capability Type
256                cap_buf.write_u8(0)?; // Capability Length
257            }
258            OpenCapability::OutboundRouteFiltering(orfs) => {
259                let length = orfs.len();
260                for (i, orf) in orfs.iter().enumerate() {
261                    let (afi, safi, orf_type, orf_direction) = orf;
262                    if i == 0 {
263                        cap_buf.write_u16::<BigEndian>(*afi as u16)?;
264                        cap_buf.write_u8(0)?; // Reserved
265                        cap_buf.write_u8(*safi as u8)?;
266                        cap_buf.write_u8(length as u8)?;
267                    }
268                    cap_buf.write_u8(*orf_type)?;
269                    cap_buf.write_u8(*orf_direction as u8)?;
270                }
271            }
272            OpenCapability::FourByteASN(asn) => {
273                cap_buf.write_u8(65)?; // Capability Type
274                cap_buf.write_u8(4)?; // Capability Length
275                cap_buf.write_u32::<BigEndian>(*asn)?;
276            }
277            OpenCapability::AddPath(add_paths) => {
278                cap_buf.write_u8(69)?; // Capability Type
279                if add_paths.len() * 4 > std::u8::MAX as usize {
280                    return Err(Error::new(
281                        ErrorKind::Other,
282                        format!(
283                            "Cannot encode ADD-PATH with too many AFIs {}",
284                            add_paths.len()
285                        ),
286                    ));
287                }
288                cap_buf.write_u8(add_paths.len() as u8 * 4)?; // Capability Length
289                for p in add_paths.iter() {
290                    cap_buf.write_u16::<BigEndian>(p.0 as u16)?;
291                    cap_buf.write_u8(p.1 as u8)?;
292                    cap_buf.write_u8(p.2 as u8)?;
293                }
294            }
295            OpenCapability::Unknown {
296                cap_code,
297                cap_length,
298                value,
299            } => {
300                cap_buf.write_u8(*cap_code)?;
301                cap_buf.write_u8(*cap_length)?;
302                cap_buf.write_all(&value)?;
303            }
304        }
305        buf.write_u8(2)?; // Parameter Type
306        buf.write_u8(cap_buf.len() as u8)?;
307        buf.write_all(&cap_buf)
308    }
309}
310
311/// Represents a parameter in the optional parameter section of an Open message.
312#[derive(Clone, Debug)]
313pub enum OpenParameter {
314    /// A list of capabilities supported by the sender.
315    Capabilities(Vec<OpenCapability>),
316
317    /// Unknown (or unsupported) parameter
318    Unknown {
319        /// The type of the parameter.
320        param_type: u8,
321
322        /// The length of the data that this parameter holds in bytes.
323        param_length: u8,
324
325        /// The value that is set for this parameter.
326        value: Vec<u8>,
327    },
328}
329
330impl OpenParameter {
331    fn parse(stream: &mut impl Read) -> Result<(u16, OpenParameter), Error> {
332        let param_type = stream.read_u8()?;
333        let param_length = stream.read_u8()?;
334
335        Ok((
336            2 + (param_length as u16),
337            if param_type == 2 {
338                let mut bytes_read: i32 = 0;
339                let mut capabilities = Vec::with_capacity(param_length as usize / 2);
340                while bytes_read < param_length as i32 {
341                    let (cap_length, cap) = OpenCapability::parse(stream)?;
342                    capabilities.push(cap);
343                    bytes_read += cap_length as i32;
344                }
345                if bytes_read != param_length as i32 {
346                    return Err(Error::new(
347                        ErrorKind::InvalidData,
348                        format!(
349                            "Capability length {} does not match parameter length {}",
350                            bytes_read, param_length
351                        ),
352                    ));
353                } else {
354                    OpenParameter::Capabilities(capabilities)
355                }
356            } else {
357                let mut value = vec![0; param_length as usize];
358                stream.read_exact(&mut value)?;
359                OpenParameter::Unknown {
360                    param_type,
361                    param_length,
362                    value,
363                }
364            },
365        ))
366    }
367
368    fn encode(&self, buf: &mut impl Write) -> Result<(), Error> {
369        match self {
370            OpenParameter::Capabilities(caps) => {
371                let mut cap_buf: Vec<u8> = Vec::with_capacity(20);
372                for c in caps.iter() {
373                    c.encode(&mut cap_buf)?;
374                }
375                if cap_buf.len() > std::u8::MAX as usize {
376                    return Err(Error::new(
377                        ErrorKind::Other,
378                        format!("Cannot encode capabilities with length {}", cap_buf.len()),
379                    ));
380                }
381                buf.write_all(&cap_buf)
382            }
383            OpenParameter::Unknown {
384                param_type,
385                param_length,
386                value,
387            } => {
388                buf.write_u8(*param_type)?;
389                buf.write_u8(*param_length)?;
390                buf.write_all(&value)
391            }
392        }
393    }
394}
395
396/// Contains the BGP session parameters that distinguish how BGP messages should be parsed.
397#[allow(non_snake_case)]
398#[derive(Clone, Debug, Default)]
399pub struct Capabilities {
400    /// Support for 4-octet AS number capability.
401    /// 1 - Multiprotocol Extensions for BGP-4
402    pub MP_BGP_SUPPORT: HashSet<(AFI, SAFI)>,
403    /// 2 - Route Refresh Capability for BGP-4
404    pub ROUTE_REFRESH_SUPPORT: bool,
405    /// 3 - Outbound Route Filtering Capability
406    pub OUTBOUND_ROUTE_FILTERING_SUPPORT: HashSet<(AFI, SAFI, u8, AddPathDirection)>,
407    /// 5 - Support for reading NLRI extended with a Path Identifier
408    pub EXTENDED_NEXT_HOP_ENCODING: HashMap<(AFI, SAFI), AFI>,
409    /// 7 - BGPsec
410    pub BGPSEC_SUPPORT: bool,
411    /// 8 - Multiple Labels
412    pub MULTIPLE_LABELS_SUPPORT: HashMap<(AFI, SAFI), u8>,
413    /// 64 - Graceful Restart
414    pub GRACEFUL_RESTART_SUPPORT: HashSet<(AFI, SAFI)>,
415    /// 65 - Support for 4-octet AS number capability.
416    pub FOUR_OCTET_ASN_SUPPORT: bool,
417    /// 69 - ADD_PATH
418    pub ADD_PATH_SUPPORT: HashMap<(AFI, SAFI), AddPathDirection>,
419    /// Support for reading NLRI extended with a Path Identifier
420    pub EXTENDED_PATH_NLRI_SUPPORT: bool,
421    /// 70 - Enhanced Route Refresh
422    pub ENHANCED_ROUTE_REFRESH_SUPPORT: bool,
423    /// 71 - Long-Lived Graceful Restart
424    pub LONG_LIVED_GRACEFUL_RESTART: bool,
425}
426
427impl Capabilities {
428    /// Convert from a collection of Open Parameters
429    pub fn from_parameters(parameters: Vec<OpenParameter>) -> Self {
430        let mut capabilities = Capabilities::default();
431
432        for parameter in parameters {
433            if let OpenParameter::Capabilities(caps) = parameter {
434                for capability in caps {
435                    match capability {
436                        OpenCapability::MultiProtocol(family) => {
437                            capabilities.MP_BGP_SUPPORT.insert(family);
438                        }
439                        OpenCapability::RouteRefresh => {
440                            capabilities.ROUTE_REFRESH_SUPPORT = true;
441                        }
442                        OpenCapability::OutboundRouteFiltering(families) => {
443                            capabilities.OUTBOUND_ROUTE_FILTERING_SUPPORT = families;
444                        }
445                        OpenCapability::FourByteASN(_) => {
446                            capabilities.FOUR_OCTET_ASN_SUPPORT = true;
447                        }
448                        OpenCapability::AddPath(paths) => {
449                            capabilities.EXTENDED_PATH_NLRI_SUPPORT = true;
450                            for path in paths {
451                                capabilities
452                                    .ADD_PATH_SUPPORT
453                                    .insert((path.0, path.1), path.2);
454                            }
455                        }
456                        // Ignore unimplemented capabilities
457                        _ => (),
458                    }
459                }
460            }
461        }
462
463        capabilities
464    }
465}