use std::net::{Ipv4Addr, Ipv6Addr}; use bitfield::bitfield; use bytes::BufMut; use bytes::BytesMut; use nom::Err::Failure; use nom::IResult; use nom::Parser; use nom::number::complete::be_u8; use nom::number::complete::be_u16; use nom::number::complete::be_u32; use serde::Serialize; use serde_repr::Serialize_repr; use crate::update_decode::UpdateError; use bgp_primitives::constants::{Afi, AfiSafi, IpVersion, Safi}; use bgp_primitives::ip_prefix::{IpPrefix, Nlri}; use bgp_primitives::parser::BgpParserError; use bgp_primitives::parser::EncodeError; use bgp_primitives::parser::ParserContext; use bgp_primitives::parser::ProtocolErrorValues; use crate::errors::BgpError; use crate::errors::DecodeError; use crate::errors::MsgHeaderSubcode; use crate::errors::OpenMsgSubcode; use crate::open::OpenMessage; /// A sentinel AS number used to denote that the actual AS number is provided as /// a 4 byte value in the capabilities instead. pub const AS_TRANS: u16 = 23456; /// Version number used in the BGP4 protocol. pub const BGP4_VERSION: u8 = 4; /// Total length of the BGP message header (marker + length + type). pub const BGP_HEADER_LEN: u16 = 19; /// Maximum BGP message length per RFC 4271 §4.1. pub const BGP_MAX_MSG_LEN: u16 = 4096; const BGP_MARKER: [u8; 16] = [0xFF; 16]; /// BGP message type codes (RFC 4271 §4.1, RFC 2918). mod msg_type { pub const OPEN: u8 = 1; pub const UPDATE: u8 = 2; pub const NOTIFICATION: u8 = 3; pub const KEEPALIVE: u8 = 4; pub const ROUTE_REFRESH: u8 = 5; } /// Message represents the top-level messages in the BGP protocol. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub enum Message { Open(OpenMessage), Update(UpdateMessage), Notification(NotificationMessage), KeepAlive, /// BGP Route Refresh message (RFC 2918). RouteRefresh(RouteRefreshMessage), } fn header_error(subcode: MsgHeaderSubcode, data: Vec, detail: String) -> DecodeError { DecodeError { notification: BgpError::MsgHeader(subcode), data, detail, } } impl Message { /// Decodes one complete BGP message, header included, from the start of /// `buf`. Bytes after the message are ignored. /// /// A failure says which NOTIFICATION reports it, following RFC 4271 §6.1 /// for the header and §6.2 and §6.3 for OPEN and UPDATE bodies. /// /// An UPDATE comes back as it is to be acted on: errors that RFC 7606 /// handles without resetting the session have been handled. Use /// [`Message::decode_with_report`] to learn of them. pub fn decode(ctx: &ParserContext, buf: &[u8]) -> Result { Self::decode_with_report(ctx, buf).map(|decoded| decoded.message) } /// As [`Message::decode`], and also returns what else decoding an /// UPDATE found out: the errors in it that were handled without /// resetting the session, and its attributes as they were on the wire. pub fn decode_with_report(ctx: &ParserContext, buf: &[u8]) -> Result { let header_len = usize::from(BGP_HEADER_LEN); if buf.len() < header_len { return Err(header_error( MsgHeaderSubcode::BadMessageLength, vec![], format!("{} bytes is shorter than a BGP header", buf.len()), )); } if buf[..16] != BGP_MARKER { return Err(header_error( MsgHeaderSubcode::ConnNotSynchronized, vec![], "invalid BGP marker: expected 16 x 0xFF".into(), )); } let length = u16::from_be_bytes([buf[16], buf[17]]); let type_code = buf[18]; if !(BGP_HEADER_LEN..=BGP_MAX_MSG_LEN).contains(&length) || usize::from(length) > buf.len() { return Err(header_error( MsgHeaderSubcode::BadMessageLength, length.to_be_bytes().to_vec(), format!( "BGP message length {length} is not between {BGP_HEADER_LEN} and \ {BGP_MAX_MSG_LEN}, or exceeds the {} bytes received", buf.len() ), )); } let body = &buf[header_len..usize::from(length)]; let bad_length = |what: &str| { header_error( MsgHeaderSubcode::BadMessageLength, length.to_be_bytes().to_vec(), format!("{what} with a length of {length}"), ) }; let message = match type_code { msg_type::OPEN => Self::Open(decode_open(body)?), msg_type::UPDATE => { let decoded = UpdateMessage::decode(ctx, body)?; return Ok(Decoded { message: Self::Update(decoded.update), update_errors: decoded.errors, attributes_wire: decoded.attributes_wire, }); } msg_type::NOTIFICATION => match body { [code, subcode, data @ ..] => Self::Notification(NotificationMessage { error: BgpError::from_code_subcode(*code, *subcode), data: data.to_vec(), }), _ => return Err(bad_length("NOTIFICATION")), }, msg_type::KEEPALIVE if body.is_empty() => Self::KeepAlive, msg_type::KEEPALIVE => return Err(bad_length("KEEPALIVE with a body")), msg_type::ROUTE_REFRESH => match body { [afi_hi, afi_lo, subtype, safi] => Self::RouteRefresh(RouteRefreshMessage { afi: Afi(u16::from_be_bytes([*afi_hi, *afi_lo])), subtype: *subtype, safi: Safi(*safi), }), _ => return Err(bad_length("ROUTE-REFRESH")), }, other => { return Err(header_error( MsgHeaderSubcode::BadMessageType, vec![other], format!("unknown BGP message type {other}"), )); } }; Ok(Decoded { message, update_errors: Vec::new(), attributes_wire: None, }) } /// Writes the message, header and body, to the end of `out`. If it /// cannot be encoded, `out` is left as it was. pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { let start = out.len(); out.put_slice(&BGP_MARKER); // The length, filled in once the body is written. out.put_u16(0); out.put_u8(self.type_code()); let written = self.body_to_wire(ctx, out).and_then(|()| { let length = out.len() - start; u16::try_from(length) .ok() .filter(|length| *length <= BGP_MAX_MSG_LEN) .ok_or(EncodeError::TooLong { what: "BGP message", len: length, max: BGP_MAX_MSG_LEN.into(), }) }); match written { Ok(length) => { out[start + 16..start + 18].copy_from_slice(&length.to_be_bytes()); Ok(()) } Err(e) => { out.truncate(start); Err(e) } } } fn body_to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { match self { Self::Open(m) => m.to_wire(out), Self::Update(m) => m.to_wire(ctx, out), Self::Notification(m) => { m.to_wire(out); Ok(()) } Self::RouteRefresh(m) => { out.put_u16(m.afi.0); out.put_u8(m.subtype); out.put_u8(m.safi.0); Ok(()) } Self::KeepAlive => Ok(()), } } fn type_code(&self) -> u8 { match self { Self::Open(_) => msg_type::OPEN, Self::Update(_) => msg_type::UPDATE, Self::Notification(_) => msg_type::NOTIFICATION, Self::KeepAlive => msg_type::KEEPALIVE, Self::RouteRefresh(_) => msg_type::ROUTE_REFRESH, } } } /// Decodes the body of an OPEN, with the NOTIFICATION that reports a /// failure (RFC 4271 §6.2). fn decode_open(body: &[u8]) -> Result { let refused = |subcode, data: Vec, detail: String| DecodeError { notification: BgpError::OpenMsg(subcode), data, detail: format!("malformed OPEN: {detail}"), }; match OpenMessage::from_wire(body) { Ok(([], open)) => Ok(open), Ok((rest, _)) => Err(refused( OpenMsgSubcode::Unspecific, vec![], format!("{} octets after the optional parameters", rest.len()), )), Err(e) => { let (subcode, data) = match &e { nom::Err::Error(BgpParserError::ProtocolError(value)) | nom::Err::Failure(BgpParserError::ProtocolError(value)) => match value { // The Data field is the largest version we do support. ProtocolErrorValues::UnsupportedVersion(_) => ( OpenMsgSubcode::UnsupportedVersion, u16::from(BGP4_VERSION).to_be_bytes().to_vec(), ), ProtocolErrorValues::UnknownOpenOption(_) => { (OpenMsgSubcode::UnsupportedOptionalParam, vec![]) } }, _ => (OpenMsgSubcode::Unspecific, vec![]), }; Err(refused(subcode, data, describe(&e))) } } } /// A message, and what else decoding it found out. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Decoded { pub message: Message, /// The errors in an UPDATE that were handled without resetting the /// session (RFC 7606). Empty for a well-formed UPDATE and for every /// other message. pub update_errors: Vec, /// See [`crate::update_decode::DecodedUpdate::attributes_wire`]. pub attributes_wire: Option>, } /// A request to send again every route of one address family (RFC 2918). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] pub struct RouteRefreshMessage { pub afi: Afi, /// Zero for a plain request. RFC 7313 uses the octet, reserved in /// RFC 2918, to mark the beginning and end of a refresh. pub subtype: u8, pub safi: Safi, } impl RouteRefreshMessage { /// A plain request for the routes of `family`. pub fn request(family: AfiSafi) -> Self { Self { afi: family.afi, subtype: 0, safi: family.safi, } } pub fn family(&self) -> AfiSafi { AfiSafi::new(self.afi, self.safi) } } /// Represents a BGP Update message. #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub struct UpdateMessage { pub withdrawn_routes: Vec, pub path_attributes: Vec, pub announced_routes: Vec, } impl UpdateMessage { /// Writes the body of the UPDATE (what follows the 19-octet header). pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<(), EncodeError> { // The prefixes in the body are IPv4 unicast (RFC 4271 §4.3). let with_path_id = ctx.has_add_path(AfiSafi::IPV4_UNICAST); let withdrawn = reserve_length(out); write_nlri(&self.withdrawn_routes, with_path_id, out); fill_length(out, withdrawn, "withdrawn routes")?; let attributes = reserve_length(out); for attribute in &self.path_attributes { attribute.to_wire(ctx, out)?; } fill_length(out, attributes, "path attributes")?; // The NLRI take up the rest of the message. write_nlri(&self.announced_routes, with_path_id, out); Ok(()) } } /// Leaves room at the end of `out` for a two-octet length that is known /// only once what it counts has been written, and returns where. fn reserve_length(out: &mut BytesMut) -> usize { let at = out.len(); out.put_u16(0); at } /// Fills in the length reserved at `at`: everything written since. fn fill_length(out: &mut BytesMut, at: usize, what: &'static str) -> Result<(), EncodeError> { let length = out.len() - at - 2; let field = u16::try_from(length).map_err(|_| EncodeError::TooLong { what, len: length, max: u16::MAX.into(), })?; out[at..at + 2].copy_from_slice(&field.to_be_bytes()); Ok(()) } /// Writes prefixes as an UPDATE names them: for each, its path identifier /// if the session uses them for the family (RFC 7911 §3), its length in /// bits, and the bytes that length covers. fn write_nlri(nlri: &[Nlri], with_path_id: bool, out: &mut BytesMut) { for n in nlri { if with_path_id { out.put_u32(n.path_id); } out.put_u8(n.prefix.length()); out.put(n.prefix.bytes()); } } /// A parser failure as text for a log line. pub(crate) fn describe(e: &nom::Err>) -> String { match e { nom::Err::Incomplete(_) => "truncated".into(), nom::Err::Error(inner) | nom::Err::Failure(inner) => match inner { BgpParserError::Malformed(text) => text.clone(), BgpParserError::ProtocolError(value) => format!("{value:?}"), BgpParserError::Nom(rest, kind) => { format!("unexpected input ({kind:?}) with {} bytes left", rest.len()) } }, } } 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); } } /// Represents a BGP NOTIFICATION message per RFC 4271 §4.5. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct NotificationMessage { pub error: BgpError, /// Optional diagnostic data; may be empty. pub data: Vec, } impl NotificationMessage { /// Writes the body of the NOTIFICATION. pub fn to_wire(&self, out: &mut BytesMut) { let (code, subcode) = self.error.code_subcode(); out.put_u8(code); out.put_u8(subcode); out.put_slice(&self.data); } } #[cfg(test)] mod tests { use std::{ net::{Ipv4Addr, Ipv6Addr}, str::FromStr, }; use bytes::BytesMut; use eyre::{Result, eyre}; use crate::message::{ AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute, CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity, LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, Message, MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute, NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute, PathAttributeFlags, UpdateMessage, }; use bgp_primitives::{ constants::{AfiSafi, IpVersion}, ip_prefix::{IpPrefix, Nlri}, parser::ParserContext, }; macro_rules! test_path_attribute_roundtrip { ($name:ident, $input_bytes: expr, $expected: expr) => { #[test] fn $name() -> Result<()> { let ctx = &default_v6_context(); let (buf, parsed) = PathAttribute::from_wire(ctx, $input_bytes)?; assert_eq!(parsed, $expected); assert!(buf.is_empty()); let mut out = BytesMut::with_capacity(u16::MAX as usize); parsed.to_wire(ctx, &mut out)?; assert_eq!(out.to_vec(), $input_bytes); Ok(()) } }; } macro_rules! ipv6 { ($addr_str: expr) => { Ipv6Addr::from_str($addr_str) .map_err(|e| eyre!("Failed to parse IPv6 address: {}", e))? }; } /// Creates a default context for evaluating the test cases below. /// A session with four-octet AS numbers and an external peer. fn default_v6_context() -> ParserContext { ParserContext { four_octet_asn: true, ibgp: false, add_path: Vec::new(), } } // Origin Path Attribute. test_path_attribute_roundtrip!( test_origin_igp_roundtrip, &[0x40, 0x01, 0x01, 0x00], PathAttribute::Origin(OriginPathAttribute::IGP) ); test_path_attribute_roundtrip!( test_origin_egp_roundtrip, &[0x40, 0x01, 0x01, 0x01], PathAttribute::Origin(OriginPathAttribute::EGP) ); test_path_attribute_roundtrip!( test_origin_incomplete_roundtrip, &[0x40, 0x01, 0x01, 0x02], PathAttribute::Origin(OriginPathAttribute::INCOMPLETE) ); // AS Path Attribute. test_path_attribute_roundtrip!( test_as_path_segment, &[ 0x40, 0x02, 0x12, 0x02, 0x04, 0x00, 0x00, 0xfd, 0xe8, 0x00, 0x00, 0xfd, 0xe9, 0x00, 0x00, 0xfd, 0xea, 0x00, 0x00, 0xfd, 0xeb ], PathAttribute::AsPath(AsPathAttribute { segments: vec![AsPathSegment { ordered: true, path: vec![65000, 65001, 65002, 65003], }] }) ); test_path_attribute_roundtrip!( test_as_path_mixed_segments, &[ 0x40, 0x02, 0x1c, 0x02, 0x04, 0x00, 0x00, 0xfd, 0xe8, 0x00, 0x00, 0xfd, 0xe9, 0x00, 0x00, 0xfd, 0xea, 0x00, 0x00, 0xfd, 0xeb, 0x01, 0x02, 0x00, 0x00, 0xfd, 0xea, 0x00, 0x00, 0xfd, 0xeb, ], PathAttribute::AsPath(AsPathAttribute { segments: vec![ AsPathSegment { ordered: true, path: vec![65000, 65001, 65002, 65003], }, AsPathSegment { ordered: false, path: vec![65002, 65003], } ] }) ); // Next Hop Path Attribute. test_path_attribute_roundtrip!( test_next_hop, &[0x40, 0x03, 0x04, 0xc0, 0xa8, 0x01, 0x01], PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 168, 1, 1))) ); // Multi Exit Discriminator Path Attribute. test_path_attribute_roundtrip!( test_multi_exit_discriminator, &[0x80, 0x04, 0x04, 0xca, 0xfe, 0xba, 0xbe], PathAttribute::MultiExitDisc(MultiExitDiscPathAttribute(0xcafebabe)) ); // Local Pref Path Attribute. test_path_attribute_roundtrip!( test_local_pref, &[0x40, 0x05, 0x04, 0xca, 0xfe, 0xd0, 0x0d], PathAttribute::LocalPref(LocalPrefPathAttribute(0xcafed00d)) ); // Atomic Aggregate Path Attribute. test_path_attribute_roundtrip!( test_atomic_aggregate, &[0x40, 0x06, 0x00], PathAttribute::AtomicAggregate(AtomicAggregatePathAttribute {}) ); // Aggregator Path Attribute. test_path_attribute_roundtrip!( test_aggregator, &[ 0xc0, 0x07, 0x08, 0x00, 0x00, 0xfd, 0xe8, 0xc0, 0xa8, 0x01, 0x01 ], PathAttribute::Aggregator(AggregatorPathAttribute { asn: 65000, ip: Ipv4Addr::new(192, 168, 1, 1) }) ); // Communities Path Attribute. test_path_attribute_roundtrip!( test_communities, &[0xc0, 0x08, 0x04, 0xca, 0xfe, 0xba, 0xbe], PathAttribute::Communities(CommunitiesPathAttribute(vec![(0xcafe, 0xbabe)])) ); // MP Reach NLRI Path Attribute. test_path_attribute_roundtrip!( test_mp_reach_nlri, &[ 0x80, 0x0e, 0x2a, 0x00, 0x02, 0x01, 0x20, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x20, 0x20, 0x01, 0x0d, 0xb8 ], PathAttribute::MpReachNlri(MpReachNlriPathAttribute { family: AfiSafi::IPV6_UNICAST, next_hop: NlriNextHop::Ipv6WithLl { global: ipv6!("2001:db8::1"), link_local: ipv6!("fe80::12"), }, prefixes: vec![ IpPrefix::new(IpVersion::Ipv6, [0x20, 0x01, 0x0d, 0xb8], 32) .unwrap() .into() ] }) ); // MP Unreach NLRI Path Attribute. test_path_attribute_roundtrip!( mp_unreach_nlri, &[ 0x80, 0x0f, 0x08, 0x00, 0x02, 0x01, 0x20, 0x20, 0x01, 0x0d, 0xb8 ], PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute { family: AfiSafi::IPV6_UNICAST, prefixes: vec![ IpPrefix::new(IpVersion::Ipv6, [0x20, 0x01, 0x0d, 0xb8], 32) .unwrap() .into() ], }) ); // Extended Communities Path Attribute. test_path_attribute_roundtrip!( test_extended_communities, &[ 0xc0, 0x10, 0x18, 0x00, 0x03, 0xfd, 0xe8, 0x00, 0x00, 0x05, 0x39, 0x00, 0x03, 0xfd, 0xe9, 0x00, 0x00, 0x05, 0x39, 0x02, 0x03, 0xfd, 0xe8, 0x00, 0x00, 0x05, 0x39 ], PathAttribute::ExtendedCommunities(ExtendedCommunitiesPathAttribute { extended_communities: vec![ ExtendedCommunity([0x00, 0x03, 0xfd, 0xe8, 0x00, 0x00, 0x05, 0x39]), ExtendedCommunity([0x00, 0x03, 0xfd, 0xe9, 0x00, 0x00, 0x05, 0x39]), ExtendedCommunity([0x02, 0x03, 0xfd, 0xe8, 0x00, 0x00, 0x05, 0x39]), ] }) ); // Large Communities Path Attribute. test_path_attribute_roundtrip!( test_large_communities, &[ 0xc0, 0x20, 0x0c, 0x00, 0x00, 0xfd, 0xe8, 0x00, 0x00, 0xca, 0xfe, 0x00, 0x00, 0xf0, 0x0d ], PathAttribute::LargeCommunities(LargeCommunitiesPathAttribute { communities: vec![LargeCommunity { global_admin: 65000, data_1: 0xcafe, data_2: 0xf00d, }] }) ); // Unknown Path Attribute. Ensures we both parse an unrecognized type code // into the catch-all variant and re-serialize it with the correct field // ordering (flags, type, length, payload) per RFC 4271 §4.3. test_path_attribute_roundtrip!( test_unknown_path_attribute, &[0x80, 0x63, 0x04, 0xde, 0xad, 0xbe, 0xef], PathAttribute::UnknownPathAttribute { flags: PathAttributeFlags::new( /*optional=*/ true, /*transitive=*/ false, /*partial=*/ false, /*extended_length=*/ false, ), type_code: 0x63, payload: vec![0xde, 0xad, 0xbe, 0xef], } ); // -- UPDATE message roundtrip tests -- /// The UPDATE in `body`, as it is to be acted on. fn decode_update(ctx: &ParserContext, body: &[u8]) -> Result { Ok(UpdateMessage::decode(ctx, body)?.update) } /// Asserts that an UPDATE message roundtrips byte-for-byte. macro_rules! test_update_roundtrip { ($name:ident, $input_bytes:expr, $expected:expr) => { #[test] fn $name() -> Result<()> { let ctx = &default_v6_context(); let parsed = decode_update(ctx, $input_bytes)?; assert_eq!(parsed, $expected); let mut out = BytesMut::with_capacity(u16::MAX as usize); parsed.to_wire(ctx, &mut out)?; assert_eq!(out.to_vec(), $input_bytes); Ok(()) } }; } // Wrapper to generate a IPv4 prefix used in tests below. fn v4_prefix(length: u8, bytes: &[u8]) -> IpPrefix { IpPrefix::new(IpVersion::Ipv4, bytes, length).unwrap() } /// A small set of path attributes that commonly accompany an IPv4 unicast /// announcement: Origin=IGP, AS_PATH=[65000], NEXT_HOP=192.0.2.1. fn sample_ipv4_path_attributes() -> Vec { vec![ PathAttribute::Origin(OriginPathAttribute::IGP), PathAttribute::AsPath(AsPathAttribute { segments: vec![AsPathSegment { ordered: true, path: vec![65000], }], }), PathAttribute::NextHop(NextHopPathAttribute(Ipv4Addr::new(192, 0, 2, 1))), ] } // Empty UPDATE (equivalent to a keepalive-shaped body): no withdrawn // routes, no path attributes, no NLRI. test_update_roundtrip!( test_update_empty, &[0x00, 0x00, 0x00, 0x00], UpdateMessage { withdrawn_routes: vec![], path_attributes: vec![], announced_routes: vec![], } ); // Pure announcement: no withdrawn routes, standard v4 path attributes, one // NLRI prefix (10.0.0.0/8). test_update_roundtrip!( test_update_announcement, &[ // Withdrawn routes length = 0. 0x00, 0x00, // Total path attribute length = 20 bytes. 0x00, 0x14, // ORIGIN = IGP. 0x40, 0x01, 0x01, 0x00, // AS_PATH = AS_SEQUENCE [65000] (4-octet ASNs). 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0xfd, 0xe8, // NEXT_HOP = 192.0.2.1. 0x40, 0x03, 0x04, 0xc0, 0x00, 0x02, 0x01, // NLRI: 10.0.0.0/8. 0x08, 0x0a, ], UpdateMessage { withdrawn_routes: vec![], path_attributes: sample_ipv4_path_attributes(), announced_routes: vec![v4_prefix(8, &[0x0a]).into()], } ); // Pure withdrawal: one withdrawn route, no path attributes, no NLRI. RFC // 4271 allows (and indeed requires) this shape for unreach updates. test_update_roundtrip!( test_update_withdrawal, &[ // Withdrawn routes length = 2. 0x00, 0x02, // 10.0.0.0/8. 0x08, 0x0a, // Total path attribute length = 0. 0x00, 0x00, ], UpdateMessage { withdrawn_routes: vec![v4_prefix(8, &[0x0a]).into()], path_attributes: vec![], announced_routes: vec![], } ); // Mixed withdrawal + announcement with multiple NLRI entries at different // prefix lengths, exercising the variable-length NLRI parser tail. test_update_roundtrip!( test_update_mixed, &[ // Withdrawn routes length = 2. 0x00, 0x02, // 10.0.0.0/8 withdrawn. 0x08, 0x0a, // Total path attribute length = 20. 0x00, 0x14, // ORIGIN = IGP. 0x40, 0x01, 0x01, 0x00, // AS_PATH = AS_SEQUENCE [65000]. 0x40, 0x02, 0x06, 0x02, 0x01, 0x00, 0x00, 0xfd, 0xe8, // NEXT_HOP = 192.0.2.1. 0x40, 0x03, 0x04, 0xc0, 0x00, 0x02, 0x01, // NLRI: 172.16.0.0/16. 0x10, 0xac, 0x10, // NLRI: 192.168.1.0/24. 0x18, 0xc0, 0xa8, 0x01, ], UpdateMessage { withdrawn_routes: vec![v4_prefix(8, &[0x0a]).into()], path_attributes: sample_ipv4_path_attributes(), announced_routes: vec![ v4_prefix(16, &[0xac, 0x10]).into(), v4_prefix(24, &[0xc0, 0xa8, 0x01]).into(), ], } ); // An UPDATE carrying an unrecognized path attribute. test_update_roundtrip!( test_update_with_unknown_path_attribute, &[ // Withdrawn routes length = 0. 0x00, 0x00, // Total path attribute length = 7. 0x00, 0x07, // Unknown attribute: flags=0x80 (optional), type=0x63, len=4. 0x80, 0x63, 0x04, 0xde, 0xad, 0xbe, 0xef, ], UpdateMessage { withdrawn_routes: vec![], path_attributes: vec![PathAttribute::UnknownPathAttribute { flags: PathAttributeFlags::new(true, false, false, false), type_code: 0x63, payload: vec![0xde, 0xad, 0xbe, 0xef], }], announced_routes: vec![], } ); // Rejects trailing garbage after the NLRI section (a partial prefix). /// With ADD-PATH in use for a family, each of its prefixes has a /// four-octet path identifier in front (RFC 7911 §3): in the body for /// IPv4 unicast, in MP_REACH_NLRI and MP_UNREACH_NLRI for the others. #[test] fn prefixes_carry_a_path_identifier_where_add_path_is_in_use() { let plain = default_v6_context(); let v4_only = ParserContext { add_path: vec![AfiSafi::IPV4_UNICAST], ..default_v6_context() }; let v6_only = ParserContext { add_path: vec![AfiSafi::IPV6_UNICAST], ..default_v6_context() }; // Withdrawing 10.0.0.0/8, path 7, in the body. #[rustfmt::skip] let body = [ 0x00, 0x06, // withdrawn routes length 0x00, 0x00, 0x00, 0x07, 0x08, 0x0a, // path 7, 10.0.0.0/8 0x00, 0x00, // no attributes ]; let update = decode_update(&v4_only, &body).unwrap(); assert_eq!( update.withdrawn_routes, vec![Nlri { prefix: v4_prefix(8, &[0x0a]), path_id: 7, }] ); let mut out = BytesMut::new(); update.to_wire(&v4_only, &mut out).unwrap(); assert_eq!(&out[..], &body[..]); // The same route on a session without ADD-PATH has no identifier. let mut out = BytesMut::new(); update.to_wire(&plain, &mut out).unwrap(); assert_eq!(&out[..], [0x00, 0x02, 0x08, 0x0a, 0x00, 0x00]); // And ADD-PATH for IPv6 says nothing about the IPv4 prefixes in // the body: read that way, these bytes are not prefixes at all. assert!(decode_update(&v6_only, &body).is_err()); // Two routes for 2001:db8::/32, paths 1 and 2, withdrawn. #[rustfmt::skip] let unreach = [ 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x02, 0x20, 0x20, 0x01, 0x0d, 0xb8, ]; let (rest, parsed) = MpUnreachNlriPathAttribute::from_wire(&v6_only, &unreach).unwrap(); assert!(rest.is_empty()); assert_eq!( parsed .prefixes .iter() .map(|n| n.path_id) .collect::>(), [1, 2] ); assert_eq!(parsed.prefixes[0].prefix, parsed.prefixes[1].prefix); let mut out = BytesMut::new(); parsed.to_wire(&v6_only, &mut out); assert_eq!(&out[..], &unreach[..]); } // Found by the property tests in tests/properties.rs. /// A prefix longer than its address family allows was accepted, and /// showing it (as the session does when it logs a route) panicked. A /// peer could end the session task with one UPDATE. #[test] fn a_prefix_longer_than_an_address_is_malformed() { let ctx = default_v6_context(); // No withdrawn routes, no attributes, NLRI of length 99. let mut body = vec![0, 0, 0, 0, 99]; body.extend_from_slice(&[0xff; 13]); assert!(decode_update(&ctx, &body).is_err()); let mut unreach = vec![0x00, 0x02, 0x01, 129]; unreach.extend_from_slice(&[0xff; 17]); assert!(MpUnreachNlriPathAttribute::from_wire(&ctx, &unreach).is_err()); } /// The bits of a prefix past its length mean nothing (RFC 4271 §4.3). /// They used to be kept, which made 10.0.0.0/8 sent with stray bits a /// different route from 10.0.0.0/8. #[test] fn bits_past_the_prefix_length_are_cleared() { let ctx = default_v6_context(); // 10.255.0.0/12 withdrawn, with the low four bits of the second // octet set. let body = [0, 3, 12, 10, 0xff, 0, 0]; let update = decode_update(&ctx, &body).unwrap(); assert_eq!(update.withdrawn_routes, vec![v4_prefix(12, &[10, 0xf0])]); assert_eq!(update.withdrawn_routes[0].to_string(), "10.240.0.0/12"); } /// An extended community is eight octets whatever its type. The opaque /// kind was read as seven, which put every community after it out of /// step. #[test] fn an_opaque_extended_community_is_eight_octets() { #[rustfmt::skip] let wire = [ 0x03, 0x0c, 1, 2, 3, 4, 5, 6, // opaque 0x00, 0x02, 0, 0, 0xfd, 0xe8, 0, 100, // route target 65000:100 ]; let (rest, parsed) = ExtendedCommunitiesPathAttribute::from_wire(&wire).unwrap(); assert!(rest.is_empty()); assert_eq!( parsed.extended_communities, vec![ ExtendedCommunity([0x03, 0x0c, 1, 2, 3, 4, 5, 6]), ExtendedCommunity([0x00, 0x02, 0, 0, 0xfd, 0xe8, 0, 100]), ] ); assert_eq!(parsed.extended_communities[0].typ(), 0x03); assert_eq!(parsed.extended_communities[0].sub_typ(), 0x0c); let mut out = BytesMut::new(); parsed.to_wire(&mut out); assert_eq!(&out[..], &wire[..]); // A list that is not a whole number of them is malformed. assert!(ExtendedCommunitiesPathAttribute::from_wire(&wire[..12]).is_err()); assert!(ExtendedCommunitiesPathAttribute::from_wire(&[]).is_err()); } /// AGGREGATOR carries an AS number as wide as the session's. It could /// neither be read from nor written to a two-octet peer, so one such /// attribute ended the session. #[test] fn aggregator_is_six_octets_on_a_two_octet_session() { let two_octet = ParserContext { four_octet_asn: false, ibgp: false, add_path: Vec::new(), }; let wire = [0xfd, 0xe8, 192, 0, 2, 1]; let (rest, parsed) = AggregatorPathAttribute::from_wire(&two_octet, &wire).unwrap(); assert!(rest.is_empty()); assert_eq!( parsed, AggregatorPathAttribute { asn: 65000, ip: Ipv4Addr::new(192, 0, 2, 1), } ); let mut out = BytesMut::new(); parsed.to_wire(&two_octet, &mut out); assert_eq!(&out[..], &wire[..]); } /// The reserved octet after the next hop is there for every family. It /// used to be skipped only for IPv6, so an IPv4 MP_REACH_NLRI gained a /// 0.0.0.0/0 route that the peer never sent. #[test] fn mp_reach_for_ipv4_has_no_phantom_default_route() { let ctx = default_v6_context(); #[rustfmt::skip] let wire = [ 0x00, 0x01, 0x01, // IPv4 unicast 0x04, 192, 0, 2, 1, // next hop 0x00, // reserved 0x18, 10, 1, 2, // 10.1.2.0/24 ]; let (rest, parsed) = MpReachNlriPathAttribute::from_wire(&ctx, &wire).unwrap(); assert!(rest.is_empty()); assert_eq!( parsed.family, bgp_primitives::constants::AfiSafi::IPV4_UNICAST ); assert_eq!( parsed.next_hop, NlriNextHop::Ipv4(Ipv4Addr::new(192, 0, 2, 1)) ); assert_eq!(parsed.prefixes, vec![v4_prefix(24, &[10, 1, 2])]); let mut out = BytesMut::new(); parsed.to_wire(&ctx, &mut out); assert_eq!(&out[..], &wire[..]); } /// Any family can be named, but only IP prefixes can be read as NLRI. /// Reading the routes of another family as prefixes would invent routes. #[test] fn nlri_of_a_family_that_is_not_ip_prefixes_is_refused() { let ctx = default_v6_context(); // L2VPN EVPN, and IPv4 with labels: neither is a list of prefixes. for family in [[0x00, 0x19, 0x46], [0x00, 0x01, 0x04]] { let mut reach = family.to_vec(); reach.extend_from_slice(&[0x04, 192, 0, 2, 1, 0x00, 0x18, 10, 1, 2]); assert!(MpReachNlriPathAttribute::from_wire(&ctx, &reach).is_err()); let mut unreach = family.to_vec(); unreach.extend_from_slice(&[0x18, 10, 1, 2]); assert!(MpUnreachNlriPathAttribute::from_wire(&ctx, &unreach).is_err()); } } /// AS_PATH is as wide as the session negotiated; AS4_PATH is always /// four octets wide (RFC 6793). #[test] fn as_paths_are_encoded_as_wide_as_the_session_allows() { let path = AsPathAttribute { segments: vec![AsPathSegment { ordered: true, path: vec![65000, 4_200_000_001], }], }; let two_octet = ParserContext { four_octet_asn: false, ibgp: false, add_path: Vec::new(), }; let four_octet = ParserContext { four_octet_asn: true, ibgp: false, add_path: Vec::new(), }; let wire = |attribute: &PathAttribute, ctx: &ParserContext| { let mut out = BytesMut::new(); attribute.to_wire(ctx, &mut out).unwrap(); out.to_vec() }; // Two octets: the number that does not fit goes out as AS_TRANS. let as_path = PathAttribute::AsPath(path.clone()); #[rustfmt::skip] assert_eq!( wire(&as_path, &two_octet), [0x40, 0x02, 0x06, 0x02, 0x02, 0xfd, 0xe8, 0x5b, 0xa0] ); #[rustfmt::skip] assert_eq!( wire(&as_path, &four_octet), [0x40, 0x02, 0x0a, 0x02, 0x02, 0x00, 0x00, 0xfd, 0xe8, 0xfa, 0x56, 0xea, 0x01] ); // AS4_PATH: optional transitive, type 17, the same on any session. let as4_path = path.as4_path().expect("one AS number needs four octets"); #[rustfmt::skip] let expected = [0xc0, 0x11, 0x0a, 0x02, 0x02, 0x00, 0x00, 0xfd, 0xe8, 0xfa, 0x56, 0xea, 0x01]; for ctx in [&two_octet, &four_octet] { let bytes = wire(&as4_path, ctx); assert_eq!(bytes, expected); let (rest, parsed) = PathAttribute::from_wire(ctx, &bytes).unwrap(); assert!(rest.is_empty()); assert_eq!(parsed, as4_path); } // A path of numbers that all fit needs no AS4_PATH. let narrow = AsPathAttribute { segments: vec![AsPathSegment { ordered: true, path: vec![65000, 65001], }], }; assert_eq!(narrow.as4_path(), None); } #[test] fn test_update_rejects_trailing_garbage() { let ctx = default_v6_context(); // Well-formed withdrawn_len=0, pa_len=0, followed by a single byte that // declares a prefix length of 8 but provides no prefix bytes. let buf = &[0x00, 0x00, 0x00, 0x00, 0x08]; let result = decode_update(&ctx, buf); assert!( result.is_err(), "Expected a parse error for trailing garbage, got {:?}", result ); } #[test] fn a_notification_round_trips_with_its_data() -> Result<()> { use crate::errors::{BgpError, CeaseSubcode, FsmSubcode}; let ctx = &default_v6_context(); for (error, data, body) in [ ( BgpError::Cease(CeaseSubcode::AdminShutdown), vec![0xde, 0xad], &[6, 2, 0xde, 0xad][..], ), ( BgpError::FsmError(FsmSubcode::UnexpectedEstablished), vec![], &[5, 3][..], ), ] { let msg = Message::Notification(NotificationMessage { error, data }); let mut buf = BytesMut::new(); msg.to_wire(ctx, &mut buf)?; assert_eq!(&buf[19..], body); assert_eq!(Message::decode(ctx, &buf)?, msg); } Ok(()) } /// A peer may give a reason we have no name for: a subcode of zero /// ("unspecific"), or one assigned since. Such a NOTIFICATION used to /// be refused as malformed, which lost the reason and answered a /// message that must not be answered. #[test] fn a_notification_with_an_unnamed_reason_is_still_read() { use crate::errors::BgpError; let ctx = default_v6_context(); for (code, subcode) in [(6, 0), (5, 9), (2, 5), (3, 7), (4, 9), (99, 1)] { let decoded = Message::decode(&ctx, &framed(21, 3, &[code, subcode])).unwrap(); let Message::Notification(notification) = &decoded else { panic!("expected a NOTIFICATION, got {decoded:?}"); }; assert_eq!(notification.error, BgpError::Other { code, subcode }); assert_eq!(notification.error.code_subcode(), (code, subcode)); } // A state machine error without a subcode has a name (RFC 6608); // FRR sends it. let decoded = Message::decode(&ctx, &framed(21, 3, &[5, 0])).unwrap(); assert!(matches!( &decoded, Message::Notification(n) if n.error == BgpError::FsmError(crate::errors::FsmSubcode::Unspecified) )); // Too short to hold a code and subcode. assert!(Message::decode(&ctx, &framed(20, 3, &[6])).is_err()); } #[test] fn test_message_keepalive_roundtrip() -> Result<()> { let ctx = &default_v6_context(); let mut buf = BytesMut::new(); Message::KeepAlive.to_wire(ctx, &mut buf)?; // 16-byte marker + 2-byte length (19) + 1-byte type (4) assert_eq!(buf.len(), 19); assert!(buf[..16].iter().all(|&b| b == 0xFF)); assert_eq!(&buf[16..19], &[0x00, 0x13, 0x04]); assert_eq!(Message::decode(ctx, &buf)?, Message::KeepAlive); Ok(()) } #[test] fn test_message_rejects_bad_marker() { let ctx = default_v6_context(); let mut buf = vec![0u8; 19]; buf[16] = 0x00; buf[17] = 0x13; buf[18] = 0x04; assert!(Message::decode(&ctx, &buf).is_err()); } #[test] fn test_message_rejects_short_length() { let ctx = default_v6_context(); let mut buf = vec![0xFF; 19]; buf[16] = 0x00; buf[17] = 0x12; // length = 18, below minimum 19 buf[18] = 0x04; assert!(Message::decode(&ctx, &buf).is_err()); } /// A message that cannot be encoded leaves nothing behind in the /// buffer, so that what is already there can still be sent. #[test] fn a_message_that_cannot_be_encoded_leaves_the_buffer_as_it_was() { let ctx = default_v6_context(); let mut buf = BytesMut::new(); Message::KeepAlive.to_wire(&ctx, &mut buf).unwrap(); let too_long = Message::Notification(NotificationMessage { error: crate::errors::BgpError::HoldTimer, data: vec![0; 5000], }); assert!(too_long.to_wire(&ctx, &mut buf).is_err()); assert_eq!(buf.len(), 19); } /// An attribute's length is one octet unless its value needs two. #[test] fn a_long_attribute_gets_the_extended_length() { let ctx = default_v6_context(); for (communities, extended) in [(63, false), (64, true)] { let attribute = PathAttribute::Communities(CommunitiesPathAttribute(vec![(1, 2); communities])); let mut out = BytesMut::new(); attribute.to_wire(&ctx, &mut out).unwrap(); let header = if extended { 4 } else { 3 }; assert_eq!(out.len(), header + 4 * communities); assert_eq!(out[0] & 0x10 != 0, extended); let (rest, parsed) = PathAttribute::from_wire(&ctx, &out).unwrap(); assert!(rest.is_empty()); assert_eq!(parsed, attribute); } } /// A header, with `length` and `msg_type` as given, followed by `body`. fn framed(length: u16, msg_type: u8, body: &[u8]) -> Vec { let mut buf = vec![0xFF; 16]; buf.extend_from_slice(&length.to_be_bytes()); buf.push(msg_type); buf.extend_from_slice(body); buf } /// RFC 4271 §6.1 to §6.3: what is wrong decides the NOTIFICATION, and /// for a bad length or type the Data field carries the offending value. #[test] fn decode_failures_name_the_notification_to_send() { use crate::errors::{ BgpError, MsgHeaderSubcode as Header, OpenMsgSubcode, UpdateMsgSubcode, }; let ctx = default_v6_context(); let failure = |buf: Vec| { let e = Message::decode(&ctx, &buf).unwrap_err(); (e.notification, e.data) }; let mut bad_marker = framed(19, 4, &[]); bad_marker[3] = 0; assert_eq!( failure(bad_marker), (BgpError::MsgHeader(Header::ConnNotSynchronized), vec![]) ); // Shorter than a header, longer than the maximum, longer than the // bytes at hand: all a bad length, reported with that length. for length in [18u16, 4097, 100] { assert_eq!( failure(framed(length, 4, &[])), ( BgpError::MsgHeader(Header::BadMessageLength), length.to_be_bytes().to_vec() ), "length {length}" ); } assert_eq!( failure(framed(20, 4, &[0])), (BgpError::MsgHeader(Header::BadMessageLength), vec![0, 20]), "a KEEPALIVE has no body" ); assert_eq!( failure(framed(19, 9, &[])), (BgpError::MsgHeader(Header::BadMessageType), vec![9]) ); // An OPEN for BGP version 3: the reply says we speak version 4. assert_eq!( failure(framed(29, 1, &[3, 0xfd, 0xe8, 0, 90, 10, 0, 0, 1, 0])), ( BgpError::OpenMsg(OpenMsgSubcode::UnsupportedVersion), vec![0, 4] ) ); // The same OPEN for version 4 is fine: no optional parameters is // a valid OPEN. let plain = framed(29, 1, &[4, 0xfd, 0xe8, 0, 90, 10, 0, 0, 1, 0]); assert!( matches!(Message::decode(&ctx, &plain), Ok(Message::Open(open)) if open.options.is_empty()) ); // An OPEN cut short. assert_eq!( failure(framed(21, 1, &[4, 0xfd])).0, BgpError::OpenMsg(OpenMsgSubcode::Unspecific) ); // An UPDATE whose attribute length points past its end. assert_eq!( failure(framed(23, 2, &[0, 0, 0, 40])).0, BgpError::UpdateMsg(UpdateMsgSubcode::MalformedAttrs) ); } #[test] fn decode_ignores_bytes_after_the_message() { let ctx = default_v6_context(); let mut buf = framed(19, 4, &[]); buf.extend_from_slice(&[1, 2, 3]); assert_eq!(Message::decode(&ctx, &buf), Ok(Message::KeepAlive)); } /// The optional parameters are the last thing in an OPEN. #[test] fn bytes_after_the_optional_parameters_of_an_open_are_refused() { use crate::errors::{BgpError, OpenMsgSubcode}; let ctx = default_v6_context(); let open = framed(31, 1, &[4, 0xfd, 0xe8, 0, 90, 10, 0, 0, 1, 0, 0xaa, 0xbb]); assert_eq!( Message::decode(&ctx, &open).unwrap_err().notification, BgpError::OpenMsg(OpenMsgSubcode::Unspecific) ); } }