//! Path attributes: what an UPDATE says about the routes it announces //! (RFC 4271 §5 and those that added to it), each with how it is read //! from the wire and written to it. //! //! Everything here is re-exported from [`crate::message`], which is where //! the rest of the tree names it from. use std::net::{Ipv4Addr, Ipv6Addr}; use bitfield::bitfield; use bytes::{BufMut, BytesMut}; use nom::Err::Failure; use nom::number::complete::{be_u8, be_u16, be_u32}; use nom::{IResult, Parser}; use serde::Serialize; use serde_repr::Serialize_repr; use bgp_primitives::constants::{Afi, AfiSafi, IpVersion, Safi}; use bgp_primitives::ip_prefix::{IpPrefix, Nlri}; use bgp_primitives::parser::{BgpParserError, EncodeError, ParserContext}; use crate::message::{AS_TRANS, write_nlri}; bitfield! { #[derive(Clone, Debug, Serialize, PartialEq, Eq)] pub struct PathAttributeFlags(u8); impl new; u8; pub optional, set_optional: 7; pub transitive, set_transitive: 6; pub partial, set_partial: 5; pub extended_length, set_extended_length: 4; } /// Path attribute type codes. pub mod attr_type { pub const ORIGIN: u8 = 1; pub const AS_PATH: u8 = 2; pub const NEXT_HOP: u8 = 3; pub const MULTI_EXIT_DISC: u8 = 4; pub const LOCAL_PREF: u8 = 5; pub const ATOMIC_AGGREGATE: u8 = 6; pub const AGGREGATOR: u8 = 7; pub const COMMUNITIES: u8 = 8; pub const ORIGINATOR_ID: u8 = 9; pub const CLUSTER_LIST: u8 = 10; pub const MP_REACH_NLRI: u8 = 14; pub const MP_UNREACH_NLRI: u8 = 15; pub const EXTENDED_COMMUNITIES: u8 = 16; pub const AS4_PATH: u8 = 17; pub const AS4_AGGREGATOR: u8 = 18; pub const LARGE_COMMUNITIES: u8 = 32; pub const ONLY_TO_CUSTOMER: u8 = 35; } #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub enum PathAttribute { Origin(OriginPathAttribute), AsPath(AsPathAttribute), NextHop(NextHopPathAttribute), MultiExitDisc(MultiExitDiscPathAttribute), LocalPref(LocalPrefPathAttribute), AtomicAggregate(AtomicAggregatePathAttribute), Aggregator(AggregatorPathAttribute), Communities(CommunitiesPathAttribute), MpReachNlri(MpReachNlriPathAttribute), MpUnreachNlri(MpUnreachNlriPathAttribute), ExtendedCommunities(ExtendedCommunitiesPathAttribute), /// The AS path with four-octet AS numbers, for a route that crosses /// speakers that only handle two-octet ones (RFC 6793 §3). As4Path(AsPathAttribute), LargeCommunities(LargeCommunitiesPathAttribute), /// Only to Customer (RFC 9234 §5): the route has been handed to a /// customer, a peer or a route server client by the AS with this /// number, and from there on may only go down to customers. OnlyToCustomer(u32), /// An attribute this implementation does not interpret, kept as it /// came so that it can be passed on. UnknownPathAttribute { flags: PathAttributeFlags, type_code: u8, payload: Vec, }, } /// The Optional and Transitive flags that the attribute with this type code /// is defined to have, for the attributes whose definition we know. A /// received attribute with other values is in error (RFC 7606 §3.c). pub(crate) fn defined_flags(type_code: u8) -> Option<(bool, bool)> { use attr_type::*; const WELL_KNOWN: (bool, bool) = (false, true); const OPTIONAL_TRANSITIVE: (bool, bool) = (true, true); const OPTIONAL_NON_TRANSITIVE: (bool, bool) = (true, false); Some(match type_code { ORIGIN | AS_PATH | NEXT_HOP | LOCAL_PREF | ATOMIC_AGGREGATE => WELL_KNOWN, MULTI_EXIT_DISC | ORIGINATOR_ID | CLUSTER_LIST | MP_REACH_NLRI | MP_UNREACH_NLRI => { OPTIONAL_NON_TRANSITIVE } AGGREGATOR | COMMUNITIES | EXTENDED_COMMUNITIES | AS4_PATH | AS4_AGGREGATOR | LARGE_COMMUNITIES | ONLY_TO_CUSTOMER => OPTIONAL_TRANSITIVE, _ => return None, }) } /// A parsed value, provided the parser used all of it. fn whole((rest, parsed): (&[u8], T)) -> Result>> { if rest.is_empty() { Ok(parsed) } else { Err(Failure(BgpParserError::Malformed(format!( "{} octets left over", rest.len() )))) } } /// One attribute as it lies on the wire, split out but not interpreted. #[derive(Debug, Clone)] pub struct RawAttribute<'a> { pub flags: PathAttributeFlags, pub type_code: u8, pub value: &'a [u8], /// The whole attribute, header and value. pub wire: &'a [u8], } /// Why the bytes at the front of a buffer are not an attribute. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NotAnAttribute { /// Too few octets for flags, type and length. NoHeader, /// The length says more than there is. Truncated { type_code: u8, length: usize, available: usize, }, } impl std::fmt::Display for NotAnAttribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NoHeader => f.write_str("too few octets for an attribute"), Self::Truncated { type_code, length, available, } => write!( f, "attribute {type_code} has length {length} but only {available} octets remain" ), } } } /// Splits the attribute at the front of `buf` from what follows it. pub fn split_attribute(buf: &[u8]) -> Result<(RawAttribute<'_>, &[u8]), NotAnAttribute> { let [flags, type_code, rest @ ..] = buf else { return Err(NotAnAttribute::NoHeader); }; let flags = PathAttributeFlags(*flags); let (length, rest) = match (flags.extended_length(), rest) { (true, [hi, lo, rest @ ..]) => (usize::from(u16::from_be_bytes([*hi, *lo])), rest), (false, [length, rest @ ..]) => (usize::from(*length), rest), _ => return Err(NotAnAttribute::NoHeader), }; let Some((value, rest)) = rest.split_at_checked(length) else { return Err(NotAnAttribute::Truncated { type_code: *type_code, length, available: rest.len(), }); }; let raw = RawAttribute { flags, type_code: *type_code, value, wire: &buf[..buf.len() - rest.len()], }; Ok((raw, rest)) } impl PathAttribute { pub fn type_code(&self) -> u8 { use attr_type::*; match self { Self::Origin(_) => ORIGIN, Self::AsPath(_) => AS_PATH, Self::NextHop(_) => NEXT_HOP, Self::MultiExitDisc(_) => MULTI_EXIT_DISC, Self::LocalPref(_) => LOCAL_PREF, Self::AtomicAggregate(_) => ATOMIC_AGGREGATE, Self::Aggregator(_) => AGGREGATOR, Self::Communities(_) => COMMUNITIES, Self::MpReachNlri(_) => MP_REACH_NLRI, Self::MpUnreachNlri(_) => MP_UNREACH_NLRI, Self::ExtendedCommunities(_) => EXTENDED_COMMUNITIES, Self::As4Path(_) => AS4_PATH, Self::LargeCommunities(_) => LARGE_COMMUNITIES, Self::OnlyToCustomer(_) => ONLY_TO_CUSTOMER, Self::UnknownPathAttribute { type_code, .. } => *type_code, } } /// The flags to send the attribute with, but for Extended Length, which /// depends on how long the value turns out to be. fn flags(&self) -> PathAttributeFlags { match self { // As they came: the Partial bit has to survive (RFC 4271 §5). Self::UnknownPathAttribute { flags, .. } => flags.clone(), known => { let (optional, transitive) = defined_flags(known.type_code()) .expect("every attribute with a variant has defined flags"); PathAttributeFlags::new(optional, transitive, false, false) } } } /// Parses one attribute, header and value, from the front of `buf`. /// The flags are not checked against the attribute's definition; /// [`UpdateMessage::decode`] does that for attributes off a session. pub fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> { let (raw, rest) = split_attribute(buf).map_err(|e| Failure(BgpParserError::Malformed(e.to_string())))?; let attribute = Self::parse_value(ctx, raw.flags, raw.type_code, raw.value)?; Ok((rest, attribute)) } /// Makes the attribute with this type code from its value. `value` is /// exactly the value, and all of it has to be used. A type code without /// a variant of its own is kept as bytes. pub(crate) fn parse_value<'a>( ctx: &ParserContext, flags: PathAttributeFlags, type_code: u8, value: &'a [u8], ) -> Result>> { use attr_type::*; Ok(match type_code { ORIGIN => Self::Origin(whole(OriginPathAttribute::from_wire(value)?)?), AS_PATH => Self::AsPath(whole(AsPathAttribute::from_wire(ctx, value)?)?), NEXT_HOP => Self::NextHop(whole(NextHopPathAttribute::from_wire(value)?)?), MULTI_EXIT_DISC => { Self::MultiExitDisc(whole(MultiExitDiscPathAttribute::from_wire(value)?)?) } LOCAL_PREF => Self::LocalPref(whole(LocalPrefPathAttribute::from_wire(value)?)?), ATOMIC_AGGREGATE => { Self::AtomicAggregate(whole((value, AtomicAggregatePathAttribute {}))?) } AGGREGATOR => Self::Aggregator(whole(AggregatorPathAttribute::from_wire(ctx, value)?)?), COMMUNITIES => Self::Communities(whole(CommunitiesPathAttribute::from_wire(value)?)?), MP_REACH_NLRI => { Self::MpReachNlri(whole(MpReachNlriPathAttribute::from_wire(ctx, value)?)?) } MP_UNREACH_NLRI => { Self::MpUnreachNlri(whole(MpUnreachNlriPathAttribute::from_wire(ctx, value)?)?) } EXTENDED_COMMUNITIES => Self::ExtendedCommunities(whole( ExtendedCommunitiesPathAttribute::from_wire(value)?, )?), // Always four octets wide, whatever the session's AS_PATH is. AS4_PATH => Self::As4Path(whole(AsPathAttribute::from_wire_with_width(true, value)?)?), LARGE_COMMUNITIES => { Self::LargeCommunities(whole(LargeCommunitiesPathAttribute::from_wire(value)?)?) } ONLY_TO_CUSTOMER => { let asn = <[u8; 4]>::try_from(value).map_err(|_| { malformed(format!( "Only to Customer is {} octets long, not four", value.len() )) })?; Self::OnlyToCustomer(u32::from_be_bytes(asn)) } _ => Self::UnknownPathAttribute { flags, type_code, payload: value.to_vec(), }, }) } /// Writes the attribute, header and value, to the end of `out`. pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { // A header with a one-octet length first; whether the value needs // the two-octet one is known once it is written. let start = out.len(); let mut flags = self.flags(); out.put_u8(0); out.put_u8(self.type_code()); out.put_u8(0); let value_start = out.len(); self.value_to_wire(ctx, out)?; let length = out.len() - value_start; if let Ok(short) = u8::try_from(length) && !flags.extended_length() { out[start + 2] = short; } else { let long = u16::try_from(length).map_err(|_| EncodeError::TooLong { what: "path attribute", len: length, max: u16::MAX.into(), })?; flags.set_extended_length(true); // Move the value up by one octet to make room. out.put_u8(0); out.copy_within(value_start..value_start + length, value_start + 1); out[start + 2..start + 4].copy_from_slice(&long.to_be_bytes()); } out[start] = flags.0; Ok(()) } fn value_to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { match self { Self::Origin(a) => a.to_wire(out), Self::AsPath(a) => a.to_wire(ctx, out)?, Self::NextHop(a) => a.to_wire(out), Self::MultiExitDisc(a) => a.to_wire(out), Self::LocalPref(a) => a.to_wire(out), Self::AtomicAggregate(_) => {} Self::Aggregator(a) => a.to_wire(ctx, out), Self::Communities(a) => a.to_wire(out), Self::MpReachNlri(a) => a.to_wire(ctx, out), Self::MpUnreachNlri(a) => a.to_wire(ctx, out), Self::ExtendedCommunities(a) => a.to_wire(out), Self::As4Path(a) => a.to_wire_with_width(true, out)?, Self::LargeCommunities(a) => a.to_wire(out), Self::OnlyToCustomer(asn) => out.put_u32(*asn), Self::UnknownPathAttribute { payload, .. } => out.put_slice(payload), } Ok(()) } } /// An Only to Customer attribute with this AS number, as it is on the /// wire: for adding to attributes that are kept as bytes. pub fn only_to_customer_wire(asn: u32) -> [u8; 7] { const OPTIONAL_TRANSITIVE: u8 = 0xc0; let [a, b, c, d] = asn.to_be_bytes(); [ OPTIONAL_TRANSITIVE, attr_type::ONLY_TO_CUSTOMER, 4, a, b, c, d, ] } type Parsed<'a, T> = IResult<&'a [u8], T, BgpParserError<&'a [u8]>>; fn malformed<'a>(detail: String) -> nom::Err> { Failure(BgpParserError::Malformed(detail)) } /// ORIGIN: how the route came into BGP (RFC 4271 §5.1.1). #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize_repr)] #[repr(u8)] pub enum OriginPathAttribute { IGP = 0, EGP = 1, INCOMPLETE = 2, } impl OriginPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, byte) = be_u8(buf)?; let origin = match byte { 0 => Self::IGP, 1 => Self::EGP, 2 => Self::INCOMPLETE, other => return Err(malformed(format!("unknown ORIGIN {other}"))), }; Ok((buf, origin)) } pub fn to_wire(&self, out: &mut BytesMut) { out.put_u8(*self as u8); } } /// AS_PATH: the ASes a route has passed through, as a list of segments /// (RFC 4271 §4.3). Each segment is a type (1 for AS_SET, 2 for /// AS_SEQUENCE), a count in one octet, and that many AS numbers. #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct AsPathAttribute { pub segments: Vec, } #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct AsPathSegment { /// True for an AS_SEQUENCE, false for an AS_SET. pub ordered: bool, pub path: Vec, } impl AsPathAttribute { pub fn from_asns(asns: Vec) -> PathAttribute { let segment = AsPathSegment { ordered: true, path: asns, }; PathAttribute::AsPath(AsPathAttribute { segments: vec![segment], }) } /// How long the path is, as RFC 4271 §9.1.2.2 counts: one for each AS /// of a sequence, one for a whole set. pub fn hops(&self) -> usize { self.segments .iter() .map(|segment| { if segment.ordered { segment.path.len() } else { 1 } }) .sum() } /// The path as it really is, given what a two-octet session carried: /// this, with AS_TRANS wherever a number did not fit, and `wide`, the /// AS4_PATH that has the numbers themselves for the path's tail /// (RFC 6793 §4.2.3). What `wide` does not cover is kept from the /// front of this. /// /// `None` if `wide` is longer than the path, which is not to be used: /// some speaker on the way that knew nothing of AS4_PATH changed the /// one and not the other. pub fn with_as4_path(&self, wide: &AsPathAttribute) -> Option { let mut keep = self.hops().checked_sub(wide.hops())?; let mut segments = Vec::with_capacity(self.segments.len() + wide.segments.len()); for segment in &self.segments { if keep == 0 { break; } let (hops, taken) = if segment.ordered { let taken = segment.path.len().min(keep); (taken, &segment.path[..taken]) } else { (1, &segment.path[..]) }; keep -= hops; segments.push(AsPathSegment { ordered: segment.ordered, path: taken.to_vec(), }); } segments.extend(wide.segments.iter().cloned()); Some(AsPathAttribute { segments }) } /// Parses an AS_PATH with AS numbers as wide as the session negotiated /// (RFC 6793). pub fn from_wire<'a>(ctx: &ParserContext, buf: &'a [u8]) -> Parsed<'a, Self> { Self::from_wire_with_width(ctx.four_octet_asn, buf) } fn from_wire_with_width(four_octet: bool, buf: &[u8]) -> Parsed<'_, Self> { let parse_segment = |buf| -> Parsed<'_, AsPathSegment> { let (buf, typ) = be_u8(buf)?; let (buf, len) = be_u8(buf)?; // AS_SET and AS_SEQUENCE. The confederation segment types of // RFC 5065 are an error from a peer outside a confederation, // and this speaker is never inside one. if typ != 1 && typ != 2 { return Err(malformed(format!( "unrecognised AS path segment type {typ}" ))); } if len == 0 { return Err(malformed("AS path segment with no AS numbers".into())); } let count = usize::from(len); let (buf, path): (_, Vec) = if four_octet { nom::multi::many_m_n(count, count, be_u32).parse(buf)? } else { nom::multi::many_m_n(count, count, be_u16) .parse(buf) .map(|(buf, asns)| (buf, asns.into_iter().map(u32::from).collect()))? }; Ok(( buf, AsPathSegment { ordered: typ == 2, path, }, )) }; // Not many0: that stops quietly at a segment it cannot read, and a // truncated segment is an error. let mut segments = Vec::new(); let mut buf = buf; while !buf.is_empty() { let (rest, segment) = parse_segment(buf)?; segments.push(segment); buf = rest; } Ok((buf, Self { segments })) } /// Writes the path with AS numbers as wide as the session negotiated. /// On a two-octet session an AS number that does not fit is written as /// AS_TRANS (RFC 6793 §4.2.2); [`AsPathAttribute::as4_path`] is what /// carries the real numbers alongside. pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { self.to_wire_with_width(ctx.four_octet_asn, out) } fn to_wire_with_width(&self, four_octet: bool, out: &mut BytesMut) -> Result<(), EncodeError> { for segment in &self.segments { out.put_u8(if segment.ordered { 2 } else { 1 }); let count = u8::try_from(segment.path.len()).map_err(|_| EncodeError::TooLong { what: "AS path segment", len: segment.path.len(), max: u8::MAX.into(), })?; out.put_u8(count); for asn in &segment.path { if four_octet { out.put_u32(*asn); } else { out.put_u16(u16::try_from(*asn).unwrap_or(AS_TRANS)); } } } Ok(()) } /// The AS4_PATH to send with this path on a two-octet session, if one is /// needed: it is when some AS number does not fit in two octets and so /// goes out as AS_TRANS in the AS_PATH itself (RFC 6793 §4.2.2). pub fn as4_path(&self) -> Option { self.segments .iter() .flat_map(|segment| &segment.path) .any(|asn| u16::try_from(*asn).is_err()) .then(|| PathAttribute::As4Path(self.clone())) } } #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct NextHopPathAttribute(pub Ipv4Addr); impl NextHopPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, address) = be_u32(buf)?; Ok((buf, Self(Ipv4Addr::from(address)))) } pub fn to_wire(&self, out: &mut BytesMut) { out.put_u32(self.0.into()); } } #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct MultiExitDiscPathAttribute(pub u32); impl MultiExitDiscPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, value) = be_u32(buf)?; Ok((buf, Self(value))) } pub fn to_wire(&self, out: &mut BytesMut) { out.put_u32(self.0); } } #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct LocalPrefPathAttribute(pub u32); impl LocalPrefPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, value) = be_u32(buf)?; Ok((buf, Self(value))) } pub fn to_wire(&self, out: &mut BytesMut) { out.put_u32(self.0); } } /// ATOMIC_AGGREGATE: a marker with no value (RFC 4271 §5.1.6). #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct AtomicAggregatePathAttribute {} #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct AggregatorPathAttribute { pub asn: u32, pub ip: Ipv4Addr, } impl AggregatorPathAttribute { /// The AS number is as wide as the session's AS numbers (RFC 6793), so /// the attribute is six or eight octets. pub fn from_wire<'a>(ctx: &ParserContext, buf: &'a [u8]) -> Parsed<'a, Self> { let (buf, asn) = if ctx.four_octet_asn { be_u32(buf)? } else { be_u16(buf).map(|(buf, asn)| (buf, u32::from(asn)))? }; let (buf, ip) = be_u32(buf)?; Ok(( buf, Self { asn, ip: Ipv4Addr::from(ip), }, )) } /// On a two-octet session an AS number that does not fit is written as /// AS_TRANS (RFC 6793 §4.2.2). pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) { if ctx.four_octet_asn { out.put_u32(self.asn); } else { out.put_u16(u16::try_from(self.asn).unwrap_or(AS_TRANS)); } out.put_u32(self.ip.into()); } } #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct CommunitiesPathAttribute(pub Vec<(u16, u16)>); impl CommunitiesPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { // At least one: an empty COMMUNITIES is malformed (RFC 7606 §7.8). let (buf, values) = nom::multi::many1(|i| (be_u16, be_u16).parse(i)).parse(buf)?; Ok((buf, CommunitiesPathAttribute(values))) } pub fn to_wire(&self, out: &mut BytesMut) { for value in &self.0 { out.put_u16(value.0); out.put_u16(value.1); } } } /// EXTENDED COMMUNITIES (RFC 4360). #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct ExtendedCommunitiesPathAttribute { pub extended_communities: Vec, } impl ExtendedCommunitiesPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (communities, rest) = buf.as_chunks::<8>(); if communities.is_empty() || !rest.is_empty() { return Err(malformed(format!( "{} octets are not a list of extended communities", buf.len() ))); } Ok(( rest, Self { extended_communities: communities.iter().copied().map(ExtendedCommunity).collect(), }, )) } pub fn to_wire(&self, out: &mut BytesMut) { for community in &self.extended_communities { out.put_slice(&community.0); } } } /// One extended community: eight octets, of which the first is the type /// and, for most types, the second a sub-type (RFC 4360 §2). How the rest /// divides into an administrator and a value depends on the type, and is /// left to whoever acts on a particular community. #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] pub struct ExtendedCommunity(pub [u8; 8]); impl ExtendedCommunity { pub fn typ(&self) -> u8 { self.0[0] } pub fn sub_typ(&self) -> u8 { self.0[1] } } #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct LargeCommunitiesPathAttribute { pub communities: Vec, } impl LargeCommunitiesPathAttribute { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, communities) = nom::multi::many1(LargeCommunity::from_wire).parse(buf)?; Ok((buf, Self { communities })) } pub fn to_wire(&self, out: &mut BytesMut) { for community in &self.communities { community.to_wire(out); } } } #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct LargeCommunity { pub global_admin: u32, pub data_1: u32, pub data_2: u32, } impl LargeCommunity { pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> { let (buf, global_admin) = be_u32(buf)?; let (buf, data_1) = be_u32(buf)?; let (buf, data_2) = be_u32(buf)?; Ok(( buf, Self { global_admin, data_1, data_2, }, )) } pub fn to_wire(&self, out: &mut BytesMut) { out.put_u32(self.global_admin); out.put_u32(self.data_1); out.put_u32(self.data_2); } } /// The next hop of routes in MP_REACH_NLRI. #[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] pub enum NlriNextHop { Ipv4(Ipv4Addr), /// A global IPv6 address. Ipv6(Ipv6Addr), /// A global IPv6 address and a link-local one (RFC 2545 §3). Ipv6WithLl { global: Ipv6Addr, link_local: Ipv6Addr, }, } impl std::fmt::Display for NlriNextHop { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { NlriNextHop::Ipv4(addr) => write!(f, "{addr}"), NlriNextHop::Ipv6(addr) => write!(f, "{addr}"), NlriNextHop::Ipv6WithLl { global, link_local } => { write!(f, "{global} (link-local {link_local})") } } } } impl NlriNextHop { /// Reads a next hop that is all of `bytes`. `None` if they are not the /// length of one. pub fn from_bytes(bytes: &[u8]) -> Option { let v6 = |bytes: &[u8]| <[u8; 16]>::try_from(bytes).ok().map(Ipv6Addr::from); match bytes.len() { 4 => <[u8; 4]>::try_from(bytes) .ok() .map(|a| Self::Ipv4(a.into())), 16 => v6(bytes).map(Self::Ipv6), 32 => Some(Self::Ipv6WithLl { global: v6(&bytes[..16])?, link_local: v6(&bytes[16..])?, }), _ => None, } } pub fn to_wire(&self, out: &mut BytesMut) { match self { NlriNextHop::Ipv4(address) => out.put_slice(&address.octets()), NlriNextHop::Ipv6(address) => out.put_slice(&address.octets()), NlriNextHop::Ipv6WithLl { global, link_local } => { out.put_slice(&global.octets()); out.put_slice(&link_local.octets()); } } } /// How many octets the next hop is on the wire. pub fn wire_len(&self) -> u8 { match self { NlriNextHop::Ipv4(_) => 4, NlriNextHop::Ipv6(_) => 16, NlriNextHop::Ipv6WithLl { .. } => 32, } } } /// Parses one prefix as an UPDATE names it: with a path identifier in /// front if the session uses them for the family (RFC 7911 §3). fn parse_nlri(version: IpVersion, with_path_id: bool, buf: &[u8]) -> Parsed<'_, Nlri> { let (buf, path_id) = if with_path_id { be_u32(buf)? } else { (buf, 0) }; let (buf, prefix_len) = be_u8(buf)?; let byte_len = usize::from(prefix_len.div_ceil(8)); let (buf, prefix_bytes) = nom::bytes::take(byte_len).parse(buf)?; let prefix = IpPrefix::new(version, prefix_bytes, prefix_len).map_err(|e| malformed(e.to_string()))?; Ok((buf, Nlri { prefix, path_id })) } /// Parses a field that is nothing but prefixes, to its end. pub(crate) fn parse_nlri_list( version: IpVersion, with_path_id: bool, mut buf: &[u8], ) -> Parsed<'_, Vec> { // Not many0: that stops quietly at a prefix it cannot read. let mut nlri = Vec::new(); while !buf.is_empty() { let (rest, parsed) = parse_nlri(version, with_path_id, buf)?; nlri.push(parsed); buf = rest; } Ok((buf, nlri)) } /// The IP version of the prefixes that make up the NLRI of `family`. /// /// Any family can be named in MP_REACH_NLRI and MP_UNREACH_NLRI, but only /// unicast and multicast routes of IPv4 and IPv6 are plain prefixes, and /// those are the only NLRI this parser reads. A peer does not send routes /// of a family that was not negotiated, so the others do not come up. fn ip_version_of_nlri<'a>( family: AfiSafi, ) -> Result>> { let unsupported = || malformed(format!("NLRI of {family} is not supported")); if family.safi != Safi::UNICAST && family.safi != Safi::MULTICAST { return Err(unsupported()); } IpVersion::try_from(family.afi).map_err(|_| unsupported()) } fn parse_family(buf: &[u8]) -> Parsed<'_, AfiSafi> { let (buf, afi) = be_u16(buf)?; let (buf, safi) = be_u8(buf)?; Ok((buf, AfiSafi::new(Afi(afi), Safi(safi)))) } /// MP_REACH_NLRI: routes of a family other than the one in the message /// body, with their next hop (RFC 4760 §3). #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct MpReachNlriPathAttribute { pub family: AfiSafi, pub next_hop: NlriNextHop, pub prefixes: Vec, } impl MpReachNlriPathAttribute { pub fn from_wire<'a>(ctx: &ParserContext, buf: &'a [u8]) -> Parsed<'a, Self> { let (buf, family) = parse_family(buf)?; let version = ip_version_of_nlri(family)?; let (buf, next_hop_len) = be_u8(buf)?; let (buf, next_hop) = nom::bytes::take(usize::from(next_hop_len)).parse(buf)?; // For IPv6 routes, a global address, optionally followed by a // link-local one (RFC 2545). For IPv4 routes an IPv4 address, or // the same as for IPv6: routes over a link that has only IPv6 // addresses (RFC 8950). Which of the two it is shows in its length. let next_hop = NlriNextHop::from_bytes(next_hop) .filter(|next_hop| { version == IpVersion::Ipv4 || !matches!(next_hop, NlriNextHop::Ipv4(_)) }) .ok_or_else(|| { malformed(format!( "a next hop of {next_hop_len} octets is not valid for {family}" )) })?; // Reserved octet (formerly the SNPA count), for every family. let (buf, _) = be_u8(buf)?; let (buf, prefixes) = parse_nlri_list(version, ctx.has_add_path(family), buf)?; Ok(( buf, Self { family, next_hop, prefixes, }, )) } pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) { out.put_u16(self.family.afi.0); out.put_u8(self.family.safi.0); out.put_u8(self.next_hop.wire_len()); self.next_hop.to_wire(out); out.put_u8(0); write_nlri(&self.prefixes, ctx.has_add_path(self.family), out); } } /// MP_UNREACH_NLRI: withdrawals of routes of a family other than the one /// in the message body (RFC 4760 §4). #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct MpUnreachNlriPathAttribute { pub family: AfiSafi, pub prefixes: Vec, } impl MpUnreachNlriPathAttribute { pub fn from_wire<'a>(ctx: &ParserContext, buf: &'a [u8]) -> Parsed<'a, Self> { let (buf, family) = parse_family(buf)?; let version = ip_version_of_nlri(family)?; let (buf, prefixes) = parse_nlri_list(version, ctx.has_add_path(family), buf)?; Ok((buf, Self { family, prefixes })) } pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) { out.put_u16(self.family.afi.0); out.put_u8(self.family.safi.0); write_nlri(&self.prefixes, ctx.has_add_path(self.family), out); } }