Files
ferrix/crates/packet/src/open.rs
T
Rayhaan JaufeerallyandClaude Opus 5.5 65f136048e Graceful restart (RFC 4724, RFC 8538)
A peer that restarts keeps its routes with us, and we keep ours with our
peers while we restart, so that a restart withdraws nothing across the
network and the machine goes on forwarding. On by default for every
peer; `graceful_restart` sets the restart time we give, caps how long a
peer's routes are kept, or turns it off.

When a peer restarts: its routes are marked stale in the routing table,
and chosen as before, for at most its restart time. When it is back, what
it sends again is fresh, and the stale routes of a family go at its
End-of-RIB marker, or after the cap; at once for a family whose
forwarding it did not keep. Only a connection that failed counts as a
restart, and, with RFC 8538 agreed, an expired hold timer or a
NOTIFICATION other than a Hard Reset. The stale mark is a bit of the
route's word that selection never looks at.

When we restart: SIGUSR1 leaves the kernel's routes where they are,
closes the sessions without a word where graceful restart is agreed, and
exits with status 75, on which the systemd unit starts us again. The next
start finds the routes in the kernel, says so in its OPENs (R, and F where
forwarding was kept), and has them go once every peer has sent its
End-of-RIB. Our own End-of-RIB markers for learned routes wait for that
too. SIGTERM still stops for good, now with a Hard Reset where RFC 8538 is
agreed, as are errors of ours.

The status page and API show what was agreed with each peer, the routes
kept while a peer restarts, and which routes are stale. Tested against
FRR and BIRD in a new phase of the interoperability lab, and with routes
kept in a real kernel over a restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:43:47 +02:00

1314 lines
46 KiB
Rust

