feature: Implement more path attributes
This commit is contained in:
@ -16,3 +16,4 @@ nom.workspace = true
|
||||
serde.workspace = true
|
||||
serde_repr.workspace = true
|
||||
thiserror.workspace = true
|
||||
strum.workspace = true
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::ops::Deref;
|
||||
|
||||
use bitfield::bitfield;
|
||||
use bytes::BufMut;
|
||||
@ -15,6 +16,7 @@ use nom::number::complete::be_u16;
|
||||
use nom::number::complete::be_u32;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use strum::EnumDiscriminants;
|
||||
|
||||
use crate::constants::AddressFamilyId;
|
||||
use crate::constants::SubsequentAfi;
|
||||
@ -108,19 +110,29 @@ pub enum Capability {
|
||||
pub struct UpdateMessage {}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PathAttributeFlags(u8);
|
||||
impl Debug;
|
||||
impl new;
|
||||
u8;
|
||||
optional, set_optional: 0;
|
||||
transitive, set_transitive: 1;
|
||||
partial, set_partial: 2;
|
||||
extended_length, set_extended_length: 3;
|
||||
optional, set_optional: 7;
|
||||
transitive, set_transitive: 6;
|
||||
partial, set_partial: 5;
|
||||
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)]
|
||||
pub enum PathAttribute {
|
||||
Origin(OriginPathAttribute) = 1,
|
||||
ASPath(AsPathAttribute) = 2,
|
||||
AsPath(AsPathAttribute) = 2,
|
||||
NextHop(NextHopPathAttribute) = 3,
|
||||
MultiExitDisc(MultiExitDiscPathAttribute) = 4,
|
||||
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 {
|
||||
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
|
||||
/// determine how many bytes to take and pass down to the corresponding sub-parser.
|
||||
pub fn from_wire<'a>(
|
||||
@ -154,7 +244,143 @@ impl PathAttribute {
|
||||
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,
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
/// containing the number of ASNS and the value contains the ASNs. This is defined in Section 4.3
|
||||
/// of RFC4271.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct AsPathAttribute {
|
||||
pub segments: Vec<AsPathSegment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct AsPathSegment {
|
||||
/// ordered is true when representing an AS_SEQUENCE, andd false when
|
||||
/// representing an AS_SET.
|
||||
@ -204,7 +460,7 @@ impl AsPathAttribute {
|
||||
ordered: true,
|
||||
path: asns,
|
||||
};
|
||||
PathAttribute::ASPath(AsPathAttribute {
|
||||
PathAttribute::AsPath(AsPathAttribute {
|
||||
segments: vec![segment],
|
||||
})
|
||||
}
|
||||
@ -256,7 +512,7 @@ impl AsPathAttribute {
|
||||
// Segment type.
|
||||
out.put_u8(if segment.ordered { 2 } else { 1 });
|
||||
// Segment AS length.
|
||||
out.put_u16(
|
||||
out.put_u8(
|
||||
segment
|
||||
.path
|
||||
.len()
|
||||
@ -264,8 +520,23 @@ impl AsPathAttribute {
|
||||
.wrap_err("AS Path length too long")?,
|
||||
);
|
||||
// AS numbers.
|
||||
for asn in &segment.path {
|
||||
out.put_u32(*asn);
|
||||
match ctx
|
||||
.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()),
|
||||
None => bail!("ParserContext needs four_octet_asn set"),
|
||||
};
|
||||
counter += 2 + (4 * segment.path.len());
|
||||
}
|
||||
Ok(counter as u16)
|
||||
}
|
||||
@ -298,7 +568,7 @@ impl NextHopPathAttribute {
|
||||
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());
|
||||
Ok(())
|
||||
}
|
||||
@ -444,6 +714,37 @@ pub struct ExtendedCommunitiesPathAttribute {
|
||||
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)]
|
||||
pub enum ExtendedCommunity {
|
||||
/// 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 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 global_admin: 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 {
|
||||
/// Represents an IPv4 address nexthop.
|
||||
Ipv4(Ipv4Addr),
|
||||
@ -739,12 +1040,12 @@ fn parse_prefix<'a>(
|
||||
let (buf, prefix_len) = be_u8(buf)?;
|
||||
let byte_len = (prefix_len + 7) / 8;
|
||||
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)))?;
|
||||
Ok((buf, prefix))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct MpReachNlriPathAttribute {
|
||||
pub afi: AddressFamilyId,
|
||||
pub safi: SubsequentAfi,
|
||||
@ -797,10 +1098,10 @@ impl MpReachNlriPathAttribute {
|
||||
32 => {
|
||||
// unwrap should never fire since we have explicitly checked the length.
|
||||
let slice: [u8; 32] = nh_bytes.try_into().unwrap();
|
||||
let link_local_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_bytes: [u8; 16] = slice[0..16].try_into().unwrap();
|
||||
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 }
|
||||
}
|
||||
_ => {
|
||||
@ -810,6 +1111,8 @@ impl MpReachNlriPathAttribute {
|
||||
))));
|
||||
}
|
||||
};
|
||||
// Reserved 0 byte (formerly SNPA).
|
||||
let (buf, _) = be_u8(buf)?;
|
||||
let (buf, prefixes) =
|
||||
nom::multi::many0(|buf| parse_prefix(AddressFamilyId::Ipv6, buf)).parse(buf)?;
|
||||
(buf, nexthop, prefixes)
|
||||
@ -832,6 +1135,7 @@ impl MpReachNlriPathAttribute {
|
||||
out.put_u8(self.safi as u8);
|
||||
out.put_u8(self.next_hop.wire_len());
|
||||
self.next_hop.to_wire(ctx, out)?;
|
||||
out.put_u8(0);
|
||||
for prefix in &self.prefixes {
|
||||
out.put_u8(prefix.length);
|
||||
out.put(&prefix.prefix[..]);
|
||||
@ -842,6 +1146,7 @@ impl MpReachNlriPathAttribute {
|
||||
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
|
||||
Ok(4_u16
|
||||
+ self.next_hop.wire_len() as u16
|
||||
+ 1 // Reserved byte (SNPA).
|
||||
+ self
|
||||
.prefixes
|
||||
.iter()
|
||||
@ -850,7 +1155,7 @@ impl MpReachNlriPathAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct MpUnreachNlriPathAttribute {
|
||||
pub afi: AddressFamilyId,
|
||||
pub safi: SubsequentAfi,
|
||||
@ -903,3 +1208,249 @@ impl MpUnreachNlriPathAttribute {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
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,
|
||||
}]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user