diff --git a/crates/packet/src/open.rs b/crates/packet/src/open.rs index 3a77763..1ba534d 100644 --- a/crates/packet/src/open.rs +++ b/crates/packet/src/open.rs @@ -171,11 +171,9 @@ impl OpenOption { } Ok((buf, Self::Capabilities(capabilities))) } - other => { - return Err(Failure(BgpParserError::ProtocolError( - ProtocolErrorValues::UnknownOpenOption(other), - ))); - } + other => Err(Failure(BgpParserError::ProtocolError( + ProtocolErrorValues::UnknownOpenOption(other), + ))), } } @@ -207,6 +205,15 @@ impl OpenOption { } } +/// One address family in a Multiple Labels capability (RFC 8277 §2.1). +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub struct MultiLabelEntry { + pub afi: u16, + pub safi: u8, + /// Maximum number of labels in one NLRI of this family. + pub count: u8, +} + #[derive(Debug, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)] #[repr(u8)] pub enum Capability { @@ -235,8 +242,11 @@ pub enum Capability { header: BgpSecHeader, afi: AddressFamilyId, } = 7, - /// Multiple labels compatibility (RFC 8277). - MultiLabelCompat {} = 8, + /// Multiple labels (RFC 8277 §2.1): for each address family, how many + /// labels the speaker can handle in one NLRI. + MultiLabelCompat { + entries: Vec, + } = 8, BgpRole { role: BgpRole, } = 9, @@ -292,7 +302,7 @@ impl Capability { Capability::ExtendedNextHop { .. } => CapabilityDiscriminants::ExtendedNextHop as u8, Capability::ExtendedMessage => CapabilityDiscriminants::ExtendedMessage as u8, Capability::BgpSec { .. } => CapabilityDiscriminants::BgpSec as u8, - Capability::MultiLabelCompat {} => CapabilityDiscriminants::MultiLabelCompat as u8, + Capability::MultiLabelCompat { .. } => CapabilityDiscriminants::MultiLabelCompat as u8, Capability::BgpRole { .. } => CapabilityDiscriminants::BgpRole as u8, Capability::GracefulRestart { .. } => CapabilityDiscriminants::GracefulRestart as u8, Capability::FourByteAsn { .. } => CapabilityDiscriminants::FourByteAsn as u8, @@ -414,11 +424,25 @@ impl Capability { )) } v if v == CapabilityDiscriminants::MultiLabelCompat as u8 => { - todo!() + // The AFI and SAFI are kept as numbers: this capability is + // used with labelled families we have no named constant for. + let parse_entry = |buf: &'a [u8]| -> IResult< + &'a [u8], + MultiLabelEntry, + BgpParserError<&'a [u8]>, + > { + let (buf, afi) = be_u16(buf)?; + let (buf, safi) = be_u8(buf)?; + let (buf, count) = be_u8(buf)?; + Ok((buf, MultiLabelEntry { afi, safi, count })) + }; + let (cap_buf, entries) = nom::multi::many1(parse_entry).parse(cap_buf)?; + require_empty!(cap_buf, "MultiLabelCompat"); + Ok((buf, Self::MultiLabelCompat { entries })) } v if v == CapabilityDiscriminants::BgpRole as u8 => { let (cap_buf, role) = be_u8(cap_buf)?; - let role = BgpRole::try_from(role).map_err(|e| fail!("{}", e))?; + let role = BgpRole::from(role); require_empty!(cap_buf, "BgpRole"); Ok((buf, Self::BgpRole { role })) } @@ -555,7 +579,13 @@ impl Capability { out.put_u8(header.0); out.put_u16(*afi as u16); } - Capability::MultiLabelCompat {} => todo!(), + Capability::MultiLabelCompat { entries } => { + for entry in entries { + out.put_u16(entry.afi); + out.put_u8(entry.safi); + out.put_u8(entry.count); + } + } Capability::BgpRole { role } => { out.put_u8(role.into()); } @@ -600,7 +630,7 @@ impl Capability { for llgr in llgrs { out.put_u16(llgr.afi as u16); out.put_u8(llgr.safi as u8); - out.put_u8(llgr.flags as u8); + out.put_u8(llgr.flags); out.put(&llgr.stale_time.to_be_bytes()[1..]); } } @@ -621,7 +651,7 @@ impl Capability { Capability::ExtendedNextHop { entries } => 6 * entries.len() as u8, Capability::ExtendedMessage => 0, Capability::BgpSec { .. } => 3, - Capability::MultiLabelCompat {} => todo!(), + Capability::MultiLabelCompat { entries } => 4 * entries.len() as u8, Capability::BgpRole { .. } => 1, Capability::GracefulRestart { entries, .. } => 2 + 4 * entries.len() as u8, Capability::FourByteAsn { .. } => 4, @@ -818,7 +848,9 @@ mod tests { use bytes::BytesMut; use eyre::Result; - use crate::open::{Capability, ExtendedNextHop, GracefulRestartEntry, OpenMessage, OpenOption}; + use crate::open::{ + Capability, ExtendedNextHop, GracefulRestartEntry, MultiLabelEntry, OpenMessage, OpenOption, + }; use bgp_primitives::{ constants::{AddressFamilyId, SubsequentAfi}, @@ -986,6 +1018,40 @@ mod tests { /// Returning the (empty) per-capability buffer instead makes the list /// parser stop early and silently lose every later capability, e.g. the /// 4-octet ASN that routers list after graceful restart. + /// A peer advertising Multiple Labels used to hit a `todo!()` and take + /// the session task down with it. + #[test] + fn multiple_labels_capability_round_trips() { + let ctx = ParserContext::default(); + // code 8, length 8: (IPv4, labelled unicast, 2) and (IPv6, labelled unicast, 3) + let wire = [0x08, 0x08, 0x00, 0x01, 0x04, 0x02, 0x00, 0x02, 0x04, 0x03]; + let (rest, cap) = Capability::from_wire(&ctx, &wire).unwrap(); + assert!(rest.is_empty()); + assert_eq!( + cap, + Capability::MultiLabelCompat { + entries: vec![ + MultiLabelEntry { + afi: 1, + safi: 4, + count: 2 + }, + MultiLabelEntry { + afi: 2, + safi: 4, + count: 3 + }, + ] + } + ); + let mut out = BytesMut::new(); + cap.to_wire(&ctx, &mut out).unwrap(); + assert_eq!(&out[..], &wire[..]); + + // A truncated entry is a parse error, not a panic. + assert!(Capability::from_wire(&ctx, &[0x08, 0x03, 0x00, 0x01, 0x04]).is_err()); + } + #[test] fn capability_parsers_leave_the_following_bytes() { let ctx = ParserContext::default();