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
+80
-14
+80
-14
@@ -171,11 +171,9 @@ impl OpenOption {
|
|||||||
}
|
}
|
||||||
Ok((buf, Self::Capabilities(capabilities)))
|
Ok((buf, Self::Capabilities(capabilities)))
|
||||||
}
|
}
|
||||||
other => {
|
other => Err(Failure(BgpParserError::ProtocolError(
|
||||||
return Err(Failure(BgpParserError::ProtocolError(
|
ProtocolErrorValues::UnknownOpenOption(other),
|
||||||
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)]
|
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
@@ -235,8 +242,11 @@ pub enum Capability {
|
|||||||
header: BgpSecHeader,
|
header: BgpSecHeader,
|
||||||
afi: AddressFamilyId,
|
afi: AddressFamilyId,
|
||||||
} = 7,
|
} = 7,
|
||||||
/// Multiple labels compatibility (RFC 8277).
|
/// Multiple labels (RFC 8277 §2.1): for each address family, how many
|
||||||
MultiLabelCompat {} = 8,
|
/// labels the speaker can handle in one NLRI.
|
||||||
|
MultiLabelCompat {
|
||||||
|
entries: Vec<MultiLabelEntry>,
|
||||||
|
} = 8,
|
||||||
BgpRole {
|
BgpRole {
|
||||||
role: BgpRole,
|
role: BgpRole,
|
||||||
} = 9,
|
} = 9,
|
||||||
@@ -292,7 +302,7 @@ impl Capability {
|
|||||||
Capability::ExtendedNextHop { .. } => CapabilityDiscriminants::ExtendedNextHop as u8,
|
Capability::ExtendedNextHop { .. } => CapabilityDiscriminants::ExtendedNextHop as u8,
|
||||||
Capability::ExtendedMessage => CapabilityDiscriminants::ExtendedMessage as u8,
|
Capability::ExtendedMessage => CapabilityDiscriminants::ExtendedMessage as u8,
|
||||||
Capability::BgpSec { .. } => CapabilityDiscriminants::BgpSec 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::BgpRole { .. } => CapabilityDiscriminants::BgpRole as u8,
|
||||||
Capability::GracefulRestart { .. } => CapabilityDiscriminants::GracefulRestart as u8,
|
Capability::GracefulRestart { .. } => CapabilityDiscriminants::GracefulRestart as u8,
|
||||||
Capability::FourByteAsn { .. } => CapabilityDiscriminants::FourByteAsn as u8,
|
Capability::FourByteAsn { .. } => CapabilityDiscriminants::FourByteAsn as u8,
|
||||||
@@ -414,11 +424,25 @@ impl Capability {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
v if v == CapabilityDiscriminants::MultiLabelCompat as u8 => {
|
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 => {
|
v if v == CapabilityDiscriminants::BgpRole as u8 => {
|
||||||
let (cap_buf, role) = be_u8(cap_buf)?;
|
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");
|
require_empty!(cap_buf, "BgpRole");
|
||||||
Ok((buf, Self::BgpRole { role }))
|
Ok((buf, Self::BgpRole { role }))
|
||||||
}
|
}
|
||||||
@@ -555,7 +579,13 @@ impl Capability {
|
|||||||
out.put_u8(header.0);
|
out.put_u8(header.0);
|
||||||
out.put_u16(*afi as u16);
|
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 } => {
|
Capability::BgpRole { role } => {
|
||||||
out.put_u8(role.into());
|
out.put_u8(role.into());
|
||||||
}
|
}
|
||||||
@@ -600,7 +630,7 @@ impl Capability {
|
|||||||
for llgr in llgrs {
|
for llgr in llgrs {
|
||||||
out.put_u16(llgr.afi as u16);
|
out.put_u16(llgr.afi as u16);
|
||||||
out.put_u8(llgr.safi as u8);
|
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..]);
|
out.put(&llgr.stale_time.to_be_bytes()[1..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,7 +651,7 @@ impl Capability {
|
|||||||
Capability::ExtendedNextHop { entries } => 6 * entries.len() as u8,
|
Capability::ExtendedNextHop { entries } => 6 * entries.len() as u8,
|
||||||
Capability::ExtendedMessage => 0,
|
Capability::ExtendedMessage => 0,
|
||||||
Capability::BgpSec { .. } => 3,
|
Capability::BgpSec { .. } => 3,
|
||||||
Capability::MultiLabelCompat {} => todo!(),
|
Capability::MultiLabelCompat { entries } => 4 * entries.len() as u8,
|
||||||
Capability::BgpRole { .. } => 1,
|
Capability::BgpRole { .. } => 1,
|
||||||
Capability::GracefulRestart { entries, .. } => 2 + 4 * entries.len() as u8,
|
Capability::GracefulRestart { entries, .. } => 2 + 4 * entries.len() as u8,
|
||||||
Capability::FourByteAsn { .. } => 4,
|
Capability::FourByteAsn { .. } => 4,
|
||||||
@@ -818,7 +848,9 @@ mod tests {
|
|||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
|
||||||
use crate::open::{Capability, ExtendedNextHop, GracefulRestartEntry, OpenMessage, OpenOption};
|
use crate::open::{
|
||||||
|
Capability, ExtendedNextHop, GracefulRestartEntry, MultiLabelEntry, OpenMessage, OpenOption,
|
||||||
|
};
|
||||||
|
|
||||||
use bgp_primitives::{
|
use bgp_primitives::{
|
||||||
constants::{AddressFamilyId, SubsequentAfi},
|
constants::{AddressFamilyId, SubsequentAfi},
|
||||||
@@ -986,6 +1018,40 @@ mod tests {
|
|||||||
/// Returning the (empty) per-capability buffer instead makes the list
|
/// Returning the (empty) per-capability buffer instead makes the list
|
||||||
/// parser stop early and silently lose every later capability, e.g. the
|
/// parser stop early and silently lose every later capability, e.g. the
|
||||||
/// 4-octet ASN that routers list after graceful restart.
|
/// 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]
|
#[test]
|
||||||
fn capability_parsers_leave_the_following_bytes() {
|
fn capability_parsers_leave_the_following_bytes() {
|
||||||
let ctx = ParserContext::default();
|
let ctx = ParserContext::default();
|
||||||
|
|||||||
Reference in new issue
Block a user