Parse the Multiple Labels capability instead of panicking
The capability parser had a todo!() for capability code 8 (RFC 8277), and the encoder two more. A peer advertising it took the session task down. It is now parsed into its (AFI, SAFI, count) entries and round-trips. Also carries clippy's mechanical fixes for this file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ab6c1c38b
commit
126368852a
1 file changed
+79
-13
+79
-13
@@ -171,11 +171,9 @@ impl OpenOption {
|
||||
}
|
||||
Ok((buf, Self::Capabilities(capabilities)))
|
||||
}
|
||||
other => {
|
||||
return Err(Failure(BgpParserError::ProtocolError(
|
||||
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<MultiLabelEntry>,
|
||||
} = 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();
|
||||
|
||||
Reference in new issue
Block a user