Files
bgp/bgpd/src/bgp_packet/path_attributes.rs

1227 lines
45 KiB
Rust

// Copyright 2021 Rayhaan Jaufeerally.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::bgp_packet::constants::AddressFamilyIdentifier;
use crate::bgp_packet::constants::SubsequentAddressFamilyIdentifier;
use crate::bgp_packet::nlri::NLRI;
use crate::bgp_packet::traits::BGPParserError;
use crate::bgp_packet::traits::ParserContext;
use crate::bgp_packet::traits::ReadablePacket;
use crate::bgp_packet::traits::WritablePacket;
use byteorder::ByteOrder;
use byteorder::NetworkEndian;
use nom::number::complete::{be_u16, be_u32, be_u8};
use nom::Err::Failure;
use nom::IResult;
use serde::Serialize;
use std::convert::TryInto;
use std::fmt;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
/// PathAttribute represents path attributes in a BGP Update message.
#[derive(Debug, PartialEq, Clone, Serialize)]
pub enum PathAttribute {
OriginPathAttribute(OriginPathAttribute),
ASPathAttribute(ASPathAttribute),
NextHopPathAttribute(NextHopPathAttribute),
MultiExitDiscPathAttribute(MultiExitDiscPathAttribute),
LocalPrefPathAttribute(LocalPrefPathAttribute),
AtomicAggregatePathAttribute(AtomicAggregatePathAttribute),
AggregatorPathAttribute(AggregatorPathAttribute),
CommunitiesPathAttribute(CommunitiesPathAttribute),
ExtendedCommunitiesPathAttribute(ExtendedCommunitiesPathAttribute),
LargeCommunitiesPathAttribute(LargeCommunitiesPathAttribute),
MPReachNLRIPathAttribute(MPReachNLRIPathAttribute),
MPUnreachNLRIPathAttribute(MPUnreachNLRIPathAttribute),
UnknownPathAttribute(Vec<u8>),
}
const PATH_ATTRIBUTE_FLAG_OPTONAL: u8 = 0x80; // when set to 1: optional, well-known: 0.
const PATH_ATTRIBUTE_FLAG_TRANSITIVE: u8 = 0x40; // when set to 1: transitive, non-transitive: 0.
const _PATH_ATTRIBUTE_FLAG_PARTIAL: u8 = 0x20; // when set to 1: partial, complete: 0.
const PATH_ATTRIBUTE_EXTENDED_LENGTH: u8 = 0x10; // when set to 1: length is u16, otherwise when 0 length is u8.
// For well known attributes the transitive bit MUST be set to 1.
// Write the type, length and call the child serializer
impl WritablePacket for PathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(match self {
PathAttribute::OriginPathAttribute(a) => {
let typ: u8 = 1;
let flag: u8 = PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::ASPathAttribute(a) => {
let typ: u8 = 2;
let flag: u8 = PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::NextHopPathAttribute(a) => {
let typ: u8 = 3;
let flag: u8 = PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::MultiExitDiscPathAttribute(a) => {
let typ: u8 = 4;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::LocalPrefPathAttribute(a) => {
let typ: u8 = 5;
let flag: u8 = 0;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::AtomicAggregatePathAttribute(a) => {
let typ: u8 = 6;
let flag: u8 = 0;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::AggregatorPathAttribute(a) => {
let typ: u8 = 7;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL | PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::CommunitiesPathAttribute(a) => {
let typ: u8 = 8;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL | PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::MPReachNLRIPathAttribute(a) => {
let typ: u8 = 14;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::MPUnreachNLRIPathAttribute(a) => {
let typ: u8 = 15;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::ExtendedCommunitiesPathAttribute(a) => {
let typ: u8 = 16;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL | PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::LargeCommunitiesPathAttribute(a) => {
let typ: u8 = 32;
let flag: u8 = PATH_ATTRIBUTE_FLAG_OPTONAL | PATH_ATTRIBUTE_FLAG_TRANSITIVE;
let len: u8 = a.wire_len(ctx)? as u8;
[vec![flag, typ, len], a.to_wire(ctx)?].concat()
}
PathAttribute::UnknownPathAttribute(u) => u.to_vec(),
})
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
Ok(match self {
PathAttribute::OriginPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::ASPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::NextHopPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::MultiExitDiscPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::LocalPrefPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::AtomicAggregatePathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::AggregatorPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::CommunitiesPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::MPReachNLRIPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::MPUnreachNLRIPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::ExtendedCommunitiesPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::LargeCommunitiesPathAttribute(a) => 3 + a.wire_len(ctx)?,
PathAttribute::UnknownPathAttribute(u) => u.len() as u16,
})
}
}
// Read the type, length and dispatch accordingly.
impl ReadablePacket for PathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, flag) = be_u8(buf)?;
let (buf, typ) = be_u8(buf)?;
let mut len8 = 0; // to preserve the 1 octet length for the unknown option.
let (buf, len) = match flag & PATH_ATTRIBUTE_EXTENDED_LENGTH {
PATH_ATTRIBUTE_EXTENDED_LENGTH => be_u16(buf)?,
_ => {
let (b, t) = be_u8(buf)?;
len8 = t;
(b, t as u16)
}
};
// Explicitly read the attribute here and pass the attribute only buffer to the child parser.
let (buf, pa_buf) = nom::bytes::complete::take(len)(buf)?;
let (_, res): (_, PathAttribute) = match typ {
1 => {
let (b, r) = OriginPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::OriginPathAttribute(r))
}
2 => {
let (b, r) = ASPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::ASPathAttribute(r))
}
3 => {
let (b, r) = NextHopPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::NextHopPathAttribute(r))
}
4 => {
let (b, r) = MultiExitDiscPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::MultiExitDiscPathAttribute(r))
}
5 => {
let (b, r) = LocalPrefPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::LocalPrefPathAttribute(r))
}
6 => {
let (b, r) = AtomicAggregatePathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::AtomicAggregatePathAttribute(r))
}
7 => {
let (b, r) = AggregatorPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::AggregatorPathAttribute(r))
}
8 => {
let (b, r) = CommunitiesPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::CommunitiesPathAttribute(r))
}
14 => {
let (b, r) = MPReachNLRIPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::MPReachNLRIPathAttribute(r))
}
15 => {
let (b, r) = MPUnreachNLRIPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::MPUnreachNLRIPathAttribute(r))
}
16 => {
let (b, r) = ExtendedCommunitiesPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::ExtendedCommunitiesPathAttribute(r))
}
32 => {
let (b, r) = LargeCommunitiesPathAttribute::from_wire(ctx, pa_buf)?;
(b, PathAttribute::LargeCommunitiesPathAttribute(r))
}
_ => {
let mut tmp = vec![flag, typ];
if len8 != 0 {
tmp.push(len8);
} else {
let mut t = [0u8; 2];
byteorder::NetworkEndian::write_u16(&mut t, len);
tmp.append(&mut t.to_vec());
}
tmp.extend(pa_buf.to_vec());
(&[], PathAttribute::UnknownPathAttribute(tmp))
}
};
Ok((buf, res))
}
}
impl fmt::Display for PathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathAttribute::OriginPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::ASPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::NextHopPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::MultiExitDiscPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::LocalPrefPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::AtomicAggregatePathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::AggregatorPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::CommunitiesPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::MPReachNLRIPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::MPUnreachNLRIPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::ExtendedCommunitiesPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::LargeCommunitiesPathAttribute(a) => std::fmt::Display::fmt(&a, f),
PathAttribute::UnknownPathAttribute(a) => {
write!(f, "unknown PathAttribute, bytes: {:?}", a)
}
}
}
}
// Path attribute implementations.
/// Origin path attribute is a mandatory attribute defined in RFC4271.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct OriginPathAttribute(pub u8);
pub mod origin_path_attribute_values {
use super::OriginPathAttribute;
pub const IGP: OriginPathAttribute = OriginPathAttribute(0);
pub const EGP: OriginPathAttribute = OriginPathAttribute(1);
pub const UNKNOWN: OriginPathAttribute = OriginPathAttribute(2);
}
impl ReadablePacket for OriginPathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, opa) = be_u8(buf)?;
Ok((buf, OriginPathAttribute(opa)))
}
}
impl WritablePacket for OriginPathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(vec![self.0])
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(1)
}
}
impl fmt::Display for OriginPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use origin_path_attribute_values::*;
match self {
&IGP => write!(f, "Origin: IGP"),
&EGP => write!(f, "Origin: EGP"),
&UNKNOWN => write!(f, "Origin: Unknown"),
_ => write!(f, "Origin: invalid value"),
}
}
}
/// ASPathAttribute is a well-known mandatory attribute that contains a list of TLV encoded path
/// 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)]
pub struct ASPathAttribute {
pub segments: Vec<ASPathSegment>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct ASPathSegment {
/// ordered is true when representing an AS_SEQUENCE, andd false when
/// representing an AS_SET.
pub ordered: bool,
/// Path is the list of ASNs.
pub path: Vec<u32>,
}
impl ASPathAttribute {
pub fn from_asns(asns: Vec<u32>) -> PathAttribute {
let segment = ASPathSegment {
ordered: true,
path: asns,
};
PathAttribute::ASPathAttribute(ASPathAttribute {
segments: vec![segment],
})
}
}
impl ReadablePacket for ASPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let parse_segment = |ctx: &ParserContext,
buf: &'a [u8]|
-> IResult<&'a [u8], ASPathSegment, BGPParserError<&'a [u8]>> {
let (buf, typ) = be_u8(buf)?;
let (buf, len) = be_u8(buf)?;
let (buf, asns): (_, Vec<u32>) = match ctx.four_octet_asn {
Some(true) => nom::multi::many_m_n(len as usize, len as usize, be_u32)(buf)?,
Some(false) => {
let (buf, asn_u16) =
nom::multi::many_m_n(len as usize, len as usize, be_u16)(buf)?;
let mut asn_u32: Vec<u32> = Vec::new();
for asn in asn_u16 {
asn_u32.push(asn as u32);
}
(buf, asn_u32)
}
None => {
return Err(Failure(BGPParserError::CustomText(
"Can't parse ASPath without four_octet_asn being set".to_owned(),
)));
}
};
Ok((
buf,
ASPathSegment {
ordered: (typ == 2),
path: asns,
},
))
};
let (buf, segments): (_, Vec<ASPathSegment>) =
nom::multi::many0(|buf: &'a [u8]| parse_segment(ctx, buf))(buf)?;
Ok((buf, ASPathAttribute { segments }))
}
}
impl WritablePacket for ASPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
if !ctx.four_octet_asn.unwrap_or(false) {
return Err(
"Can't use ASPathAttribute to communicate with legacy peer, use AS4PathAttribute",
);
}
let mut wire: Vec<u8> = Vec::new();
for segment in &self.segments {
wire.push(if segment.ordered { 2 } else { 1 });
wire.push(
segment
.path
.len()
.try_into()
.map_err(|_| "ASPath segment too long")?,
);
for asn in &segment.path {
let mut tmp: Vec<u8> = vec![0u8; 4];
NetworkEndian::write_u32(&mut tmp, *asn);
wire.append(&mut tmp);
}
}
Ok(wire)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
let mut ctr: u16 = 0;
for segment in &self.segments {
ctr += 2;
ctr += (4 * segment.path.len()) as u16;
}
Ok(ctr)
}
}
impl fmt::Display for ASPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AS Path: {{ ")?;
for segment in &self.segments {
write!(f, "Segment [ ")?;
if segment.ordered {
write!(f, "Type: AS_SEGMENT ")?
} else {
write!(f, "Type: AS_SET ")?
};
for asn in &segment.path {
write!(f, "{} ", asn)?;
}
write!(f, " ]")?;
}
write!(f, "] }}")
}
}
// TODO: AS4 path attribute
// Per RFC 6793 the AS4 path attribute is for legacy BGP speakers to propagate
// 4 octet ASNs in update messages.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct AS4PathAttribute {
pub ordered: bool,
pub path: Vec<u32>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct NextHopPathAttribute(pub Ipv4Addr);
impl ReadablePacket for NextHopPathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (_, ip_u32) = be_u32(buf)?;
let nexthop = Ipv4Addr::from(ip_u32);
Ok((buf, NextHopPathAttribute(nexthop)))
}
}
impl WritablePacket for NextHopPathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
return Ok(self.0.octets().to_vec());
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
return Ok(4);
}
}
impl fmt::Display for NextHopPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NextHop: {}", self.0)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct MultiExitDiscPathAttribute(pub u32);
impl ReadablePacket for MultiExitDiscPathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, val) = be_u32(buf)?;
Ok((buf, MultiExitDiscPathAttribute(val)))
}
}
impl WritablePacket for MultiExitDiscPathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![0u8; 4];
byteorder::NetworkEndian::write_u32(&mut buf, self.0);
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl fmt::Display for MultiExitDiscPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MultiExitDisc: {}", self.0)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct LocalPrefPathAttribute(pub u32);
impl ReadablePacket for LocalPrefPathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, val) = be_u32(buf)?;
Ok((buf, LocalPrefPathAttribute(val)))
}
}
impl WritablePacket for LocalPrefPathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf: Vec<u8> = vec![0u8; 4];
byteorder::NetworkEndian::write_u32(&mut buf, self.0);
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl fmt::Display for LocalPrefPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LocalPref: {}", self.0)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct AtomicAggregatePathAttribute {}
impl ReadablePacket for AtomicAggregatePathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
Ok((buf, AtomicAggregatePathAttribute {}))
}
}
impl WritablePacket for AtomicAggregatePathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
Ok(vec![])
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(0)
}
}
impl fmt::Display for AtomicAggregatePathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AtomicAggregate: present")
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct AggregatorPathAttribute {
pub asn: u32,
pub ip: Ipv4Addr,
}
// TODO: Support non AS4 peers.
impl ReadablePacket for AggregatorPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
if !ctx.four_octet_asn.is_some() {
return Err(Failure(BGPParserError::CustomText(
"Non four byte ASN not supported (AggregatorPathAttribute from_wire)".to_string(),
)));
}
let (buf, asn) = be_u32(buf)?;
let (buf, ip) = nom::bytes::complete::take(4u8)(buf)?;
let correct: [u8; 4] = ip.try_into().expect("wrong slice len");
Ok((
buf,
AggregatorPathAttribute {
asn,
ip: Ipv4Addr::from(correct),
},
))
}
}
impl WritablePacket for AggregatorPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
if !ctx.four_octet_asn.is_some() {
panic!("Non four byte ASN not supported (AggregatorPathAttribute from_wire)");
}
let mut buf: Vec<u8> = vec![0u8; 4];
byteorder::NetworkEndian::write_u32(&mut buf, self.asn);
buf.extend(self.ip.octets().to_vec());
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
if !ctx.four_octet_asn.is_some() {
panic!("Non four byte ASN not supported (AggregatorPathAttribute from_wire)");
}
Ok(8)
}
}
impl fmt::Display for AggregatorPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Aggregator: asn: {}, ip: {}", self.asn, self.ip)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct CommunitiesPathAttribute {
pub values: Vec<CommunitiesPayload>,
}
impl ReadablePacket for CommunitiesPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, values): (_, Vec<CommunitiesPayload>) =
nom::multi::many0(|i| CommunitiesPayload::from_wire(ctx, i))(buf)?;
Ok((buf, CommunitiesPathAttribute { values }))
}
}
impl WritablePacket for CommunitiesPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![];
for val in &self.values {
buf.extend(val.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ttl: u16 = 0;
for val in &self.values {
ttl += val.wire_len(ctx)?;
}
Ok(ttl)
}
}
impl fmt::Display for CommunitiesPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Communities: [ ")?;
for c in &self.values {
write!(f, " {}, ", c)?;
}
write!(f, " ] ")
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct CommunitiesPayload {
pub asn: u16,
pub payload: u16,
}
impl ReadablePacket for CommunitiesPayload {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, asn): (_, u16) = be_u16(buf)?;
let (buf, payload): (_, u16) = be_u16(buf)?;
Ok((buf, CommunitiesPayload { asn, payload }))
}
}
impl WritablePacket for CommunitiesPayload {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![0u8; 4];
byteorder::NetworkEndian::write_u16(&mut buf[0..2], self.asn);
byteorder::NetworkEndian::write_u16(&mut buf[2..4], self.payload);
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(4)
}
}
impl fmt::Display for CommunitiesPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.asn, self.payload)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct ExtendedCommunitiesPathAttribute {
pub t_high: u8,
// TODO: Handle t_low and subtypes of the Extended Communities attribute as defined in rfc4360.
pub value: Vec<u8>,
}
impl ReadablePacket for ExtendedCommunitiesPathAttribute {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, t_high) = be_u8(buf)?;
let (buf, value) = nom::bytes::complete::take(7u8)(buf)?;
Ok((
buf,
ExtendedCommunitiesPathAttribute {
t_high,
value: value.to_vec(),
},
))
}
}
impl WritablePacket for ExtendedCommunitiesPathAttribute {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
if !self.value.len() == 7 {
return Err("ExtendedCommunitiesPathAttribute value length != 7");
}
Ok(vec![vec![self.t_high], self.value.to_owned()].concat())
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(8)
}
}
impl fmt::Display for ExtendedCommunitiesPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtendedCommunities: {} {:?}", self.t_high, self.value)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct LargeCommunitiesPathAttribute {
pub values: Vec<LargeCommunitiesPayload>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct LargeCommunitiesPayload {
pub global_admin: u32,
pub ld1: u32,
pub ld2: u32,
}
impl ReadablePacket for LargeCommunitiesPayload {
fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, global_admin) = be_u32(buf)?;
let (buf, ld1) = be_u32(buf)?;
let (buf, ld2) = be_u32(buf)?;
Ok((
buf,
LargeCommunitiesPayload {
global_admin,
ld1,
ld2,
},
))
}
}
impl WritablePacket for LargeCommunitiesPayload {
fn to_wire(&self, _: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![0u8; 12];
byteorder::NetworkEndian::write_u32(&mut buf[0..4], self.global_admin);
byteorder::NetworkEndian::write_u32(&mut buf[4..8], self.ld1);
byteorder::NetworkEndian::write_u32(&mut buf[8..12], self.ld2);
Ok(buf)
}
fn wire_len(&self, _: &ParserContext) -> Result<u16, &'static str> {
Ok(12)
}
}
impl fmt::Display for LargeCommunitiesPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.global_admin, self.ld1, self.ld2)
}
}
impl ReadablePacket for LargeCommunitiesPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, values): (_, Vec<LargeCommunitiesPayload>) =
nom::multi::many0(|i| LargeCommunitiesPayload::from_wire(ctx, i))(buf)?;
Ok((buf, LargeCommunitiesPathAttribute { values }))
}
}
impl WritablePacket for LargeCommunitiesPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![];
for val in &self.values {
buf.extend(val.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ttl: u16 = 0;
for val in &self.values {
ttl += val.wire_len(ctx)?;
}
Ok(ttl)
}
}
impl fmt::Display for LargeCommunitiesPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LargeCommunities: [")?;
for c in &self.values {
write!(f, " {}, ", c)?;
}
write!(f, "]")
}
}
/// MPReachPathAattribute implements the MultiProtocol extensions to BGP (RFC4760)
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct MPReachNLRIPathAttribute {
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
pub nexthop: Vec<u8>,
pub nlris: Vec<NLRI>,
}
impl MPReachNLRIPathAttribute {
// https://datatracker.ietf.org/doc/html/rfc2545 describes what the nexthop
// field can contain. Returns a tuple of (global_nh, linklocal_nh)
pub fn nexthop_to_v6(self) -> Option<(Ipv6Addr, Option<Ipv6Addr>)> {
return match self.nexthop.len() {
16 => {
let nh_bytes: [u8; 16] = self.nexthop.try_into().unwrap();
Some((Ipv6Addr::from(nh_bytes), None))
}
32 => {
let global_nh_bytes: [u8; 16] = self.nexthop[0..16].try_into().unwrap();
let llnh_bytes: [u8; 16] = self.nexthop[16..32].try_into().unwrap();
Some((
Ipv6Addr::from(global_nh_bytes),
Some(Ipv6Addr::from(llnh_bytes)),
))
}
_ => None,
};
}
}
impl ReadablePacket for MPReachNLRIPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, afi) = AddressFamilyIdentifier::from_wire(ctx, buf)?;
let (buf, safi) = SubsequentAddressFamilyIdentifier::from_wire(ctx, buf)?;
let (buf, nexthop): (_, Vec<u8>) =
nom::multi::length_value(be_u8, nom::multi::many0(be_u8))(buf)?;
// Reserved field set to 0.
let (buf, _) = be_u8(buf)?;
let (buf, nlris): (_, Vec<NLRI>) = nom::multi::many0(|i| NLRI::from_wire(ctx, i))(buf)?;
Ok((
buf,
MPReachNLRIPathAttribute {
afi,
safi,
nexthop,
nlris,
},
))
}
}
impl WritablePacket for MPReachNLRIPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![0u8; 4];
byteorder::NetworkEndian::write_u16(&mut buf[0..2], self.afi.into());
buf[2] = self.safi.into();
buf[3] = self.nexthop.len() as u8;
buf.extend(&self.nexthop);
// Reserved field set to 0.
buf.push(0);
for nlri in &self.nlris {
buf.extend(nlri.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ctr: u16 = 0;
ctr += 4; // afi + safi + the (len of nexthop) octet
ctr += self.nexthop.len() as u16;
ctr += 1; // Reserved octet.
for nlri in &self.nlris {
ctr += nlri.wire_len(ctx)?;
}
Ok(ctr)
}
}
impl fmt::Display for MPReachNLRIPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MPReachNLRI: afi: {} safi: {}, nexthop: {:?} nlris: [",
self.afi, self.safi, self.nexthop
)?;
for nlri in &self.nlris {
std::fmt::Display::fmt(nlri, f)?;
}
write!(f, "]")
}
}
/// MPUnreachNLRIPathAttribute represents a MultiProtocol prefix withdrawal.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct MPUnreachNLRIPathAttribute {
pub afi: AddressFamilyIdentifier,
pub safi: SubsequentAddressFamilyIdentifier,
pub nlris: Vec<NLRI>,
}
impl ReadablePacket for MPUnreachNLRIPathAttribute {
fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> {
let (buf, afi) = AddressFamilyIdentifier::from_wire(ctx, buf)?;
let (buf, safi) = SubsequentAddressFamilyIdentifier::from_wire(ctx, buf)?;
let (buf, nlris): (_, Vec<NLRI>) = nom::multi::many0(|i| NLRI::from_wire(ctx, i))(buf)?;
Ok((buf, MPUnreachNLRIPathAttribute { afi, safi, nlris }))
}
}
impl WritablePacket for MPUnreachNLRIPathAttribute {
fn to_wire(&self, ctx: &ParserContext) -> Result<Vec<u8>, &'static str> {
let mut buf = vec![0u8; 3];
NetworkEndian::write_u16(&mut buf[0..2], self.afi.into());
buf[2] = self.safi.into();
for nlri in &self.nlris {
buf.extend(nlri.to_wire(ctx)?);
}
Ok(buf)
}
fn wire_len(&self, ctx: &ParserContext) -> Result<u16, &'static str> {
let mut ctr: u16 = 0;
ctr += 3;
for nlri in &self.nlris {
ctr += nlri.wire_len(ctx)?;
}
Ok(ctr)
}
}
impl fmt::Display for MPUnreachNLRIPathAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MPUnreachNLRI: afi: {} safi: {}, nlris: [",
self.afi, self.safi
)?;
for nlri in &self.nlris {
std::fmt::Display::fmt(nlri, f)?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use crate::bgp_packet::constants::AddressFamilyIdentifier::Ipv6;
use crate::bgp_packet::constants::SubsequentAddressFamilyIdentifier::Unicast;
use crate::bgp_packet::traits::ParserContext;
use crate::bgp_packet::traits::ReadablePacket;
use crate::bgp_packet::traits::WritablePacket;
use super::ASPathAttribute;
use super::CommunitiesPathAttribute;
use super::LargeCommunitiesPathAttribute;
use super::LocalPrefPathAttribute;
use super::MPReachNLRIPathAttribute;
use super::MPUnreachNLRIPathAttribute;
use super::MultiExitDiscPathAttribute;
use super::NextHopPathAttribute;
use super::PathAttribute;
#[test]
fn test_as_path_segment() {
let as_path_bytes = &[
0x02, 0x04, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x20, 0x1a,
0x00, 0x00, 0x78, 0xfc,
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = &ASPathAttribute::from_wire(ctx, as_path_bytes).unwrap();
let expected_aspath: Vec<u32> = vec![39540, 57118, 8218, 30972];
assert_eq!(result.1.segments.len(), 1);
assert!(result.1.segments[0].ordered);
assert_eq!(result.1.segments[0].path, expected_aspath);
let wire = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, as_path_bytes);
}
#[test]
fn test_as_path_multi_segment() {
let as_path_bytes = &[
0x02, 0x04, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x20, 0x1a,
0x00, 0x00, 0x78, 0xfc, 0x01, 0x02, 0x00, 0x00, 0x9a, 0x74, 0x00, 0x00, 0xdf, 0x1e,
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = &ASPathAttribute::from_wire(ctx, as_path_bytes).unwrap();
let expected_aspath: Vec<u32> = vec![39540, 57118, 8218, 30972];
let expected_asset: Vec<u32> = vec![39540, 57118];
assert_eq!(result.1.segments.len(), 2);
assert!(result.1.segments[0].ordered);
assert_eq!(result.1.segments[0].path, expected_aspath);
assert!(!result.1.segments[1].ordered);
assert_eq!(result.1.segments[1].path, expected_asset);
let wire = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, as_path_bytes);
}
#[test]
fn test_next_hop_path_attribute() {
let nh_bytes: &[u8] = &[192, 168, 1, 1];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = NextHopPathAttribute::from_wire(ctx, nh_bytes).unwrap();
assert_eq!(result.1 .0, "192.168.1.1".parse::<Ipv4Addr>().unwrap());
let wire = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, nh_bytes);
assert_eq!(result.1.wire_len(ctx).unwrap(), wire.len() as u16);
}
#[test]
fn test_multi_exit_discriminator_path_attribute() {
let med_bytes: &[u8] = &[0xca, 0x00, 0x00, 0xbe];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = MultiExitDiscPathAttribute::from_wire(ctx, med_bytes).unwrap();
assert_eq!(result.1 .0, 3388997822);
let wire = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, med_bytes);
assert_eq!(result.1.wire_len(ctx).unwrap(), wire.len() as u16);
}
#[test]
fn test_local_pref_path_attribute() {
let local_pref_bytes: &[u8] = &[0xca, 0x00, 0x00, 0xbe];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = LocalPrefPathAttribute::from_wire(ctx, local_pref_bytes).unwrap();
assert_eq!(result.1 .0, 3388997822);
let wire = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, local_pref_bytes);
assert_eq!(result.1.wire_len(ctx).unwrap(), wire.len() as u16);
}
#[test]
fn test_communities_path_attribute() {
let communities_bytes: &[u8] = &[
0x00, 0x00, 0x32, 0xbd, 0x00, 0x00, 0x41, 0x5f, 0x32, 0xe6, 0x00, 0x01, 0x32, 0xe6,
0x10, 0x73, 0x32, 0xe6, 0xca, 0xbd, 0x57, 0x54, 0x0b, 0xb8, 0x57, 0x54, 0x0b, 0xb9,
0x57, 0x54, 0x2b, 0x5c, 0x57, 0x54, 0xff, 0xe6, 0x57, 0x54, 0xff, 0xf1, 0x6f, 0xf7,
0xff, 0xf1, 0x73, 0xfb, 0x0f, 0xa0, 0x73, 0xfb, 0x0f, 0xc8, 0x9a, 0x74, 0x0f, 0xa0,
0x9a, 0x74, 0x0f, 0xb4, 0xdf, 0x1e, 0x07, 0xd0, 0xdf, 0x1e, 0x07, 0xe4,
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = CommunitiesPathAttribute::from_wire(ctx, communities_bytes).unwrap();
let expected_communities: Vec<(u16, u16)> = vec![
(0, 0x32bd),
(0, 0x415f),
(13030, 1),
(13030, 4211),
(13030, 51901),
(22356, 3000),
(22356, 3001),
(22356, 11100),
(22356, 65510),
(22356, 65521),
(28663, 65521),
(29691, 4000),
(29691, 4040),
(39540, 4000),
(39540, 4020),
(57118, 2000),
(57118, 2020),
];
assert_eq!(result.1.values.len(), expected_communities.len());
for (i, community) in result.1.values.iter().enumerate() {
assert_eq!(community.asn, expected_communities[i].0);
assert_eq!(community.payload, expected_communities[i].1);
}
let wire: Vec<u8> = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, communities_bytes);
assert_eq!(wire.len() as u16, result.1.wire_len(ctx).unwrap());
}
#[test]
fn test_large_communities_path_attribute() {
let large_community_bytes: &[u8] = &[
0x00, 0x00, 0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xdf, 0x1e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result = LargeCommunitiesPathAttribute::from_wire(ctx, large_community_bytes).unwrap();
assert_eq!(result.1.values.len(), 2);
assert_eq!(result.1.values[0].global_admin, 57118);
assert_eq!(result.1.values[0].ld1, 20);
assert_eq!(result.1.values[0].ld2, 0);
assert_eq!(result.1.values[1].global_admin, 57118);
assert_eq!(result.1.values[1].ld1, 20);
assert_eq!(result.1.values[1].ld2, 20);
let wire: Vec<u8> = result.1.to_wire(ctx).unwrap();
assert_eq!(wire, large_community_bytes);
assert_eq!(wire.len() as u16, result.1.wire_len(ctx).unwrap());
}
#[test]
fn test_mp_reach_nlri_path_attribute() {
let mp_reach_bytes: &[u8] = &[
0x00, 0x02, // IPv6
0x01, // Unicast
0x10, // Length of IPv6 nexthop
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // nh addr part one
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // nh addr part two
0x00, // Reserved
0x20, 0x20, 0x01, 0x0d, 0xb8, // NLRI 1
0x10, 0xfe, 0x80, // NLRI 2
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result: (&[u8], MPReachNLRIPathAttribute) =
MPReachNLRIPathAttribute::from_wire(ctx, mp_reach_bytes).unwrap();
assert_eq!(result.1.afi, Ipv6);
assert_eq!(result.1.safi, Unicast);
assert_eq!(
result.1.nexthop,
vec![
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // nh addr part one
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // nh addr part two
]
);
assert_eq!(result.1.nlris.len(), 2);
assert_eq!(format!("{}", result.1.nlris[0]), "2001:db8::/32");
assert_eq!(format!("{}", result.1.nlris[1]), "fe80::/16");
assert_eq!(result.0.len(), 0);
let wire: Vec<u8> = result.1.to_wire(ctx).unwrap();
assert_eq!(wire.as_slice(), mp_reach_bytes);
assert_eq!(result.1.wire_len(ctx).unwrap() as usize, wire.len());
}
#[test]
fn test_mp_unreach_nlri_path_attribute() {
let mp_unreach_bytes: &[u8] = &[
0x00, 0x02, // IPv6
0x01, // Unicast
0x20, 0x20, 0x01, 0x0d, 0xb8, // NLRI 1
0x10, 0xfe, 0x80, // NLRI 2
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let result: (&[u8], MPUnreachNLRIPathAttribute) =
MPUnreachNLRIPathAttribute::from_wire(ctx, mp_unreach_bytes).unwrap();
assert_eq!(result.1.afi, Ipv6);
assert_eq!(result.1.safi, Unicast);
assert_eq!(result.1.nlris.len(), 2);
assert_eq!(format!("{}", result.1.nlris[0]), "2001:db8::/32");
assert_eq!(format!("{}", result.1.nlris[1]), "fe80::/16");
assert_eq!(result.0.len(), 0);
let wire: Vec<u8> = result.1.to_wire(ctx).unwrap();
assert_eq!(wire.as_slice(), mp_unreach_bytes);
assert_eq!(result.1.wire_len(ctx).unwrap() as usize, wire.len());
}
// Tests the high level dispatching of the path attribute parser
#[test]
fn test_path_attribute_parsing<'a>() {
let path_attr_bytes: &[u8] = &[
0x40, 0x01, 0x01, 0x00, 0x50, 0x02, 0x00, 0x1a, 0x02, 0x06, 0x00, 0x00, 0x9a, 0x74,
0x00, 0x00, 0x62, 0x03, 0x00, 0x00, 0x0b, 0x62, 0x00, 0x00, 0x19, 0x35, 0x00, 0x00,
0x20, 0x9a, 0x00, 0x00, 0x34, 0x17, 0x40, 0x03, 0x04, 0xb9, 0x5f, 0xdb, 0x24, 0xc0,
0x08, 0x2c, 0x0b, 0x62, 0x01, 0xa4, 0x0b, 0x62, 0x04, 0xbf, 0x0b, 0x62, 0x08, 0xa6,
0x0b, 0x62, 0x0c, 0x80, 0x19, 0x35, 0x07, 0xd0, 0x19, 0x35, 0x09, 0xc4, 0x19, 0x35,
0x09, 0xcf, 0x62, 0x03, 0x0b, 0x62, 0x62, 0x03, 0x2f, 0x69, 0x9a, 0x74, 0x0f, 0xa0,
0x9a, 0x74, 0x0f, 0xbe,
];
let ctx = &ParserContext::new().four_octet_asn(true).nlri_mode(Ipv6);
let (buf, res): (_, Vec<PathAttribute>) =
nom::multi::many0(|buf: &'a [u8]| PathAttribute::from_wire(ctx, buf))(path_attr_bytes)
.unwrap();
assert_eq!(buf.len(), 0);
let expected_str = "[OriginPathAttribute(OriginPathAttribute(0)), \
ASPathAttribute(ASPathAttribute { segments: \
[ASPathSegment { ordered: true, path: [39540, 25091, 2914, 6453, 8346, 13335] }] }), \
NextHopPathAttribute(NextHopPathAttribute(185.95.219.36)), \
CommunitiesPathAttribute(CommunitiesPathAttribute { values: \
[CommunitiesPayload { asn: 2914, payload: 420 }, \
CommunitiesPayload { asn: 2914, payload: 1215 }, \
CommunitiesPayload { asn: 2914, payload: 2214 }, \
CommunitiesPayload { asn: 2914, payload: 3200 }, \
CommunitiesPayload { asn: 6453, payload: 2000 }, \
CommunitiesPayload { asn: 6453, payload: 2500 }, \
CommunitiesPayload { asn: 6453, payload: 2511 }, \
CommunitiesPayload { asn: 25091, payload: 2914 }, \
CommunitiesPayload { asn: 25091, payload: 12137 }, \
CommunitiesPayload { asn: 39540, payload: 4000 }, \
CommunitiesPayload { asn: 39540, payload: 4030 }] })]";
assert_eq!(format!("{:?}", res), expected_str);
}
}