bgp_rs/update/
mod.rs

1/// Contains the implementation of all BGP path attributes.
2pub mod attributes;
3pub use crate::attributes::*;
4/// Contains the implementation of BGP NLRI.
5pub mod nlri;
6pub use crate::nlri::*;
7#[cfg(feature = "flowspec")]
8/// Contains the implementation of Flowspec attributes
9pub mod flowspec;
10#[cfg(feature = "flowspec")]
11pub use crate::flowspec::*;
12
13use crate::*;
14
15use std::collections::HashMap;
16use std::io::{Cursor, Error, Read};
17use std::net::IpAddr;
18
19/// Represents a BGP Update message.
20#[derive(Clone, Debug)]
21pub struct Update {
22    /// A collection of routes that have been withdrawn.
23    pub withdrawn_routes: Vec<NLRIEncoding>,
24
25    /// A collection of attributes associated with the announced routes.
26    pub attributes: Vec<PathAttribute>,
27
28    /// A collection of routes that are announced by the peer.
29    pub announced_routes: Vec<NLRIEncoding>,
30}
31
32impl Update {
33    /// docs
34    pub fn parse(
35        header: &Header,
36        stream: &mut impl Read,
37        capabilities: &Capabilities,
38    ) -> Result<Update, Error> {
39        if header.length < 23 {
40            return Err(Error::new(
41                ErrorKind::Other,
42                format!("Header had bogus length {} < 23", header.length),
43            ));
44        }
45        let mut nlri_length: usize = header.length as usize - 23;
46
47        // ----------------------------
48        // Read withdrawn routes.
49        // ----------------------------
50        let withdraw_len = stream.read_u16::<BigEndian>()? as usize;
51        if withdraw_len > nlri_length {
52            return Err(Error::new(
53                ErrorKind::Other,
54                format!(
55                    "Got bogus withdraw length {} < msg len {}",
56                    withdraw_len, nlri_length
57                ),
58            ));
59        }
60        let mut buffer = vec![0; withdraw_len];
61        stream.read_exact(&mut buffer)?;
62        nlri_length -= withdraw_len;
63
64        let mut withdrawn_routes: Vec<NLRIEncoding> = Vec::with_capacity(0);
65        let mut cursor = Cursor::new(buffer);
66
67        if capabilities.EXTENDED_PATH_NLRI_SUPPORT {
68            while cursor.position() < withdraw_len as u64 {
69                let path_id = cursor.read_u32::<BigEndian>()?;
70                let prefix = Prefix::parse(&mut cursor, AFI::IPV4)?;
71                withdrawn_routes.push(NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id)));
72            }
73        } else {
74            while cursor.position() < withdraw_len as u64 {
75                withdrawn_routes.push(NLRIEncoding::IP(Prefix::parse(&mut cursor, AFI::IPV4)?));
76            }
77        }
78
79        // ----------------------------
80        // Read path attributes
81        // ----------------------------
82        let length = stream.read_u16::<BigEndian>()? as usize;
83        if length > nlri_length {
84            return Err(Error::new(
85                ErrorKind::Other,
86                format!(
87                    "Got bogus attributes length {} < msg len {} - withdraw len {}",
88                    length, nlri_length, withdraw_len
89                ),
90            ));
91        }
92        let mut buffer = vec![0; length];
93        stream.read_exact(&mut buffer)?;
94        nlri_length -= length;
95
96        let mut attributes: Vec<PathAttribute> = Vec::with_capacity(8);
97        let mut cursor = Cursor::new(buffer);
98        while cursor.position() < length as u64 {
99            let attribute = match PathAttribute::parse(&mut cursor, capabilities) {
100                Ok(a) => a,
101                Err(e) => match e.kind() {
102                    ErrorKind::UnexpectedEof => return Err(e),
103                    _ => continue,
104                },
105            };
106            attributes.push(attribute);
107        }
108
109        // ----------------------------
110        // Read NLRI
111        // ----------------------------
112        let mut buffer = vec![0; nlri_length as usize];
113
114        stream.read_exact(&mut buffer)?;
115        let mut cursor = Cursor::new(buffer);
116        let mut announced_routes: Vec<NLRIEncoding> = Vec::with_capacity(4);
117
118        while cursor.position() < nlri_length as u64 {
119            if util::detect_add_path_prefix(&mut cursor, 32)? {
120                let path_id = cursor.read_u32::<BigEndian>()?;
121                let prefix = Prefix::parse(&mut cursor, AFI::IPV4)?;
122                announced_routes.push(NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id)));
123            } else {
124                announced_routes.push(NLRIEncoding::IP(Prefix::parse(&mut cursor, AFI::IPV4)?));
125            }
126        }
127
128        Ok(Update {
129            withdrawn_routes,
130            attributes,
131            announced_routes,
132        })
133    }
134
135    /// Update message to bytes
136    pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> {
137        // Create one buf to reuse for each Update attribute
138        let mut temp_buf: Vec<u8> = Vec::with_capacity(8);
139
140        let mut unreach_nlri: HashMap<(AFI, SAFI), Vec<NLRIEncoding>> = HashMap::new();
141        for withdrawal in &self.withdrawn_routes {
142            if withdrawal.is_ipv4() {
143                withdrawal.encode(&mut temp_buf)?;
144            } else {
145                // Encode into MP_UNREACH_NLRI
146                let nlris = unreach_nlri
147                    .entry((withdrawal.afi(), withdrawal.safi()))
148                    .or_insert_with(Vec::new);
149                nlris.push(withdrawal.clone());
150            }
151        }
152        buf.write_u16::<BigEndian>(temp_buf.len() as u16)?;
153        buf.write_all(&temp_buf)?;
154        temp_buf.clear();
155
156        // Path Attributes
157        for attribute in &self.attributes {
158            attribute.encode(&mut temp_buf)?;
159        }
160        for ((afi, safi), unreach_nlris) in unreach_nlri.into_iter() {
161            let pa = PathAttribute::MP_UNREACH_NLRI(MPUnreachNLRI {
162                afi,
163                safi,
164                withdrawn_routes: unreach_nlris,
165            });
166            pa.encode(&mut temp_buf)?;
167        }
168        buf.write_u16::<BigEndian>(temp_buf.len() as u16)?;
169        buf.write_all(&temp_buf)?;
170        temp_buf.clear();
171
172        // NLRI
173        for route in &self.announced_routes {
174            route.encode(&mut temp_buf)?;
175        }
176        buf.write_all(&temp_buf)
177    }
178
179    /// Retrieves the first PathAttribute that matches the given identifier.
180    pub fn get(&self, identifier: Identifier) -> Option<&PathAttribute> {
181        for a in &self.attributes {
182            if a.id() == identifier {
183                return Some(a);
184            }
185        }
186        None
187    }
188
189    /// Checks if this UPDATE message contains announced prefixes.
190    pub fn is_announcement(&self) -> bool {
191        if !self.announced_routes.is_empty() || self.get(Identifier::MP_REACH_NLRI).is_some() {
192            return true;
193        }
194        false
195    }
196
197    /// Checks if this UPDATE message contains withdrawn routes..
198    pub fn is_withdrawal(&self) -> bool {
199        if !self.withdrawn_routes.is_empty() || self.get(Identifier::MP_UNREACH_NLRI).is_some() {
200            return true;
201        }
202        false
203    }
204
205    /// Moves the MP_REACH and MP_UNREACH NLRI into the NLRI.
206    pub fn normalize(&mut self) {
207        // Move the MP_REACH_NLRI attribute in the NLRI.
208        let identifier = match self.get(Identifier::MP_REACH_NLRI) {
209            Some(PathAttribute::MP_REACH_NLRI(routes)) => Some(routes.announced_routes.clone()),
210            _ => None,
211        };
212        if let Some(routes) = identifier {
213            self.announced_routes.extend(routes)
214        }
215
216        // Move the MP_REACH_NLRI attribute in the NLRI.
217        let identifier = match self.get(Identifier::MP_UNREACH_NLRI) {
218            Some(PathAttribute::MP_UNREACH_NLRI(routes)) => Some(routes.withdrawn_routes.clone()),
219            _ => None,
220        };
221        if let Some(routes) = identifier {
222            self.withdrawn_routes.extend(routes)
223        }
224    }
225}
226
227/// Represents NLRIEncodings present in the NRLI section of an UPDATE message.
228#[derive(Debug, Clone, Eq, PartialEq)]
229#[allow(non_camel_case_types)]
230pub enum NLRIEncoding {
231    /// Encodings that specify only an IP present, either IPv4 or IPv6
232    IP(Prefix),
233
234    /// Encodings that specify a Path Identifier as specified in RFC7911. (Prefix, Path ID)
235    IP_WITH_PATH_ID((Prefix, u32)),
236
237    /// Encodings with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label)
238    IP_MPLS((Prefix, u32)),
239
240    /// Encodings with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label, Path ID)
241    IP_MPLS_WITH_PATH_ID((Prefix, u32, u32)),
242
243    /// Encodings for VPNs with a labeled nexthop as specified in RFC8277. (Prefix, MPLS Label)
244    IP_VPN_MPLS((u64, Prefix, u32)),
245
246    /// Encodings that specify a VPLS endpoint as specified in RFC4761. (RD, VE ID, Label Block Offset, Label Block Size, Label Base)
247    L2VPN((u64, u16, u16, u16, u32)),
248
249    /// Flowspec Traffic Filter Specification - RFC5575
250    #[cfg(feature = "flowspec")]
251    FLOWSPEC(Vec<FlowspecFilter>),
252}
253
254impl NLRIEncoding {
255    /// Check if this is a normal IPv4 NLRI for Update encoding
256    pub fn is_ipv4(&self) -> bool {
257        if let NLRIEncoding::IP(prefix) = &self {
258            prefix.protocol == AFI::IPV4
259        } else {
260            false
261        }
262    }
263
264    /// Derive the AFI for this NLRI
265    pub fn afi(&self) -> AFI {
266        use NLRIEncoding::*;
267        match &self {
268            IP(prefix) => prefix.protocol,
269            #[cfg(feature = "flowspec")]
270            FLOWSPEC(_) => AFI::IPV4, // TODO: match ipv6 from filters
271            _ => unimplemented!(),
272        }
273    }
274
275    /// Derive the SAFI for this NLRI
276    pub fn safi(&self) -> SAFI {
277        use NLRIEncoding::*;
278        match &self {
279            IP(_) => SAFI::Unicast,
280            #[cfg(feature = "flowspec")]
281            FLOWSPEC(_) => SAFI::Flowspec,
282            _ => unimplemented!(),
283        }
284    }
285
286    /// Encode NLRI to bytes
287    pub fn encode(&self, buf: &mut impl Write) -> Result<(), Error> {
288        match self {
289            NLRIEncoding::IP(prefix) => {
290                buf.write_u8(prefix.length)?;
291                buf.write_all(&prefix.masked_octets())
292            }
293            NLRIEncoding::IP_WITH_PATH_ID((prefix, path_id)) => {
294                buf.write_u32::<BigEndian>(*path_id)?;
295                buf.write_u8(prefix.length)?;
296                buf.write_all(&prefix.masked_octets())
297            }
298            NLRIEncoding::IP_VPN_MPLS((rd, prefix, label)) => {
299                // TODO: the parsing in nlri.rs may not be correct
300                buf.write_u32::<BigEndian>(*label)?;
301                buf.write_u64::<BigEndian>(*rd)?;
302                buf.write_all(&prefix.prefix)
303            }
304            #[cfg(feature = "flowspec")]
305            NLRIEncoding::FLOWSPEC(filters) => {
306                let mut bytes: Vec<u8> = Vec::with_capacity(16);
307                for filter in filters {
308                    filter.encode(&mut bytes)?;
309                }
310                buf.write_u8(bytes.len() as u8)?;
311                buf.write_all(&bytes)
312            }
313            _ => unimplemented!("{:?}", self),
314        }
315    }
316}
317
318/// Represents a generic prefix. For example an IPv4 prefix or IPv6 prefix.
319#[derive(Clone, Eq, PartialEq)]
320pub struct Prefix {
321    /// IP version for prefix (v4|v6)
322    pub protocol: AFI,
323    /// Prefix Mask length in bits
324    pub length: u8,
325    /// Prefix Octets
326    pub prefix: Vec<u8>,
327}
328
329impl From<&Prefix> for IpAddr {
330    fn from(prefix: &Prefix) -> Self {
331        match prefix.protocol {
332            AFI::IPV4 => {
333                let mut buffer: [u8; 4] = [0; 4];
334                buffer[..prefix.prefix.len()].clone_from_slice(&prefix.prefix[..]);
335                IpAddr::from(buffer)
336            }
337            AFI::IPV6 => {
338                let mut buffer: [u8; 16] = [0; 16];
339                buffer[..prefix.prefix.len()].clone_from_slice(&prefix.prefix[..]);
340                IpAddr::from(buffer)
341            }
342            AFI::L2VPN => unimplemented!(),
343            AFI::BGPLS => unimplemented!(),
344        }
345    }
346}
347
348impl From<&Prefix> for (IpAddr, u8) {
349    /// Convert from IpAddr/CIDR to Prefix
350    /// ```
351    /// use std::net::{IpAddr, Ipv4Addr};
352    /// use bgp_rs::Prefix;
353    /// let prefix: Prefix = ("5.5.5.5".parse().unwrap(), 32).into();
354    /// let (addr, length) = (&prefix).into();
355    /// assert_eq!(addr, IpAddr::from(Ipv4Addr::new(5, 5, 5, 5)));
356    /// assert_eq!(length, 32);
357    /// ```
358    fn from(prefix: &Prefix) -> (IpAddr, u8) {
359        (IpAddr::from(prefix), prefix.length)
360    }
361}
362
363impl From<(IpAddr, u8)> for Prefix {
364    /// Convert from IpAddr/CIDR to Prefix
365    /// ```
366    /// use bgp_rs::Prefix;
367    /// let prefix: Prefix = ("5.5.5.5".parse().unwrap(), 32).into();
368    /// assert_eq!(prefix.length, 32);
369    /// assert_eq!(prefix.prefix, vec![5, 5, 5, 5]);
370    /// ```
371    fn from(prefix: (IpAddr, u8)) -> Prefix {
372        let (protocol, octets) = match prefix.0 {
373            IpAddr::V4(v4) => (AFI::IPV4, v4.octets().to_vec()),
374            IpAddr::V6(v6) => (AFI::IPV6, v6.octets().to_vec()),
375        };
376        Prefix {
377            protocol,
378            length: prefix.1,
379            prefix: octets,
380        }
381    }
382}
383
384impl Display for Prefix {
385    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
386        write!(f, "{}/{}", IpAddr::from(self), self.length)
387    }
388}
389
390impl Debug for Prefix {
391    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
392        write!(f, "{}/{}", IpAddr::from(self), self.length)
393    }
394}
395
396impl Prefix {
397    fn new(protocol: AFI, length: u8, prefix: Vec<u8>) -> Self {
398        Self {
399            protocol,
400            length,
401            prefix,
402        }
403    }
404
405    fn octet_length(&self) -> usize {
406        (self.length as usize + 7) / 8
407    }
408
409    /// Get a slice of the prefix octets covered by the prefix mask
410    /// Useful for encoding the prefix in NLRI
411    pub fn masked_octets(&self) -> &[u8] {
412        &self.prefix[..self.octet_length()]
413    }
414
415    fn parse(stream: &mut impl Read, protocol: AFI) -> Result<Prefix, Error> {
416        let length = stream.read_u8()?;
417
418        if length
419            > match protocol {
420                AFI::IPV4 => 32,
421                AFI::IPV6 => 128,
422                AFI::L2VPN => unimplemented!(),
423                AFI::BGPLS => unimplemented!(),
424            }
425        {
426            return Err(Error::new(
427                ErrorKind::Other,
428                format!("Bogus prefix length {}", length),
429            ));
430        }
431
432        let mut prefix: Vec<u8> = vec![0; ((length + 7) / 8) as usize];
433        stream.read_exact(&mut prefix)?;
434
435        Ok(Prefix {
436            protocol,
437            length,
438            prefix,
439        })
440    }
441}
442
443#[test]
444fn test_prefix_masked_octets() {
445    let prefix = Prefix::new(AFI::IPV4, 32, vec![1, 1, 1, 1]);
446    assert_eq!(prefix.masked_octets(), &[1, 1, 1, 1]);
447    assert_eq!(&prefix.to_string(), "1.1.1.1/32");
448
449    let prefix = Prefix::new(AFI::IPV4, 16, vec![1, 1, 1, 1]);
450    assert_eq!(prefix.masked_octets(), &[1, 1]);
451    assert_eq!(&prefix.to_string(), "1.1.1.1/16");
452
453    let prefix = Prefix::new(AFI::IPV4, 18, vec![1, 1, 1, 1]);
454    assert_eq!(prefix.masked_octets(), &[1, 1, 1]);
455    assert_eq!(&prefix.to_string(), "1.1.1.1/18");
456}