use std::net::Ipv4Addr;
use bitfield::bitfield;
use bytes::{BufMut, BytesMut};
use nom::{
Err::Failure,
IResult, Parser,
number::complete::{be_u8, be_u16, be_u24, be_u32},
};
use serde::Serialize;
use crate::errors::InvalidValue;
use bgp_primitives::{
constants::{Afi, AfiSafi, Safi},
parser::{BgpParserError, EncodeError, ProtocolErrorValues},
};
type Parsed<'a, T> = IResult<&'a [u8], T, BgpParserError<&'a [u8]>>;
macro_rules! fail {
( $($arg:expr),* $(,)? ) => {
Failure(BgpParserError::Malformed(format!($($arg),*)))
};
}
/// Parses `buf` to its end as a list of `item`s. Unlike nom's `many0`,
/// which stops quietly at an item it cannot read, an error in an item is an
/// error of the list.
fn list<'a, T>(mut buf: &'a [u8], item: impl Fn(&'a [u8]) -> Parsed<'a, T>) -> Parsed<'a, Vec<T>> {
let mut items = Vec::new();
while !buf.is_empty() {
let (rest, parsed) = item(buf)?;
items.push(parsed);
buf = rest;
}
Ok((buf, items))
}
/// The optional parameter type that marks an OPEN whose optional parameters
/// have two-octet lengths (RFC 9072).
const EXTENDED_LENGTH: u8 = 255;
/// The OPEN message (RFC 4271 §4.2).
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct OpenMessage {
/// Version of the BGP protocol in use.
pub version: u8,
/// AS Number of the BGP speaker, or AS_TRANS if using 4 byte ASN.
pub asn: u16,
/// Hold time parameter in seconds.
pub hold_time: u16,
/// Global identifier of the BGP speaker.
pub identifier: Ipv4Addr,
/// Options.
pub options: Vec<OpenOption>,
}
impl OpenMessage {
/// Parses the body of an OPEN (what follows the 19-octet header).
/// Returns what is left of `buf` after the optional parameters, which
/// in a well-formed OPEN is nothing.
pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> {
let (buf, version) = be_u8(buf)?;
if version != 4 {
return Err(Failure(BgpParserError::ProtocolError(
ProtocolErrorValues::UnsupportedVersion(version),
)));
}
let (buf, asn) = be_u16(buf)?;
let (buf, hold_time) = be_u16(buf)?;
let (buf, identifier) = be_u32(buf)?;
// RFC 9072: when the first parameter type is 255, the real length
// of the optional parameters follows in two octets, and each
// parameter has a two-octet length as well. An OPEN without
// optional parameters ends at the length octet.
let (buf, opt_len) = be_u8(buf)?;
let extended = opt_len != 0 && buf.first() == Some(&EXTENDED_LENGTH);
let (buf, opt_len) = if extended {
let (buf, _) = be_u8(buf)?;
let (buf, opt_len) = be_u16(buf)?;
(buf, usize::from(opt_len))
} else {
(buf, usize::from(opt_len))
};
let (buf, opt_buf) = nom::bytes::take(opt_len).parse(buf)?;
let (_, options) = list(opt_buf, |buf| OpenOption::from_wire(extended, buf))?;
Ok((
buf,
Self {
version,
asn,
hold_time,
identifier: Ipv4Addr::from(identifier),
options,
},
))
}
/// Every capability in the message, across all the optional parameters
/// that carry them.
pub fn capabilities(&self) -> impl Iterator<Item = &Capability> {
self.options.iter().flat_map(|option| match option {
OpenOption::Capabilities(capabilities) => capabilities.iter(),
})
}
/// Writes the body of the OPEN.
pub fn to_wire(&self, out: &mut BytesMut) -> Result<(), EncodeError> {
out.put_u8(self.version);
out.put_u16(self.asn);
out.put_u16(self.hold_time);
out.put_u32(self.identifier.into());
// The parameters are written out first: how long they are decides
// which form of lengths the message uses (RFC 9072 §2).
let parameters = self
.options
.iter()
.map(|option| Ok((option.type_code(), option.value()?)))
.collect::<Result<Vec<(u8, BytesMut)>, EncodeError>>()?;
let total = |header: usize| -> usize {
parameters
.iter()
.map(|(_, value)| header + value.len())
.sum()
};
let fits_one_octet = parameters.iter().all(|(_, value)| value.len() <= 255);
if let Ok(length) = u8::try_from(total(2))
&& fits_one_octet
{
out.put_u8(length);
for (type_code, value) in &parameters {
out.put_u8(*type_code);
out.put_u8(value.len() as u8);
out.put_slice(value);
}
} else {
let too_long = |len| EncodeError::TooLong {
what: "OPEN optional parameters",
len,
max: u16::MAX.into(),
};
let length = u16::try_from(total(3)).map_err(|_| too_long(total(3)))?;
out.put_u8(255);
out.put_u8(EXTENDED_LENGTH);
out.put_u16(length);
for (type_code, value) in &parameters {
out.put_u8(*type_code);
// No longer than the total, which fits.
out.put_u16(value.len() as u16);
out.put_slice(value);
}
}
Ok(())
}
}
/// The optional parameter type for capabilities (RFC 5492).
const CAPABILITIES: u8 = 2;
/// An optional parameter of an OPEN.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub enum OpenOption {
Capabilities(Vec<Capability>),
}
impl OpenOption {
/// Parses one optional parameter. `extended` says whether its length is
/// two octets (RFC 9072) or one.
fn from_wire(extended: bool, buf: &[u8]) -> Parsed<'_, Self> {
let (buf, type_code) = be_u8(buf)?;
let (buf, length) = if extended {
let (buf, length) = be_u16(buf)?;
(buf, usize::from(length))
} else {
let (buf, length) = be_u8(buf)?;
(buf, usize::from(length))
};
let (buf, value) = nom::bytes::take(length).parse(buf)?;
match type_code {
CAPABILITIES => {
let (_, capabilities) = list(value, Capability::from_wire)?;
Ok((buf, Self::Capabilities(capabilities)))
}
other => Err(Failure(BgpParserError::ProtocolError(
ProtocolErrorValues::UnknownOpenOption(other),
))),
}
}
fn type_code(&self) -> u8 {
match self {
Self::Capabilities(_) => CAPABILITIES,
}
}
/// The parameter's value on the wire.
fn value(&self) -> Result<BytesMut, EncodeError> {
let mut value = BytesMut::new();
match self {
Self::Capabilities(capabilities) => {
for capability in capabilities {
capability.to_wire(&mut value)?;
}
}
}
Ok(value)
}
}
/// One address family in a Multiple Labels capability (RFC 8277 §2.1).
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub struct MultiLabelEntry {
pub afi: Afi,
pub safi: Safi,
/// Maximum number of labels in one NLRI of this family.
pub count: u8,
}
/// Capability codes (the IANA "Capability Codes" registry).
mod cap_type {
pub const MULTI_PROTOCOL: u8 = 1;
pub const ROUTE_REFRESH: u8 = 2;
pub const OUTBOUND_ROUTE_FILTER: u8 = 3;
pub const EXTENDED_NEXT_HOP: u8 = 5;
pub const EXTENDED_MESSAGE: u8 = 6;
pub const BGP_SEC: u8 = 7;
pub const MULTI_LABEL: u8 = 8;
pub const BGP_ROLE: u8 = 9;
pub const GRACEFUL_RESTART: u8 = 64;
pub const FOUR_BYTE_ASN: u8 = 65;
pub const ADD_PATH: u8 = 69;
pub const ENHANCED_ROUTE_REFRESH: u8 = 70;
pub const LONG_LIVED_GRACEFUL_RESTART: u8 = 71;
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub enum Capability {
/// Multiprotocol Extensions (RFC 4760).
MultiProtocol {
afi: Afi,
safi: Safi,
},
/// Route Refresh capability (RFC 2918).
RouteRefresh,
/// Outbound Route Filtering (RFC 5291): for each address family, the
/// kinds of filter the speaker can send or receive.
OutboundRouteFilter(Vec<OrfFamily>),
/// Extended Next Hop encoding (RFC 8950).
ExtendedNextHop {
entries: Vec<ExtendedNextHop>,
},
/// Extended Message (RFC 8654).
ExtendedMessage,
/// BGPSec (RFC 8205).
BgpSec {
header: BgpSecHeader,
afi: Afi,
},
/// Multiple labels (RFC 8277 §2.1): for each address family, how many
/// labels the speaker can handle in one NLRI.
MultiLabelCompat {
entries: Vec<MultiLabelEntry>,
},
BgpRole {
role: BgpRole,
},
/// Graceful restart capability (RFC 4724).
GracefulRestart {
/// The speaker has just restarted, and its peer must not wait for
/// its End-of-RIB marker before advertising routes to it.
restart_state: bool,
/// The estimated time in seconds for the session to be
/// re-established after a restart.
restart_time: u16,
/// The speaker applies graceful restart to sessions that end with
/// a NOTIFICATION too, other than a Hard Reset (RFC 8538).
notification: bool,
/// The address families whose routes are kept over a restart.
entries: Vec<GracefulRestartEntry>,
},
/// Four-octet AS numbers (RFC 6793).
FourByteAsn {
asn: u32,
},
/// ADD-PATH (RFC 7911): for each address family listed, whether the
/// speaker can send several routes for a prefix, receive them, or both.
AddPath {
entries: Vec<AddPathEntry>,
},
/// Enhanced Route Refresh (RFC 7313).
EnhancedRouteRefresh,
/// Long Lived Graceful Restart (RFC 9494).
LongLivedGracefulRestart(Vec<LongLivedGracefulRestart>),
/// A capability this implementation has no definition for, kept as it
/// came (RFC 5492 §3: what is not understood is ignored).
UnknownCapability {
type_code: u8,
value: Vec<u8>,
},
}
/// An AFI, a reserved octet and a SAFI: how several capabilities name an
/// address family.
fn afi_reserved_safi(buf: &[u8]) -> Parsed<'_, (Afi, Safi)> {
let (buf, afi) = be_u16(buf)?;
let (buf, _) = be_u8(buf)?;
let (buf, safi) = be_u8(buf)?;
Ok((buf, (Afi(afi), Safi(safi))))
}
fn afi_safi(buf: &[u8]) -> Parsed<'_, (Afi, Safi)> {
let (buf, afi) = be_u16(buf)?;
let (buf, safi) = be_u8(buf)?;
Ok((buf, (Afi(afi), Safi(safi))))
}
impl Capability {
fn type_code(&self) -> u8 {
use cap_type::*;
match self {
Self::MultiProtocol { .. } => MULTI_PROTOCOL,
Self::RouteRefresh => ROUTE_REFRESH,
Self::OutboundRouteFilter(_) => OUTBOUND_ROUTE_FILTER,
Self::ExtendedNextHop { .. } => EXTENDED_NEXT_HOP,
Self::ExtendedMessage => EXTENDED_MESSAGE,
Self::BgpSec { .. } => BGP_SEC,
Self::MultiLabelCompat { .. } => MULTI_LABEL,
Self::BgpRole { .. } => BGP_ROLE,
Self::GracefulRestart { .. } => GRACEFUL_RESTART,
Self::FourByteAsn { .. } => FOUR_BYTE_ASN,
Self::AddPath { .. } => ADD_PATH,
Self::EnhancedRouteRefresh => ENHANCED_ROUTE_REFRESH,
Self::LongLivedGracefulRestart(_) => LONG_LIVED_GRACEFUL_RESTART,
Self::UnknownCapability { type_code, .. } => *type_code,
}
}
/// The Multiprotocol capability that offers `family` to a peer.
pub fn multiprotocol(family: AfiSafi) -> Self {
Self::MultiProtocol {
afi: family.afi,
safi: family.safi,
}
}
/// Parses one capability from the front of `buf`.
///
/// Address families are read as numbers, whatever they are: a peer is
/// free to advertise families we have never heard of, and RFC 5492 §3
/// has us ignore what we do not support rather than refuse the OPEN.
pub fn from_wire(buf: &[u8]) -> Parsed<'_, Self> {
let (buf, type_code) = be_u8(buf)?;
let (buf, length) = be_u8(buf)?;
let (buf, value) = nom::bytes::take(length).parse(buf)?;
let capability = Self::parse_value(type_code, value)?;
Ok((buf, capability))
}
/// Makes the capability of type `type_code` from its value. All of
/// `value` has to be used.
fn parse_value(type_code: u8, value: &[u8]) -> Result<Self, nom::Err<BgpParserError<&[u8]>>> {
use cap_type::*;
let (rest, capability) = match type_code {
MULTI_PROTOCOL => {
let (rest, (afi, safi)) = afi_reserved_safi(value)?;
(rest, Self::MultiProtocol { afi, safi })
}
ROUTE_REFRESH => (value, Self::RouteRefresh),
OUTBOUND_ROUTE_FILTER => {
let (rest, families) = list(value, OrfFamily::from_wire)?;
(rest, Self::OutboundRouteFilter(families))
}
EXTENDED_NEXT_HOP => {
let (rest, entries) = list(value, |buf| {
let (buf, afi) = be_u16(buf)?;
let (buf, safi) = be_u16(buf)?;
let (buf, nh_afi) = be_u16(buf)?;
// Two octets on the wire here (RFC 8950), one everywhere
// else: no SAFI is larger than 255.
let safi = u8::try_from(safi)
.map(Safi)
.map_err(|_| fail!("SAFI {} does not fit in one octet", safi))?;
Ok((
buf,
ExtendedNextHop {
afi: Afi(afi),
safi,
nh_afi: Afi(nh_afi),
},
))
})?;
if entries.is_empty() {
return Err(fail!("Extended Next Hop capability with no entries"));
}
(rest, Self::ExtendedNextHop { entries })
}
EXTENDED_MESSAGE => (value, Self::ExtendedMessage),
BGP_SEC => {
let (rest, header) = be_u8(value)?;
let (rest, afi) = be_u16(rest)?;
(
rest,
Self::BgpSec {
header: BgpSecHeader(header),
afi: Afi(afi),
},
)
}
MULTI_LABEL => {
let (rest, entries) = list(value, |buf| {
let (buf, (afi, safi)) = afi_safi(buf)?;
let (buf, count) = be_u8(buf)?;
Ok((buf, MultiLabelEntry { afi, safi, count }))
})?;
if entries.is_empty() {
return Err(fail!("Multiple Labels capability with no entries"));
}
(rest, Self::MultiLabelCompat { entries })
}
BGP_ROLE => {
let (rest, role) = be_u8(value)?;
(
rest,
Self::BgpRole {
role: BgpRole::from(role),
},
)
}
GRACEFUL_RESTART => {
// Four bits of flags and twelve of restart time.
let (rest, flags_restart_time) = be_u16(value)?;
let (rest, entries) = list(rest, |buf| {
let (buf, (afi, safi)) = afi_safi(buf)?;
let (buf, flags) = be_u8(buf)?;
Ok((
buf,
GracefulRestartEntry {
afi,
safi,
preserve_forwarding: flags & (1 << 7) != 0,
},
))
})?;
(
rest,
Self::GracefulRestart {
restart_state: flags_restart_time & (1 << 15) != 0,
notification: flags_restart_time & (1 << 14) != 0,
restart_time: flags_restart_time & 0xfff,
entries,
},
)
}
FOUR_BYTE_ASN => {
let (rest, asn) = be_u32(value)?;
(rest, Self::FourByteAsn { asn })
}
ADD_PATH => {
// A list of families, four octets each. None at all says
// nothing and is let through: it is also what is left of a
// capability whose families were all ignored below.
let (entries, rest) = value.as_chunks::<4>();
let entries = entries
.iter()
.filter_map(|entry| {
// A family with a Send/Receive value that is not
// defined is ignored (RFC 7911 §4).
Some(AddPathEntry {
afi: Afi(u16::from_be_bytes([entry[0], entry[1]])),
safi: Safi(entry[2]),
send_recv: AddPathSendRecv::try_from(entry[3]).ok()?,
})
})
.collect();
(rest, Self::AddPath { entries })
}
ENHANCED_ROUTE_REFRESH => (value, Self::EnhancedRouteRefresh),
LONG_LIVED_GRACEFUL_RESTART => {
let (rest, entries) = list(value, |buf| {
let (buf, (afi, safi)) = afi_safi(buf)?;
let (buf, flags) = be_u8(buf)?;
let (buf, stale_time) = be_u24(buf)?;
Ok((
buf,
LongLivedGracefulRestart {
afi,
safi,
flags,
stale_time,
},
))
})?;
(rest, Self::LongLivedGracefulRestart(entries))
}
type_code => (
&[][..],
Self::UnknownCapability {
type_code,
value: value.to_vec(),
},
),
};
if !rest.is_empty() {
return Err(fail!(
"{} octets left over in capability {type_code}",
rest.len()
));
}
Ok(capability)
}
/// Writes the capability, with its type and length, to the end of
/// `out`.
pub fn to_wire(&self, out: &mut BytesMut) -> Result<(), EncodeError> {
out.put_u8(self.type_code());
// The length, filled in once the value is written.
let length_at = out.len();
out.put_u8(0);
self.value_to_wire(out)?;
let length = out.len() - length_at - 1;
out[length_at] = u8::try_from(length).map_err(|_| EncodeError::TooLong {
what: "capability",
len: length,
max: u8::MAX.into(),
})?;
Ok(())
}
fn value_to_wire(&self, out: &mut BytesMut) -> Result<(), EncodeError> {
match self {
Capability::MultiProtocol { afi, safi } => {
out.put_u16(afi.0);
out.put_u8(0); // Reserved.
out.put_u8(safi.0);
}
Capability::RouteRefresh
| Capability::ExtendedMessage
| Capability::EnhancedRouteRefresh => {}
Capability::OutboundRouteFilter(families) => {
for family in families {
family.to_wire(out)?;
}
}
Capability::ExtendedNextHop { entries } => {
for entry in entries {
out.put_u16(entry.afi.0);
out.put_u16(u16::from(entry.safi.0));
out.put_u16(entry.nh_afi.0);
}
}
Capability::BgpSec { header, afi } => {
out.put_u8(header.0);
out.put_u16(afi.0);
}
Capability::MultiLabelCompat { entries } => {
for entry in entries {
out.put_u16(entry.afi.0);
out.put_u8(entry.safi.0);
out.put_u8(entry.count);
}
}
Capability::BgpRole { role } => out.put_u8(role.into()),
Capability::GracefulRestart {
restart_state,
restart_time,
notification,
entries,
} => {
if *restart_time > 0xfff {
return Err(EncodeError::TooLong {
what: "graceful restart time",
len: (*restart_time).into(),
max: 0xfff,
});
}
out.put_u16(
u16::from(*restart_state) << 15 | u16::from(*notification) << 14 | restart_time,
);
for entry in entries {
out.put_u16(entry.afi.0);
out.put_u8(entry.safi.0);
out.put_u8(u8::from(entry.preserve_forwarding) << 7);
}
}
Capability::FourByteAsn { asn } => out.put_u32(*asn),
Capability::AddPath { entries } => {
for entry in entries {
out.put_u16(entry.afi.0);
out.put_u8(entry.safi.0);
out.put_u8(entry.send_recv as u8);
}
}
Capability::LongLivedGracefulRestart(entries) => {
for entry in entries {
out.put_u16(entry.afi.0);
out.put_u8(entry.safi.0);
out.put_u8(entry.flags);
out.put_slice(&entry.stale_time.to_be_bytes()[1..]);
}
}
Capability::UnknownCapability { value, .. } => out.put_slice(value),
}
Ok(())
}
}
/// One address family in an Outbound Route Filtering capability, and the
/// kinds of filter offered for it (RFC 5291 §4).
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct OrfFamily {
pub afi: Afi,
pub safi: Safi,
pub orfs: Vec<OutboundRouteFilterEntry>,
}
impl OrfFamily {
fn from_wire(buf: &[u8]) -> Parsed<'_, Self> {
let (buf, (afi, safi)) = afi_reserved_safi(buf)?;
let (mut buf, count) = be_u8(buf)?;
let mut orfs = Vec::with_capacity(count.into());
for _ in 0..count {
let (rest, orf_type) = be_u8(buf)?;
let (rest, send_recv) = be_u8(rest)?;
let send_recv = OrfSendRecv::try_from(send_recv)
.map_err(|e| fail!("Failed to parse ORF send/recv: {}", e))?;
orfs.push(OutboundRouteFilterEntry {
r#type: OrfType(orf_type),
send_recv,
});
buf = rest;
}
Ok((buf, Self { afi, safi, orfs }))
}
fn to_wire(&self, out: &mut BytesMut) -> Result<(), EncodeError> {
out.put_u16(self.afi.0);
out.put_u8(0); // Reserved.
out.put_u8(self.safi.0);
let count = u8::try_from(self.orfs.len()).map_err(|_| EncodeError::TooLong {
what: "ORF types of one address family",
len: self.orfs.len(),
max: u8::MAX.into(),
})?;
out.put_u8(count);
for orf in &self.orfs {
out.put_u8(orf.r#type.0);
out.put_u8(orf.send_recv as u8);
}
Ok(())
}
}
/// One kind of filter in an Outbound Route Filtering capability.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct OutboundRouteFilterEntry {
pub r#type: OrfType,
pub send_recv: OrfSendRecv,
}
/// An ORF type. Any number is one: most are unassigned or vendor-specific.
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct OrfType(pub u8);
impl OrfType {
pub const ADDRESS_PREFIX: Self = Self(64);
pub const COVERING_PREFIX: Self = Self(65);
pub const VPN_PREFIX: Self = Self(66);
}
/// Whether a speaker sends, receives, or does both, in a capability that
/// is offered for each direction apart: ORF (RFC 5291) and ADD-PATH
/// (RFC 7911) use the same three values.
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[repr(u8)]
pub enum SendReceive {
Receive = 1,
Send = 2,
Both = 3,
}
pub type OrfSendRecv = SendReceive;
pub type AddPathSendRecv = SendReceive;
impl SendReceive {
/// Whether the speaker that advertised this sends.
pub fn sends(self) -> bool {
matches!(self, Self::Send | Self::Both)
}
/// Whether the speaker that advertised this receives.
pub fn receives(self) -> bool {
matches!(self, Self::Receive | Self::Both)
}
}
impl TryFrom<u8> for SendReceive {
type Error = InvalidValue;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Receive),
2 => Ok(Self::Send),
3 => Ok(Self::Both),
other => Err(InvalidValue {
what: "send/receive",
value: other.into(),
}),
}
}
}
/// One address family in an ADD-PATH capability.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AddPathEntry {
pub afi: Afi,
pub safi: Safi,
pub send_recv: AddPathSendRecv,
}
impl AddPathEntry {
pub fn family(&self) -> AfiSafi {
AfiSafi::new(self.afi, self.safi)
}
}
/// `ExtendedNextHop` allows for the nexthop to be for a different address family
/// than the one being used to advertise prefixes for. E.g. it allows for sending
/// IPv4 prefixes with an IPv6 nexthop.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct ExtendedNextHop {
/// The Address Family to update the nexthop format for.
pub afi: Afi,
/// The Subsequent Address Family to update the nexthop format for.
pub safi: Safi,
/// The Address Family that nexthops will be encoded as.
pub nh_afi: Afi,
}
/// The role a speaker has towards its peer (RFC 9234 §4).
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
pub enum BgpRole {
Provider,
RouteServer,
RouteServerClient,
Customer,
Peer,
Unknown(u8),
}
impl From<u8> for BgpRole {
fn from(value: u8) -> Self {
match value {
0 => Self::Provider,
1 => Self::RouteServer,
2 => Self::RouteServerClient,
3 => Self::Customer,
4 => Self::Peer,
other => Self::Unknown(other),
}
}
}
impl From<&BgpRole> for u8 {
fn from(value: &BgpRole) -> Self {
match value {
BgpRole::Provider => 0,
BgpRole::RouteServer => 1,
BgpRole::RouteServerClient => 2,
BgpRole::Customer => 3,
BgpRole::Peer => 4,
BgpRole::Unknown(other) => *other,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct LongLivedGracefulRestart {
pub afi: Afi,
pub safi: Safi,
pub flags: u8,
/// stale_time is the time in seconds after which routes are considered stale.
/// Note that this is represented as a u24 on the wire.
pub stale_time: u32,
}
bitfield! {
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
pub struct BgpSecHeader(u8);
impl new;
u8, version, set_version : 3, 0; // 4 bits
bool, dir, set_dir : 4; // single bit (hi==lo)
u8, unassigned, set_unassigned : 7, 5; // 3 bits
}
/// Represents an entry in the BGP Graceful Restart capability.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct GracefulRestartEntry {
pub afi: Afi,
pub safi: Safi,
/// Forwarding State (F) bit, which can be used to indicate whether the
/// forwarding state for routes that were advertised with the given AFI
/// and SAFI has indeed been preserved during the previous BGP restart.
pub preserve_forwarding: bool,
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use bytes::BytesMut;
use eyre::Result;
use crate::open::{
AddPathEntry, AddPathSendRecv, Capability, ExtendedNextHop, GracefulRestartEntry,
MultiLabelEntry, OpenMessage, OpenOption, OrfFamily, OrfSendRecv, OrfType,
OutboundRouteFilterEntry,
};
use bgp_primitives::constants::{Afi, Safi};
macro_rules! test_capability_roundtrip {
($name:ident, $input_bytes: expr, $expected: expr) => {
#[test]
fn $name() -> Result<()> {
let (buf, parsed) = Capability::from_wire($input_bytes)?;
assert_eq!(parsed, $expected);
assert!(buf.is_empty());
let mut out = BytesMut::with_capacity(u16::MAX as usize);
parsed.to_wire(&mut out)?;
assert_eq!(out.to_vec(), $input_bytes);
Ok(())
}
};
}
test_capability_roundtrip!(
test_multiptotocol,
&[0x01, 0x04, 0x00, 0x01, 0x00, 0x01],
Capability::MultiProtocol {
afi: Afi::IPV4,
safi: Safi::UNICAST
}
);
test_capability_roundtrip!(test_route_refresh, &[0x02, 0x00], Capability::RouteRefresh);
test_capability_roundtrip!(
test_route_refresh_cisco_unsupported,
&[0x80, 0x00],
Capability::UnknownCapability {
type_code: 0x80,
value: vec![]
}
);
test_capability_roundtrip!(
test_four_octet_asn,
&[0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8],
Capability::FourByteAsn { asn: 65000 }
);
test_capability_roundtrip!(
test_graceful_restart,
&[0x40, 0x06, 0x01, 0x2c, 0x00, 0x01, 0x01, 0x00],
Capability::GracefulRestart {
restart_state: false,
restart_time: 300,
notification: false,
entries: vec![GracefulRestartEntry {
afi: Afi::IPV4,
safi: Safi::UNICAST,
preserve_forwarding: false,
}]
}
);
// Restarted (R), with Notification support (N, RFC 8538), and the
// forwarding of IPv6 routes kept (F).
test_capability_roundtrip!(
test_graceful_restart_with_flags,
&[0x40, 0x06, 0xc0, 0x78, 0x00, 0x02, 0x01, 0x80],
Capability::GracefulRestart {
restart_state: true,
restart_time: 120,
notification: true,
entries: vec![GracefulRestartEntry {
afi: Afi::IPV6,
safi: Safi::UNICAST,
preserve_forwarding: true,
}]
}
);
test_capability_roundtrip!(
test_extended_nexthop,
&[
0x05, 0x12, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
0x00, 0x01, 0x00, 0x80, 0x00, 0x02
],
Capability::ExtendedNextHop {
entries: vec![
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::UNICAST,
nh_afi: Afi::IPV6,
},
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::MULTICAST,
nh_afi: Afi::IPV6,
},
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::MPLS_VPN,
nh_afi: Afi::IPV6
}
]
}
);
#[test]
fn test_open_roundtrip() -> Result<()> {
let buf = &[
0x04, 0xfd, 0xe8, 0x00, 0xb4, 0x0a, 0x00, 0x00, 0x01, 0x38, 0x02, 0x06, 0x01, 0x04,
0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x06,
0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, 0x02, 0x08, 0x40, 0x06, 0x01, 0x2c, 0x00, 0x01,
0x01, 0x00, 0x02, 0x14, 0x05, 0x12, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01,
0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x80, 0x00, 0x02,
];
let expected = OpenMessage {
version: 4,
asn: 65000,
hold_time: 180,
identifier: Ipv4Addr::new(10, 0, 0, 1),
options: vec![
OpenOption::Capabilities(vec![Capability::MultiProtocol {
afi: Afi::IPV4,
safi: Safi::UNICAST,
}]),
OpenOption::Capabilities(vec![Capability::UnknownCapability {
type_code: 0x80,
value: vec![],
}]),
OpenOption::Capabilities(vec![Capability::RouteRefresh]),
OpenOption::Capabilities(vec![Capability::FourByteAsn { asn: 65000 }]),
OpenOption::Capabilities(vec![Capability::GracefulRestart {
restart_state: false,
restart_time: 300,
notification: false,
entries: vec![GracefulRestartEntry {
afi: Afi::IPV4,
safi: Safi::UNICAST,
preserve_forwarding: false,
}],
}]),
OpenOption::Capabilities(vec![Capability::ExtendedNextHop {
entries: vec![
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::UNICAST,
nh_afi: Afi::IPV6,
},
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::MULTICAST,
nh_afi: Afi::IPV6,
},
ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::MPLS_VPN,
nh_afi: Afi::IPV6,
},
],
}]),
],
};
let (_, parsed) = OpenMessage::from_wire(buf)?;
assert_eq!(parsed, expected);
let mut out = BytesMut::with_capacity(u16::MAX as usize);
parsed.to_wire(&mut out)?;
assert_eq!(buf.to_vec(), out.to_vec());
Ok(())
}
/// 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() {
// 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(&wire).unwrap();
assert!(rest.is_empty());
assert_eq!(
cap,
Capability::MultiLabelCompat {
entries: vec![
MultiLabelEntry {
afi: Afi::IPV4,
safi: Safi::MPLS_LABEL,
count: 2
},
MultiLabelEntry {
afi: Afi::IPV6,
safi: Safi::MPLS_LABEL,
count: 3
},
]
}
);
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], &wire[..]);
// A truncated entry is a parse error, not a panic.
assert!(Capability::from_wire(&[0x08, 0x03, 0x00, 0x01, 0x04]).is_err());
}
/// A peer may offer families this implementation does not support
/// (here L2VPN EVPN, AFI 25 SAFI 70). That used to fail the whole OPEN.
/// The family is read as the numbers it is, and what follows it too.
#[test]
fn a_capability_may_name_any_address_family() {
let evpn = (Afi::L2VPN, Safi::EVPN);
let samples: &[(&[u8], Capability)] = &[
(
&[0x01, 0x04, 0x00, 0x19, 0x00, 0x46],
Capability::MultiProtocol {
afi: evpn.0,
safi: evpn.1,
},
),
(
&[0x45, 0x04, 0x00, 0x19, 0x46, 0x03],
Capability::AddPath {
entries: vec![AddPathEntry {
afi: evpn.0,
safi: evpn.1,
send_recv: AddPathSendRecv::Both,
}],
},
),
(
&[0x40, 0x06, 0x00, 0x78, 0x00, 0x19, 0x46, 0x80],
Capability::GracefulRestart {
restart_state: false,
restart_time: 120,
notification: false,
entries: vec![GracefulRestartEntry {
afi: evpn.0,
safi: evpn.1,
preserve_forwarding: true,
}],
},
),
];
for (wire, expected) in samples {
let mut input = wire.to_vec();
input.extend_from_slice(&[0x02, 0x00]);
let (rest, cap) = Capability::from_wire(&input).unwrap();
assert_eq!(rest, [0x02, 0x00], "{expected:?}");
assert_eq!(cap, *expected);
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], *wire, "{expected:?}");
}
// A capability that is simply malformed is still an error.
assert!(Capability::from_wire(&[0x01, 0x03, 0x00, 0x01, 0x00]).is_err());
}
/// One ADD-PATH capability lists every family it is for. Reading only
/// one made the OPEN of a router with ADD-PATH for both IPv4 and IPv6
/// malformed, so the session never came up.
#[test]
fn add_path_capability_lists_several_families() {
// IPv4 unicast: receive. IPv6 unicast: send and receive.
let wire = [0x45, 0x08, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x01, 0x03];
let (rest, cap) = Capability::from_wire(&wire).unwrap();
assert!(rest.is_empty());
assert_eq!(
cap,
Capability::AddPath {
entries: vec![
AddPathEntry {
afi: Afi::IPV4,
safi: Safi::UNICAST,
send_recv: AddPathSendRecv::Receive,
},
AddPathEntry {
afi: Afi::IPV6,
safi: Safi::UNICAST,
send_recv: AddPathSendRecv::Both,
},
],
}
);
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], &wire[..]);
// A family with a Send/Receive value nobody defined is left out;
// the others stand.
let odd = [0x45, 0x08, 0x00, 0x01, 0x01, 0x07, 0x00, 0x02, 0x01, 0x02];
let (_, cap) = Capability::from_wire(&odd).unwrap();
assert!(
matches!(&cap, Capability::AddPath { entries } if entries.len() == 1 && entries[0].afi == Afi::IPV6)
);
// Not a whole number of families: malformed.
assert!(Capability::from_wire(&[0x45, 0x05, 0, 1, 1, 1, 0]).is_err());
}
// Found by the property tests in tests/properties.rs: both were written
// differently from how they are read, so an OPEN of ours carrying one
// would have been malformed.
#[test]
fn outbound_route_filter_capability_is_written_with_its_count() {
// IPv4 unicast, one ORF: address prefix (64), both directions.
let wire = [0x03, 0x07, 0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x03];
let (rest, cap) = Capability::from_wire(&wire).unwrap();
assert!(rest.is_empty());
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], &wire[..]);
}
/// One ORF capability may list several address families, each with its
/// own count of ORF types (RFC 5291 §4). The second family used to be
/// read as more ORF types of the first, which made the OPEN of a router
/// offering prefix filters for IPv4 and IPv6 malformed.
#[test]
fn outbound_route_filter_capability_lists_several_families() {
#[rustfmt::skip]
let wire = [
0x03, 0x0e,
0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x03, // IPv4 unicast: prefix, both
0x00, 0x02, 0x00, 0x01, 0x01, 0x40, 0x01, // IPv6 unicast: prefix, receive
];
let (rest, cap) = Capability::from_wire(&wire).unwrap();
assert!(rest.is_empty());
let family = |afi, send_recv| OrfFamily {
afi,
safi: Safi::UNICAST,
orfs: vec![OutboundRouteFilterEntry {
r#type: OrfType::ADDRESS_PREFIX,
send_recv,
}],
};
assert_eq!(
cap,
Capability::OutboundRouteFilter(vec![
family(Afi::IPV4, OrfSendRecv::Both),
family(Afi::IPV6, OrfSendRecv::Receive),
])
);
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], &wire[..]);
// A count that promises more ORF types than there are.
assert!(Capability::from_wire(&[0x03, 0x05, 0x00, 0x01, 0x00, 0x01, 0x01]).is_err());
}
/// Optional parameters longer than 255 octets use the lengths of
/// RFC 9072: two octets for the total and for each parameter.
#[test]
fn long_optional_parameters_use_two_octet_lengths() {
let capabilities: Vec<Capability> = (0..60)
.map(|i| Capability::UnknownCapability {
type_code: 200,
value: vec![i; 4],
})
.collect();
let open = OpenMessage {
version: 4,
asn: 65000,
hold_time: 90,
identifier: Ipv4Addr::new(10, 0, 0, 1),
options: vec![OpenOption::Capabilities(capabilities)],
};
let mut out = BytesMut::new();
open.to_wire(&mut out).unwrap();
// 60 capabilities of 6 octets in one parameter.
#[rustfmt::skip]
assert_eq!(
&out[9..16],
[
255, 255, 0x01, 0x6b, // extended: 363 octets of parameters
2, 0x01, 0x68, // capabilities, 360 octets
]
);
assert_eq!(out.len(), 16 + 360);
let (rest, parsed) = OpenMessage::from_wire(&out).unwrap();
assert!(rest.is_empty());
assert_eq!(parsed, open);
// The same form is read when it was not needed.
#[rustfmt::skip]
let short = [
4, 0xfd, 0xe8, 0, 90, 10, 0, 0, 1,
255, 255, 0, 5,
2, 0, 2, 0x02, 0x00,
];
let (rest, parsed) = OpenMessage::from_wire(&short).unwrap();
assert!(rest.is_empty());
assert_eq!(
parsed.capabilities().collect::<Vec<_>>(),
[&Capability::RouteRefresh]
);
}
#[test]
fn long_lived_graceful_restart_entries_are_seven_octets() {
// IPv4 unicast, no flags, stale time 3600 s.
let wire = [0x47, 0x07, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0e, 0x10];
let (rest, cap) = Capability::from_wire(&wire).unwrap();
assert!(rest.is_empty());
let mut out = BytesMut::new();
cap.to_wire(&mut out).unwrap();
assert_eq!(&out[..], &wire[..]);
}
/// A capability's value has a one-octet length. One that does not fit
/// used to be written with the length wrapped around.
#[test]
fn a_capability_too_long_for_its_length_octet_is_refused() {
let entry = ExtendedNextHop {
afi: Afi::IPV4,
safi: Safi::UNICAST,
nh_afi: Afi::IPV6,
};
let fits = Capability::ExtendedNextHop {
entries: vec![entry.clone(); 42],
};
let mut out = BytesMut::new();
fits.to_wire(&mut out).unwrap();
assert_eq!(out.len(), 2 + 252);
let too_long = Capability::ExtendedNextHop {
entries: vec![entry; 43],
};
assert!(too_long.to_wire(&mut BytesMut::new()).is_err());
}
/// Each capability must leave the bytes that follow it for the caller.
/// 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.
#[test]
fn capability_parsers_leave_the_following_bytes() {
let trailer: &[u8] = &[0x41, 0x04, 0x00, 0x03, 0x34, 0x74];
let samples: &[(&str, &[u8])] = &[
("multiprotocol", &[0x01, 0x04, 0x00, 0x02, 0x00, 0x01]),
("route refresh", &[0x02, 0x00]),
(
"extended next hop",
&[0x05, 0x06, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02],
),
("extended message", &[0x06, 0x00]),
("role", &[0x09, 0x01, 0x00]),
("graceful restart, aware only", &[0x40, 0x02, 0x00, 0x00]),
(
"graceful restart, one family",
&[0x40, 0x06, 0x00, 0x78, 0x00, 0x02, 0x01, 0x80],
),
("4-octet asn", &[0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8]),
("add-path", &[0x45, 0x04, 0x00, 0x02, 0x01, 0x03]),
("enhanced route refresh", &[0x46, 0x00]),
("long-lived graceful restart, none", &[0x47, 0x00]),
("unknown (fqdn)", &[0x49, 0x02, b'a', b'b']),
];
for (name, bytes) in samples {
let mut input = bytes.to_vec();
input.extend_from_slice(trailer);
let (rest, _) = Capability::from_wire(&input)
.unwrap_or_else(|e| panic!("{name}: failed to parse: {e:?}"));
assert_eq!(
rest, trailer,
"{name}: did not return the bytes following the capability"
);
}
}
/// The capability list of a BIRD 2 OPEN, end to end.
#[test]
fn bird_capability_list_is_parsed_completely() {
#[rustfmt::skip]
let param: &[u8] = &[
0x02, 0x18,
0x01, 0x04, 0x00, 0x02, 0x00, 0x01,
0x02, 0x00,
0x06, 0x00,
0x40, 0x02, 0x00, 0x00,
0x41, 0x04, 0x00, 0x03, 0x34, 0x74,
0x46, 0x00,
0x47, 0x00,
];
let (rest, OpenOption::Capabilities(caps)) = OpenOption::from_wire(false, param).unwrap();
assert!(rest.is_empty());
assert_eq!(caps.len(), 7, "{caps:?}");
assert!(caps.contains(&Capability::FourByteAsn { asn: 210036 }));
}
}