feature: Implement more path attributes

This commit is contained in:
Rayhaan Jaufeerally
2025-07-20 15:25:41 +02:00
parent 4b130985da
commit ab20160014
5 changed files with 635 additions and 40 deletions

35
Cargo.lock generated
View File

@ -56,6 +56,7 @@ dependencies = [
"nom", "nom",
"serde", "serde",
"serde_repr", "serde_repr",
"strum",
"thiserror", "thiserror",
] ]
@ -113,6 +114,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "indenter" name = "indenter"
version = "0.3.3" version = "0.3.3"
@ -247,6 +254,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -309,6 +322,28 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.100"

View File

@ -1,29 +1,30 @@
[workspace] [workspace]
default-members = ["bin/bgp"] default-members = ["bin/bgp"]
members = ["bin/*", "crates/*"] members = ["bin/*", "crates/*"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"] authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
description = "A Border Gateway Protocol implementation" description = "A Border Gateway Protocol implementation"
edition = "2024" edition = "2024"
homepage = "https://rayhaan.ch" homepage = "https://rayhaan.ch"
license = "Apache-2.0" license = "Apache-2.0"
name = "bgp" name = "bgp"
repository = "https://github.com/net-control-plane/bgp" repository = "https://github.com/net-control-plane/bgp"
version = "0.0.1" version = "0.0.1"
[workspace.dependencies] [workspace.dependencies]
# --- Our crates --- # --- Our crates ---
bgp-packet = { path = "crates/packet" } bgp-packet = { path = "crates/packet" }
# --- General --- # --- General ---
bitfield = "0.19.0" bitfield = "0.19.0"
bytes = "1.10.1" bytes = "1.10.1"
eyre = "0.6.12" eyre = "0.6.12"
nom = "8.0.0" nom = "8.0.0"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"
serde_repr = "0.1.20" serde_repr = "0.1.20"
thiserror = "2.0.12" thiserror = "2.0.12"
tokio = { version = "1.44.2", features = ["full"] } tokio = { version = "1.44.2", features = ["full"] }
strum = { version = "0.27.1", features = ["derive"] }

7
bin/bgp/README.md Normal file
View File

@ -0,0 +1,7 @@
# BGP
This crate provides a binary which runs the Border Gateway Protocol (BGP).
Please refer to the other implementation crates part of this repository where most of the actual implemntation lives.
Warning: This is still very much a work in progress and is not yet ready for use.

View File

@ -16,3 +16,4 @@ nom.workspace = true
serde.workspace = true serde.workspace = true
serde_repr.workspace = true serde_repr.workspace = true
thiserror.workspace = true thiserror.workspace = true
strum.workspace = true

View File

@ -1,4 +1,5 @@
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::ops::Deref;
use bitfield::bitfield; use bitfield::bitfield;
use bytes::BufMut; use bytes::BufMut;
@ -15,6 +16,7 @@ use nom::number::complete::be_u16;
use nom::number::complete::be_u32; use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use strum::EnumDiscriminants;
use crate::constants::AddressFamilyId; use crate::constants::AddressFamilyId;
use crate::constants::SubsequentAfi; use crate::constants::SubsequentAfi;
@ -108,19 +110,29 @@ pub enum Capability {
pub struct UpdateMessage {} pub struct UpdateMessage {}
bitfield! { bitfield! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PathAttributeFlags(u8); pub struct PathAttributeFlags(u8);
impl Debug; impl new;
u8; u8;
optional, set_optional: 0; optional, set_optional: 7;
transitive, set_transitive: 1; transitive, set_transitive: 6;
partial, set_partial: 2; partial, set_partial: 5;
extended_length, set_extended_length: 3; extended_length, set_extended_length: 4;
} }
impl Deref for PathAttributeFlags {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, EnumDiscriminants)]
#[repr(u8)] #[repr(u8)]
pub enum PathAttribute { pub enum PathAttribute {
Origin(OriginPathAttribute) = 1, Origin(OriginPathAttribute) = 1,
ASPath(AsPathAttribute) = 2, AsPath(AsPathAttribute) = 2,
NextHop(NextHopPathAttribute) = 3, NextHop(NextHopPathAttribute) = 3,
MultiExitDisc(MultiExitDiscPathAttribute) = 4, MultiExitDisc(MultiExitDiscPathAttribute) = 4,
LocalPref(LocalPrefPathAttribute) = 5, LocalPref(LocalPrefPathAttribute) = 5,
@ -138,7 +150,85 @@ pub enum PathAttribute {
}, },
} }
impl From<u8> for PathAttributeDiscriminants {
fn from(value: u8) -> Self {
match value {
v if v == Self::Origin as u8 => Self::Origin,
v if v == Self::AsPath as u8 => Self::AsPath,
v if v == Self::NextHop as u8 => Self::NextHop,
v if v == Self::MultiExitDisc as u8 => Self::MultiExitDisc,
v if v == Self::LocalPref as u8 => Self::LocalPref,
v if v == Self::AtomicAggregate as u8 => Self::AtomicAggregate,
v if v == Self::Aggregator as u8 => Self::Aggregator,
v if v == Self::Communitites as u8 => Self::Communitites,
v if v == Self::MpReachNlri as u8 => Self::MpReachNlri,
v if v == Self::MpUnreachNlri as u8 => Self::MpUnreachNlri,
v if v == Self::ExtendedCommunities as u8 => Self::ExtendedCommunities,
v if v == Self::LargeCommunities as u8 => Self::LargeCommunities,
_ => Self::UnknownPathAttribute,
}
}
}
impl PathAttribute { impl PathAttribute {
fn discriminant(&self) -> u8 {
match self {
PathAttribute::Origin(_) => PathAttributeDiscriminants::Origin as u8,
PathAttribute::AsPath(_) => PathAttributeDiscriminants::AsPath as u8,
PathAttribute::NextHop(_) => PathAttributeDiscriminants::NextHop as u8,
PathAttribute::MultiExitDisc(_) => PathAttributeDiscriminants::MultiExitDisc as u8,
PathAttribute::LocalPref(_) => PathAttributeDiscriminants::LocalPref as u8,
PathAttribute::AtomicAggregate(_) => PathAttributeDiscriminants::AtomicAggregate as u8,
PathAttribute::Aggregator(_) => PathAttributeDiscriminants::Aggregator as u8,
PathAttribute::Communitites(_) => PathAttributeDiscriminants::Communitites as u8,
PathAttribute::MpReachNlri(_) => PathAttributeDiscriminants::MpReachNlri as u8,
PathAttribute::MpUnreachNlri(_) => PathAttributeDiscriminants::MpUnreachNlri as u8,
PathAttribute::ExtendedCommunities(_) => {
PathAttributeDiscriminants::ExtendedCommunities as u8
}
PathAttribute::LargeCommunities(_) => {
PathAttributeDiscriminants::LargeCommunities as u8
}
PathAttribute::UnknownPathAttribute { type_code, .. } => *type_code,
}
}
fn optional(&self) -> bool {
match self {
PathAttribute::Origin(_) => false,
PathAttribute::AsPath(_) => false,
PathAttribute::NextHop(_) => false,
PathAttribute::MultiExitDisc(_) => true,
PathAttribute::LocalPref(_) => false,
PathAttribute::AtomicAggregate(_) => true,
PathAttribute::Aggregator(_) => true,
PathAttribute::Communitites(_) => true,
PathAttribute::MpReachNlri(_) => true,
PathAttribute::MpUnreachNlri(_) => true,
PathAttribute::ExtendedCommunities(_) => true,
PathAttribute::LargeCommunities(_) => true,
PathAttribute::UnknownPathAttribute { flags, .. } => flags.optional(),
}
}
fn transitive(&self) -> bool {
match self {
PathAttribute::Origin(_) => true,
PathAttribute::AsPath(_) => true,
PathAttribute::NextHop(_) => true,
PathAttribute::MultiExitDisc(_) => false,
PathAttribute::LocalPref(_) => true,
PathAttribute::AtomicAggregate(_) => true,
PathAttribute::Aggregator(_) => false,
PathAttribute::Communitites(_) => true,
PathAttribute::MpReachNlri(_) => false,
PathAttribute::MpUnreachNlri(_) => false,
PathAttribute::ExtendedCommunities(_) => true,
PathAttribute::LargeCommunities(_) => true,
PathAttribute::UnknownPathAttribute { flags, .. } => flags.transitive(),
}
}
/// The from_wire parser for `PathAttribute` consumes type and length which it uses to /// The from_wire parser for `PathAttribute` consumes type and length which it uses to
/// determine how many bytes to take and pass down to the corresponding sub-parser. /// determine how many bytes to take and pass down to the corresponding sub-parser.
pub fn from_wire<'a>( pub fn from_wire<'a>(
@ -154,7 +244,143 @@ impl PathAttribute {
be_u8(buf).map(|(buf, b)| (buf, b as u16))? be_u8(buf).map(|(buf, b)| (buf, b as u16))?
}; };
todo!(); let discriminant = PathAttributeDiscriminants::from(type_code);
Self::parse_known_path_attribute(ctx, buf, discriminant, length)
}
fn parse_known_path_attribute<'a>(
ctx: &ParserContext,
buf: &'a [u8],
discriminant: PathAttributeDiscriminants,
length: u16,
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, pa_buf) = nom::bytes::take(length).parse(buf)?;
let attr: PathAttribute = match discriminant {
PathAttributeDiscriminants::Origin => {
PathAttribute::Origin(OriginPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::AsPath => {
PathAttribute::AsPath(AsPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::NextHop => {
PathAttribute::NextHop(NextHopPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::MultiExitDisc => {
PathAttribute::MultiExitDisc(MultiExitDiscPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::LocalPref => {
PathAttribute::LocalPref(LocalPrefPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::AtomicAggregate => PathAttribute::AtomicAggregate(
AtomicAggregatePathAttribute::from_wire(ctx, pa_buf)?.1,
),
PathAttributeDiscriminants::Aggregator => {
PathAttribute::Aggregator(AggregatorPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::Communitites => {
PathAttribute::Communitites(CommunitiesPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::MpReachNlri => {
PathAttribute::MpReachNlri(MpReachNlriPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::MpUnreachNlri => {
PathAttribute::MpUnreachNlri(MpUnreachNlriPathAttribute::from_wire(ctx, pa_buf)?.1)
}
PathAttributeDiscriminants::ExtendedCommunities => PathAttribute::ExtendedCommunities(
ExtendedCommunitiesPathAttribute::from_wire(ctx, pa_buf)?.1,
),
PathAttributeDiscriminants::LargeCommunities => PathAttribute::LargeCommunities(
LargeCommunitiesPathAttribute::from_wire(ctx, pa_buf)?.1,
),
PathAttributeDiscriminants::UnknownPathAttribute => unreachable!(
"parse_known_path_attribute must never be called with an unknown attribute"
),
};
Ok((buf, attr))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
macro_rules! write_path_attribute {
($out: expr, $attribute: expr) => {
let wire_len = $attribute.wire_len(ctx)?;
let attr_flags = PathAttributeFlags::new(
self.optional(),
self.transitive(),
/*partial=*/ false,
wire_len > 255,
);
out.put_u8(*attr_flags);
out.put_u8(self.discriminant() as u8);
write_wire_len!(out, wire_len);
$attribute.to_wire(ctx, out)?;
};
}
macro_rules! write_wire_len {
($out: expr, $wire_len: expr) => {
if $wire_len > 255 {
$out.put_u16($wire_len as u16);
} else {
$out.put_u8($wire_len as u8);
}
};
}
match self {
PathAttribute::Origin(origin_path_attribute) => {
write_path_attribute!(out, origin_path_attribute);
}
PathAttribute::AsPath(as_path_attribute) => {
write_path_attribute!(out, as_path_attribute);
}
PathAttribute::NextHop(next_hop_path_attribute) => {
write_path_attribute!(out, next_hop_path_attribute);
}
PathAttribute::MultiExitDisc(multi_exit_disc_path_attribute) => {
write_path_attribute!(out, multi_exit_disc_path_attribute);
}
PathAttribute::LocalPref(local_pref_path_attribute) => {
write_path_attribute!(out, local_pref_path_attribute);
}
PathAttribute::AtomicAggregate(atomic_aggregate_path_attribute) => {
write_path_attribute!(out, atomic_aggregate_path_attribute);
}
PathAttribute::Aggregator(aggregator_path_attribute) => {
write_path_attribute!(out, aggregator_path_attribute);
}
PathAttribute::Communitites(communities_path_attribute) => {
write_path_attribute!(out, communities_path_attribute);
}
PathAttribute::MpReachNlri(mp_reach_nlri_path_attribute) => {
write_path_attribute!(out, mp_reach_nlri_path_attribute);
}
PathAttribute::MpUnreachNlri(mp_unreach_nlri_path_attribute) => {
write_path_attribute!(out, mp_unreach_nlri_path_attribute);
}
PathAttribute::ExtendedCommunities(extended_communities_path_attribute) => {
write_path_attribute!(out, extended_communities_path_attribute);
}
PathAttribute::LargeCommunities(large_communities_path_attribute) => {
write_path_attribute!(out, large_communities_path_attribute);
}
PathAttribute::UnknownPathAttribute {
flags,
type_code,
payload,
} => {
out.put_u8(flags.0);
if flags.extended_length() {
out.put_u16(payload.len() as u16);
} else {
out.put_u8(payload.len() as u8);
}
out.put_u8(*type_code);
out.put(payload.as_slice());
}
}
Ok(())
} }
} }
@ -167,6 +393,36 @@ pub enum OriginPathAttribute {
INCOMPLETE = 2, INCOMPLETE = 2,
} }
impl OriginPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, byte) = be_u8(buf)?;
let attr = match byte {
b if b == OriginPathAttribute::IGP as u8 => OriginPathAttribute::IGP,
b if b == OriginPathAttribute::EGP as u8 => OriginPathAttribute::EGP,
b if b == OriginPathAttribute::INCOMPLETE as u8 => OriginPathAttribute::INCOMPLETE,
_ => {
return IResult::Err(nom::Err::Failure(BgpParserError::Eyre(eyre!(
"Unknown Origin type {}",
byte
))));
}
};
Ok((buf, attr))
}
pub fn wire_len(&self, _ctx: &ParserContext) -> Result<u16> {
Ok(1)
}
pub fn to_wire(&self, _ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u8(*self as u8);
Ok(())
}
}
impl TryFrom<u8> for OriginPathAttribute { impl TryFrom<u8> for OriginPathAttribute {
type Error = eyre::Error; type Error = eyre::Error;
@ -184,12 +440,12 @@ impl TryFrom<u8> for OriginPathAttribute {
/// segments. Type is either 1 for AS_SET or 2 for AS_SEQUENCE, length is a 1 octet field /// segments. Type is either 1 for AS_SET or 2 for AS_SEQUENCE, length is a 1 octet field
/// containing the number of ASNS and the value contains the ASNs. This is defined in Section 4.3 /// containing the number of ASNS and the value contains the ASNs. This is defined in Section 4.3
/// of RFC4271. /// of RFC4271.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct AsPathAttribute { pub struct AsPathAttribute {
pub segments: Vec<AsPathSegment>, pub segments: Vec<AsPathSegment>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Serialize)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct AsPathSegment { pub struct AsPathSegment {
/// ordered is true when representing an AS_SEQUENCE, andd false when /// ordered is true when representing an AS_SEQUENCE, andd false when
/// representing an AS_SET. /// representing an AS_SET.
@ -204,7 +460,7 @@ impl AsPathAttribute {
ordered: true, ordered: true,
path: asns, path: asns,
}; };
PathAttribute::ASPath(AsPathAttribute { PathAttribute::AsPath(AsPathAttribute {
segments: vec![segment], segments: vec![segment],
}) })
} }
@ -256,7 +512,7 @@ impl AsPathAttribute {
// Segment type. // Segment type.
out.put_u8(if segment.ordered { 2 } else { 1 }); out.put_u8(if segment.ordered { 2 } else { 1 });
// Segment AS length. // Segment AS length.
out.put_u16( out.put_u8(
segment segment
.path .path
.len() .len()
@ -264,8 +520,23 @@ impl AsPathAttribute {
.wrap_err("AS Path length too long")?, .wrap_err("AS Path length too long")?,
); );
// AS numbers. // AS numbers.
for asn in &segment.path { match ctx
out.put_u32(*asn); .four_octet_asn
.ok_or(eyre!("ctx.four_octet_asn must be set"))?
{
true => {
for asn in &segment.path {
out.put_u32(*asn);
}
}
false => {
for asn in &segment.path {
out.put_u16(
u16::try_from(*asn)
.map_err(|e| eyre!("AS number did not fit into u16: {}", e))?,
);
}
}
} }
} }
@ -280,7 +551,6 @@ impl AsPathAttribute {
Some(false) => 2 + (2 * segment.path.len()), Some(false) => 2 + (2 * segment.path.len()),
None => bail!("ParserContext needs four_octet_asn set"), None => bail!("ParserContext needs four_octet_asn set"),
}; };
counter += 2 + (4 * segment.path.len());
} }
Ok(counter as u16) Ok(counter as u16)
} }
@ -298,7 +568,7 @@ impl NextHopPathAttribute {
Ok((buf, Self(Ipv4Addr::from(ip_u32)))) Ok((buf, Self(Ipv4Addr::from(ip_u32))))
} }
pub fn to_wire(&self, out: &mut BytesMut) -> Result<()> { pub fn to_wire(&self, _ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u32(self.0.into()); out.put_u32(self.0.into());
Ok(()) Ok(())
} }
@ -444,6 +714,37 @@ pub struct ExtendedCommunitiesPathAttribute {
pub extended_communities: Vec<ExtendedCommunity>, pub extended_communities: Vec<ExtendedCommunity>,
} }
impl ExtendedCommunitiesPathAttribute {
pub fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, extended_communities) =
nom::multi::many1(|buf| ExtendedCommunity::from_wire(ctx, buf)).parse(buf)?;
Ok((
buf,
Self {
extended_communities,
},
))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
for ec in &self.extended_communities {
ec.to_wire(ctx, out)?;
}
Ok(())
}
pub fn wire_len(&self, ctx: &ParserContext) -> Result<u16> {
Ok(self
.extended_communities
.iter()
.map(|ec| ec.wire_len(ctx))
.sum::<Result<u16>>()?)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum ExtendedCommunity { pub enum ExtendedCommunity {
/// AS Specific Extended Community as specified in Section 3.1 of RFC4360. /// AS Specific Extended Community as specified in Section 3.1 of RFC4360.
@ -631,7 +932,7 @@ impl ExtendedCommunity {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LargeCommunitiesPathAttribute { pub struct LargeCommunitiesPathAttribute {
pub communities: Vec<LargeCommunity>, pub communities: Vec<LargeCommunity>,
} }
@ -658,7 +959,7 @@ impl LargeCommunitiesPathAttribute {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LargeCommunity { pub struct LargeCommunity {
pub global_admin: u32, pub global_admin: u32,
pub data_1: u32, pub data_1: u32,
@ -695,7 +996,7 @@ impl LargeCommunity {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NlriNextHop { pub enum NlriNextHop {
/// Represents an IPv4 address nexthop. /// Represents an IPv4 address nexthop.
Ipv4(Ipv4Addr), Ipv4(Ipv4Addr),
@ -739,12 +1040,12 @@ fn parse_prefix<'a>(
let (buf, prefix_len) = be_u8(buf)?; let (buf, prefix_len) = be_u8(buf)?;
let byte_len = (prefix_len + 7) / 8; let byte_len = (prefix_len + 7) / 8;
let (buf, prefix_bytes) = nom::bytes::take(byte_len as usize).parse(buf)?; let (buf, prefix_bytes) = nom::bytes::take(byte_len as usize).parse(buf)?;
let prefix = IpPrefix::new(afi, prefix_bytes.to_vec(), byte_len) let prefix = IpPrefix::new(afi, prefix_bytes.to_vec(), prefix_len)
.map_err(|e| Failure(BgpParserError::Eyre(e)))?; .map_err(|e| Failure(BgpParserError::Eyre(e)))?;
Ok((buf, prefix)) Ok((buf, prefix))
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MpReachNlriPathAttribute { pub struct MpReachNlriPathAttribute {
pub afi: AddressFamilyId, pub afi: AddressFamilyId,
pub safi: SubsequentAfi, pub safi: SubsequentAfi,
@ -797,10 +1098,10 @@ impl MpReachNlriPathAttribute {
32 => { 32 => {
// unwrap should never fire since we have explicitly checked the length. // unwrap should never fire since we have explicitly checked the length.
let slice: [u8; 32] = nh_bytes.try_into().unwrap(); let slice: [u8; 32] = nh_bytes.try_into().unwrap();
let link_local_bytes: [u8; 16] = slice[0..16].try_into().unwrap(); let global_bytes: [u8; 16] = slice[0..16].try_into().unwrap();
let link_local = Ipv6Addr::from(link_local_bytes);
let global_bytes: [u8; 16] = slice[16..32].try_into().unwrap();
let global = Ipv6Addr::from(global_bytes); let global = Ipv6Addr::from(global_bytes);
let link_local_bytes: [u8; 16] = slice[16..32].try_into().unwrap();
let link_local = Ipv6Addr::from(link_local_bytes);
NlriNextHop::Ipv6WithLl { global, link_local } NlriNextHop::Ipv6WithLl { global, link_local }
} }
_ => { _ => {
@ -810,6 +1111,8 @@ impl MpReachNlriPathAttribute {
)))); ))));
} }
}; };
// Reserved 0 byte (formerly SNPA).
let (buf, _) = be_u8(buf)?;
let (buf, prefixes) = let (buf, prefixes) =
nom::multi::many0(|buf| parse_prefix(AddressFamilyId::Ipv6, buf)).parse(buf)?; nom::multi::many0(|buf| parse_prefix(AddressFamilyId::Ipv6, buf)).parse(buf)?;
(buf, nexthop, prefixes) (buf, nexthop, prefixes)
@ -832,6 +1135,7 @@ impl MpReachNlriPathAttribute {
out.put_u8(self.safi as u8); out.put_u8(self.safi as u8);
out.put_u8(self.next_hop.wire_len()); out.put_u8(self.next_hop.wire_len());
self.next_hop.to_wire(ctx, out)?; self.next_hop.to_wire(ctx, out)?;
out.put_u8(0);
for prefix in &self.prefixes { for prefix in &self.prefixes {
out.put_u8(prefix.length); out.put_u8(prefix.length);
out.put(&prefix.prefix[..]); out.put(&prefix.prefix[..]);
@ -842,6 +1146,7 @@ impl MpReachNlriPathAttribute {
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> { pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(4_u16 Ok(4_u16
+ self.next_hop.wire_len() as u16 + self.next_hop.wire_len() as u16
+ 1 // Reserved byte (SNPA).
+ self + self
.prefixes .prefixes
.iter() .iter()
@ -850,7 +1155,7 @@ impl MpReachNlriPathAttribute {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MpUnreachNlriPathAttribute { pub struct MpUnreachNlriPathAttribute {
pub afi: AddressFamilyId, pub afi: AddressFamilyId,
pub safi: SubsequentAfi, pub safi: SubsequentAfi,
@ -903,3 +1208,249 @@ impl MpUnreachNlriPathAttribute {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationMessage {} pub struct NotificationMessage {}
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use bytes::BytesMut;
use eyre::{Result, eyre};
use crate::{
constants::{AddressFamilyId, SubsequentAfi},
ip_prefix::IpPrefix,
message::{
AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute,
CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity,
LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute,
MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute,
NextHopPathAttribute, NlriNextHop, OriginPathAttribute, PathAttribute,
},
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.
/// This uses `four_octet_asn` set to true and address_family to IPv6.
fn default_v6_context() -> ParserContext {
ParserContext {
four_octet_asn: Some(true),
address_family: Some(AddressFamilyId::Ipv6),
}
}
// 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,
&[0xc0, 0x06, 0x00],
PathAttribute::AtomicAggregate(AtomicAggregatePathAttribute {})
);
// Aggregator Path Attribute.
test_path_attribute_roundtrip!(
test_aggregator,
&[
0x80, 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::Communitites(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 {
afi: AddressFamilyId::Ipv6,
safi: SubsequentAfi::Unicast,
next_hop: NlriNextHop::Ipv6WithLl {
global: ipv6!("2001:db8::1"),
link_local: ipv6!("fe80::12"),
},
prefixes: vec![IpPrefix {
address_family: AddressFamilyId::Ipv6,
prefix: vec![0x20, 0x01, 0x0d, 0xb8],
length: 32,
}]
})
);
// 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 {
afi: AddressFamilyId::Ipv6,
safi: SubsequentAfi::Unicast,
prefixes: vec![IpPrefix {
address_family: AddressFamilyId::Ipv6,
prefix: vec![0x20, 0x01, 0x0d, 0xb8],
length: 32,
}],
})
);
// 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::RouteOrigin {
typ: 0,
sub_typ: 3,
global_admin: 65000,
local_admin: 1337
},
ExtendedCommunity::RouteOrigin {
typ: 0,
sub_typ: 3,
global_admin: 65001,
local_admin: 1337
},
ExtendedCommunity::RouteOrigin {
typ: 2,
sub_typ: 3,
global_admin: 65000,
local_admin: 1337
},
]
})
);
// 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,
}]
})
);
}