Compare commits
5 Commits
1d3c6aa977
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dfe066c9ab | |||
| 66ce8665a6 | |||
| b5b7b8b162 | |||
| a9c14f341b | |||
| ab20160014 |
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -40,7 +40,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bgp"
|
name = "bgp"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -48,7 +48,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bgp-packet"
|
name = "bgp-packet"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield",
|
"bitfield",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -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"
|
||||||
|
|||||||
@ -10,8 +10,8 @@ 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://git.rayhaan.net/rayhaan/bgp"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# --- Our crates ---
|
# --- Our crates ---
|
||||||
@ -27,3 +27,4 @@ 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"] }
|
||||||
|
|||||||
19
README.md
19
README.md
@ -1,6 +1,6 @@
|
|||||||
# BGP
|
# BGP
|
||||||
|
|
||||||
This project is an implementation of the Border Gateway Protocol and associated functionality required to run an Autonomous System.
|
The `bgp` crate is an implementation of the Border Gateway Protocol, which is used to exchange routing and control plane information between Autonomous Systms.
|
||||||
|
|
||||||
The aim of this project is to provide a modern, high performance, secure and programatic and customizable software package that can be useful to network operators, researchers, and people interested in Internet.
|
The aim of this project is to provide a modern, high performance, secure and programatic and customizable software package that can be useful to network operators, researchers, and people interested in Internet.
|
||||||
|
|
||||||
@ -8,4 +8,19 @@ The aim of this project is to provide a modern, high performance, secure and pro
|
|||||||
|
|
||||||
The project is currently being rewritten from scratch to be more modular and maintainable, as well as to fix deeper issues with the previous implementation.
|
The project is currently being rewritten from scratch to be more modular and maintainable, as well as to fix deeper issues with the previous implementation.
|
||||||
|
|
||||||
Warning: This is currently a work in progress and not ready for use.
|
⚠️ Warning: This is currently a work in progress and not ready for use.
|
||||||
|
|
||||||
|
### 🛣️ Roadmap to v1.0
|
||||||
|
|
||||||
|
We aim for the 1.0 release to be functional and stable to operate peering sessions, and to install routes in the Linux Kernel.
|
||||||
|
|
||||||
|
* ☐ Implement basic parsers for the fundamental types required
|
||||||
|
* ✅ Path Attributes from RFC4271
|
||||||
|
* ✅ Open message
|
||||||
|
* ✅ Capabilities from RFC4271
|
||||||
|
* ✅ Update message
|
||||||
|
* ☐ Notification message
|
||||||
|
* ☐ Implement connection logic to establish sessions with peers
|
||||||
|
* ☐ Implement an efficient data structure for storing prefixes (e.g. Poptrie)
|
||||||
|
* ☐ Filtering for peer RIB in / out
|
||||||
|
* ☐ Main / auxillary RIBs for exchanging routes between peers
|
||||||
|
|||||||
9
bin/bgp/README.md
Normal file
9
bin/bgp/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
The main ongoing work is happening currently in the BGP packet crate.
|
||||||
@ -2,5 +2,5 @@ use eyre::Result;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() -> Result<()> {
|
pub async fn main() -> Result<()> {
|
||||||
Ok(())
|
todo!("Implement the server");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
7
crates/packet/README.md
Normal file
7
crates/packet/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# BGP Packet
|
||||||
|
|
||||||
|
This crate implements parsers and serialisers for the Border Gateway Protocol wire format.
|
||||||
|
|
||||||
|
## ⚠️ Current status
|
||||||
|
|
||||||
|
This crate is still under development and is not yet ready for use.
|
||||||
@ -4,7 +4,7 @@ use eyre::bail;
|
|||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||||
#[repr(u8)]
|
#[repr(u16)]
|
||||||
pub enum AddressFamilyId {
|
pub enum AddressFamilyId {
|
||||||
Ipv4 = 1,
|
Ipv4 = 1,
|
||||||
Ipv6 = 2,
|
Ipv6 = 2,
|
||||||
@ -57,6 +57,16 @@ impl TryFrom<u8> for SubsequentAfi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The implementation try_from for u16 just reuses the u8 implementation since
|
||||||
|
/// we don't have any cases where the SAFI is longer than u8::MAX yet.
|
||||||
|
impl TryFrom<u16> for SubsequentAfi {
|
||||||
|
type Error = eyre::Error;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
TryFrom::<u8>::try_from(value as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for SubsequentAfi {
|
impl Display for SubsequentAfi {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@ -2,4 +2,5 @@ pub mod constants;
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod ip_prefix;
|
pub mod ip_prefix;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod open;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|||||||
@ -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,10 +16,12 @@ 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;
|
||||||
use crate::ip_prefix::IpPrefix;
|
use crate::ip_prefix::IpPrefix;
|
||||||
|
use crate::open::OpenMessage;
|
||||||
use crate::parser::BgpParserError;
|
use crate::parser::BgpParserError;
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
|
|
||||||
@ -30,97 +33,68 @@ pub const AS_TRANS: u16 = 23456;
|
|||||||
pub const BGP4_VERSION: u8 = 4;
|
pub const BGP4_VERSION: u8 = 4;
|
||||||
|
|
||||||
/// Message represents the top-level messages in the BGP protocol.
|
/// Message represents the top-level messages in the BGP protocol.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Open(OpenMessage),
|
Open(OpenMessage) = 1,
|
||||||
Update(UpdateMessage),
|
Update(UpdateMessage) = 2,
|
||||||
Notification(NotificationMessage),
|
Notification(NotificationMessage) = 3,
|
||||||
KeepAlive,
|
|
||||||
// RouteRefresh(RouteRefreshMessage),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum MessageType {
|
|
||||||
/// BGP Open message (RFC 4271).
|
|
||||||
Open = 1,
|
|
||||||
/// BGP Update message (RFC 4271).
|
|
||||||
Update = 2,
|
|
||||||
/// BGP Notification message (RFC 4275).
|
|
||||||
Notification = 3,
|
|
||||||
/// BGP KeepAlive message (RFC 4271).
|
|
||||||
KeepAlive = 4,
|
KeepAlive = 4,
|
||||||
/// BGP Route Refresh message (RFC 2918).
|
/// BGP Route Refresh message (RFC 2918).
|
||||||
RouteRefresh = 5,
|
RouteRefresh = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
impl Message {}
|
||||||
pub struct OpenMessage {
|
|
||||||
/// Version of the BGP protocol in use.
|
|
||||||
pub version: u8,
|
|
||||||
/// AS Number of the BGP speaker, or AS_TRANS if using 4 byte ASN.
|
|
||||||
pub asn: u16,
|
|
||||||
/// Hold time parameter in seconds.
|
|
||||||
pub hold_time: u16,
|
|
||||||
/// Global identifier of the BGP speaker.
|
|
||||||
pub identifier: Ipv4Addr,
|
|
||||||
/// Options.
|
|
||||||
pub options: Vec<OpenOption>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum OpenOption {
|
|
||||||
Capabilities(Vec<Capability>),
|
|
||||||
/// BGP extended open options length (RFC 9072).
|
|
||||||
ExtendedLength(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum Capability {
|
|
||||||
/// MultiProtocol Extension (RFC 2858).
|
|
||||||
MultiProtocol { afi: u16, safi: u8 },
|
|
||||||
/// Route Refresh capability (RFC 2918).
|
|
||||||
RouteRefresh {},
|
|
||||||
/// Outbound Route Filtering (RFC 5291).
|
|
||||||
/// https://datatracker.ietf.org/doc/html/rfc5291
|
|
||||||
OutboundRouteFilter {},
|
|
||||||
/// Extended Next Hop encoding (RFC 8950).
|
|
||||||
ExtendedNextHop {},
|
|
||||||
/// Extended Message (RFC 8654).
|
|
||||||
ExtendedMessage {},
|
|
||||||
/// BGPSec (RFC 8205).
|
|
||||||
BgpSec {},
|
|
||||||
/// Multiple labels compatibility (RFC 8277).
|
|
||||||
MultiLabelCompat {},
|
|
||||||
/// Graceful restart capability (RFC 4724).
|
|
||||||
GracefulRestart {},
|
|
||||||
/// Four Byte ASN (RFC 4274).
|
|
||||||
FourByteAsn { asn: u32 },
|
|
||||||
/// Additional Path (RFC 7911).
|
|
||||||
AddPath {},
|
|
||||||
/// Enhanced Route Refresh (RFC 7313).
|
|
||||||
EnhancedRouteRefresh {},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a BGP Update message.
|
/// Represents a BGP Update message.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct UpdateMessage {}
|
pub struct UpdateMessage {
|
||||||
|
pub withdrawn_routes: Vec<IpPrefix>,
|
||||||
bitfield! {
|
pub path_attributes: Vec<PathAttribute>,
|
||||||
pub struct PathAttributeFlags(u8);
|
pub announced_routes: Vec<IpPrefix>,
|
||||||
impl Debug;
|
|
||||||
u8;
|
|
||||||
optional, set_optional: 0;
|
|
||||||
transitive, set_transitive: 1;
|
|
||||||
partial, set_partial: 2;
|
|
||||||
extended_length, set_extended_length: 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UpdateMessage {
|
||||||
|
pub fn from_wire<'a>(
|
||||||
|
_ctx: &ParserContext,
|
||||||
|
_buf: &'a [u8],
|
||||||
|
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_wire(&self, _ctx: &ParserContext, _out: &mut BytesMut) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wire_len(&self, _ctx: &ParserContext) -> Result<u16> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitfield! {
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct PathAttributeFlags(u8);
|
||||||
|
impl new;
|
||||||
|
u8;
|
||||||
|
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)]
|
#[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 +112,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 +206,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 +355,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 +402,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 +422,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 +474,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,10 +482,25 @@ impl AsPathAttribute {
|
|||||||
.wrap_err("AS Path length too long")?,
|
.wrap_err("AS Path length too long")?,
|
||||||
);
|
);
|
||||||
// AS numbers.
|
// AS numbers.
|
||||||
|
match ctx
|
||||||
|
.four_octet_asn
|
||||||
|
.ok_or(eyre!("ctx.four_octet_asn must be set"))?
|
||||||
|
{
|
||||||
|
true => {
|
||||||
for asn in &segment.path {
|
for asn in &segment.path {
|
||||||
out.put_u32(*asn);
|
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))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -280,7 +513,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 +530,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 +676,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 +894,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 +921,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 +958,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 +1002,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 +1060,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 +1073,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 +1097,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 +1108,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 +1117,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 +1170,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,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
980
crates/packet/src/open.rs
Normal file
980
crates/packet/src/open.rs
Normal file
@ -0,0 +1,980 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
use bitfield::bitfield;
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use eyre::{Result, bail, eyre};
|
||||||
|
use nom::{
|
||||||
|
Err::Failure,
|
||||||
|
IResult, Parser,
|
||||||
|
combinator::peek,
|
||||||
|
number::complete::{be_u8, be_u16, be_u24, be_u32},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::EnumDiscriminants;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
constants::{AddressFamilyId, SubsequentAfi},
|
||||||
|
parser::{BgpParserError, ParserContext, ProtocolErrorValues},
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! fail {
|
||||||
|
( $($arg:expr),* $(,)? ) => {
|
||||||
|
Failure(BgpParserError::Eyre(eyre!($($arg),*)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains messages related to the BGP Open message,
|
||||||
|
/// i.e. the Open message itself and options / capabilities.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct OpenMessage {
|
||||||
|
/// Version of the BGP protocol in use.
|
||||||
|
pub version: u8,
|
||||||
|
/// AS Number of the BGP speaker, or AS_TRANS if using 4 byte ASN.
|
||||||
|
pub asn: u16,
|
||||||
|
/// Hold time parameter in seconds.
|
||||||
|
pub hold_time: u16,
|
||||||
|
/// Global identifier of the BGP speaker.
|
||||||
|
pub identifier: Ipv4Addr,
|
||||||
|
/// Options.
|
||||||
|
pub options: Vec<OpenOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenMessage {
|
||||||
|
/// The from_wire implementation parses a BGP Open message from a buffer
|
||||||
|
/// containing the payload of the open message (i.e. without the BGP header / type / length)
|
||||||
|
/// and the buf passed in must contain only the open message.
|
||||||
|
pub fn from_wire<'a>(
|
||||||
|
ctx: &ParserContext,
|
||||||
|
buf: &'a [u8],
|
||||||
|
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
|
||||||
|
let (buf, version) = be_u8(buf)?;
|
||||||
|
if version != 4 {
|
||||||
|
return Err(Failure(BgpParserError::ProtocolError(
|
||||||
|
ProtocolErrorValues::UnknownOpenOption(version),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (buf, asn) = be_u16(buf)?;
|
||||||
|
let (buf, hold_time) = be_u16(buf)?;
|
||||||
|
let (buf, identifier) = be_u32(buf)?;
|
||||||
|
|
||||||
|
// RFC 9072: Extended Open Options length.
|
||||||
|
// To encode a capabilities length greater than 255 the packet structure
|
||||||
|
// is prefixed with the extended length option (255), a length of 255,
|
||||||
|
// and then a u16 containing the payload option.
|
||||||
|
let (buf, opt_len) = be_u8(buf)?;
|
||||||
|
let (_, inner) = peek(be_u8).parse(buf)?;
|
||||||
|
|
||||||
|
let (buf, opt_len) = if inner == 255 {
|
||||||
|
let (buf, _) = be_u8(buf)?; // Skip past the inner type.
|
||||||
|
be_u16(buf)?
|
||||||
|
} else {
|
||||||
|
(buf, opt_len as u16)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (buf, opt_buf) = nom::bytes::take(opt_len).parse(buf)?;
|
||||||
|
let (opt_buf, options) =
|
||||||
|
nom::multi::many0(|buf| OpenOption::from_wire(ctx, buf)).parse(opt_buf)?;
|
||||||
|
|
||||||
|
if !opt_buf.is_empty() {
|
||||||
|
return Err(fail!(
|
||||||
|
"Leftover bytes after parsing open options: {:x?}",
|
||||||
|
opt_buf
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
buf,
|
||||||
|
Self {
|
||||||
|
version,
|
||||||
|
asn,
|
||||||
|
hold_time,
|
||||||
|
identifier: Ipv4Addr::from(identifier),
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
|
||||||
|
out.put_u8(self.version);
|
||||||
|
out.put_u16(self.asn);
|
||||||
|
out.put_u16(self.hold_time);
|
||||||
|
out.put_u32(self.identifier.into());
|
||||||
|
|
||||||
|
// RFC 9072: Depending on whether the options fit in < 255 bytes
|
||||||
|
// we encode the length accordingly.
|
||||||
|
let opt_len = self
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.wire_len(ctx))
|
||||||
|
.sum::<Result<u16>>()?;
|
||||||
|
if opt_len > u8::MAX as u16 {
|
||||||
|
out.put_u8(0xff); // Set length to 255 as per RFC 9072.
|
||||||
|
out.put_u8(0xff); // Open option type for Ext length.
|
||||||
|
out.put_u16(opt_len);
|
||||||
|
} else {
|
||||||
|
out.put_u8(opt_len as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for option in &self.options {
|
||||||
|
option.to_wire(ctx, out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wire_len(&self, ctx: &ParserContext) -> Result<u16> {
|
||||||
|
let mut total = 10;
|
||||||
|
let opt_len = self
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.wire_len(ctx))
|
||||||
|
.sum::<Result<u16>>()?;
|
||||||
|
if opt_len > u8::MAX as u16 {
|
||||||
|
total += opt_len + 3; // 1+2 bytes extra for the type + extended length.
|
||||||
|
} else {
|
||||||
|
total += opt_len;
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum OpenOption {
|
||||||
|
Capabilities(Vec<Capability>) = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenOption {
|
||||||
|
pub fn from_wire<'a>(
|
||||||
|
ctx: &ParserContext,
|
||||||
|
buf: &'a [u8],
|
||||||
|
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
|
||||||
|
let (buf, type_code) = be_u8(buf)?;
|
||||||
|
let (buf, length) = be_u8(buf)?;
|
||||||
|
|
||||||
|
let (buf, payload) = nom::bytes::take(length).parse(buf)?;
|
||||||
|
|
||||||
|
match type_code {
|
||||||
|
v if v == OpenOptionDiscriminants::Capabilities as u8 => {
|
||||||
|
let (payload_consumed, (capabilities, _eof)) = nom::multi::many_till(
|
||||||
|
|buf| Capability::from_wire(ctx, buf),
|
||||||
|
nom::combinator::eof,
|
||||||
|
)
|
||||||
|
.parse(payload)?;
|
||||||
|
|
||||||
|
if !payload_consumed.is_empty() {
|
||||||
|
return Err(fail!(
|
||||||
|
"Leftover bytes when parsing BGP capabilities: {:x?}",
|
||||||
|
payload_consumed
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok((buf, Self::Capabilities(capabilities)))
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(Failure(BgpParserError::ProtocolError(
|
||||||
|
ProtocolErrorValues::UnknownOpenOption(other),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
OpenOption::Capabilities(items) => {
|
||||||
|
let cap_len = items.iter().map(|i| i.wire_len(ctx)).sum::<Result<u16>>()?;
|
||||||
|
if cap_len > u8::MAX as u16 {
|
||||||
|
bail!("Cannot write Capabilities with more than u8::MAX length");
|
||||||
|
}
|
||||||
|
out.put_u8(OpenOptionDiscriminants::Capabilities as u8);
|
||||||
|
out.put_u8(cap_len as u8);
|
||||||
|
for item in items {
|
||||||
|
item.to_wire(ctx, out)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wire_len(&self, ctx: &ParserContext) -> Result<u16> {
|
||||||
|
match self {
|
||||||
|
OpenOption::Capabilities(items) => items
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.wire_len(ctx))
|
||||||
|
.sum::<Result<u16>>()
|
||||||
|
.map(|v| v + 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Capability {
|
||||||
|
/// MultiProtocol Extension (RFC 2858).
|
||||||
|
MultiProtocol {
|
||||||
|
afi: AddressFamilyId,
|
||||||
|
safi: SubsequentAfi,
|
||||||
|
} = 1,
|
||||||
|
/// Route Refresh capability (RFC 2918).
|
||||||
|
RouteRefresh = 2,
|
||||||
|
/// Outbound Route Filtering (RFC 5291).
|
||||||
|
/// https://datatracker.ietf.org/doc/html/rfc5291
|
||||||
|
OutboundRouteFilter {
|
||||||
|
afi: AddressFamilyId,
|
||||||
|
safi: SubsequentAfi,
|
||||||
|
orfs: Vec<OutboundRouteFilterEntry>,
|
||||||
|
} = 3,
|
||||||
|
/// Extended Next Hop encoding (RFC 8950).
|
||||||
|
ExtendedNextHop {
|
||||||
|
entries: Vec<ExtendedNextHop>,
|
||||||
|
} = 5,
|
||||||
|
/// Extended Message (RFC 8654).
|
||||||
|
ExtendedMessage = 6,
|
||||||
|
/// BGPSec (RFC 8205).
|
||||||
|
BgpSec {
|
||||||
|
header: BgpSecHeader,
|
||||||
|
afi: AddressFamilyId,
|
||||||
|
} = 7,
|
||||||
|
/// Multiple labels compatibility (RFC 8277).
|
||||||
|
MultiLabelCompat {} = 8,
|
||||||
|
BgpRole {
|
||||||
|
role: BgpRole,
|
||||||
|
} = 9,
|
||||||
|
/// Graceful restart capability (RFC 4724).
|
||||||
|
GracefulRestart {
|
||||||
|
/// When set to true it means that the BGP speaker has just restarted
|
||||||
|
/// and the peer must not wait for the End of RIB marker from the
|
||||||
|
/// speaker before advertising routing information to the speaker.
|
||||||
|
restart_state: bool,
|
||||||
|
/// This is the estimated time (in seconds) it will take for the
|
||||||
|
/// BGP session to be re-established after a restart. This can be
|
||||||
|
/// used to speed up routing convergence by its peer in case that
|
||||||
|
/// the BGP speaker does not come back after a restart.
|
||||||
|
restart_time: u16,
|
||||||
|
/// The AFI and SAFI, taken in combination, indicate that Graceful
|
||||||
|
/// Restart is supported for routes that are advertised with the
|
||||||
|
/// same AFI and SAFI. Routes may be explicitly associated with a
|
||||||
|
/// particular AFI and SAFI using the encoding of [BGP-MP] or
|
||||||
|
/// implicitly associated with <AFI=IPv4, SAFI=Unicast> if using
|
||||||
|
/// the encoding of [BGP-4].
|
||||||
|
entries: Vec<GracefulRestartEntry>,
|
||||||
|
} = 64,
|
||||||
|
/// Four Byte ASN (RFC 4274).
|
||||||
|
FourByteAsn {
|
||||||
|
asn: u32,
|
||||||
|
} = 65,
|
||||||
|
/// Additional Path (RFC 7911).
|
||||||
|
AddPath {
|
||||||
|
afi: AddressFamilyId,
|
||||||
|
safi: SubsequentAfi,
|
||||||
|
send_recv: AddPathSendRecv,
|
||||||
|
} = 69,
|
||||||
|
/// Enhanced Route Refresh (RFC 7313).
|
||||||
|
EnhancedRouteRefresh = 70,
|
||||||
|
/// Long Lived Graceful Restart (RFC 9494).
|
||||||
|
LongLivedGracefulRestart(Vec<LongLivedGracefulRestart>) = 71,
|
||||||
|
/// Unknown Capability encapsulates any capabilities that we don't know about.
|
||||||
|
UnknownCapability {
|
||||||
|
type_code: u8,
|
||||||
|
length: u8,
|
||||||
|
value: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capability {
|
||||||
|
fn discriminant(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Capability::MultiProtocol { .. } => CapabilityDiscriminants::MultiProtocol as u8,
|
||||||
|
Capability::RouteRefresh => CapabilityDiscriminants::RouteRefresh as u8,
|
||||||
|
Capability::OutboundRouteFilter { .. } => {
|
||||||
|
CapabilityDiscriminants::OutboundRouteFilter as u8
|
||||||
|
}
|
||||||
|
Capability::ExtendedNextHop { .. } => CapabilityDiscriminants::ExtendedNextHop as u8,
|
||||||
|
Capability::ExtendedMessage => CapabilityDiscriminants::ExtendedMessage as u8,
|
||||||
|
Capability::BgpSec { .. } => CapabilityDiscriminants::BgpSec as u8,
|
||||||
|
Capability::MultiLabelCompat {} => CapabilityDiscriminants::MultiLabelCompat as u8,
|
||||||
|
Capability::BgpRole { .. } => CapabilityDiscriminants::BgpRole as u8,
|
||||||
|
Capability::GracefulRestart { .. } => CapabilityDiscriminants::GracefulRestart as u8,
|
||||||
|
Capability::FourByteAsn { .. } => CapabilityDiscriminants::FourByteAsn as u8,
|
||||||
|
Capability::AddPath { .. } => CapabilityDiscriminants::AddPath as u8,
|
||||||
|
Capability::EnhancedRouteRefresh => CapabilityDiscriminants::EnhancedRouteRefresh as u8,
|
||||||
|
Capability::LongLivedGracefulRestart(_) => {
|
||||||
|
CapabilityDiscriminants::LongLivedGracefulRestart as u8
|
||||||
|
}
|
||||||
|
Capability::UnknownCapability { type_code, .. } => *type_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// `from_wire` parses a Capability message.
|
||||||
|
pub fn from_wire<'a>(
|
||||||
|
_ctx: &ParserContext,
|
||||||
|
buf: &'a [u8],
|
||||||
|
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
|
||||||
|
let (buf, typ) = be_u8(buf)?;
|
||||||
|
let (buf, len) = be_u8(buf)?;
|
||||||
|
let (buf, cap_buf) = nom::bytes::take(len).parse(buf)?;
|
||||||
|
macro_rules! require_empty {
|
||||||
|
($buf:expr, $cap_type:expr) => {
|
||||||
|
if !buf.is_empty() {
|
||||||
|
return Err(nom::Err::Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"Unexpected leftover bytes {:x?} while parsing {} capability",
|
||||||
|
$buf,
|
||||||
|
$cap_type
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match typ {
|
||||||
|
v if v == CapabilityDiscriminants::MultiProtocol as u8 => {
|
||||||
|
// MultiProtocol capability consists of an (AFI, SAFI).
|
||||||
|
let (cap_buf, afi) = be_u16(cap_buf)?;
|
||||||
|
let (cap_buf, _) = be_u8(cap_buf)?;
|
||||||
|
let (cap_buf, safi) = be_u8(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "MultiProtocol");
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
Ok((buf, Self::MultiProtocol { afi, safi }))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::RouteRefresh as u8 => {
|
||||||
|
require_empty!(cap_buf, "RouteRefresh");
|
||||||
|
Ok((buf, Self::RouteRefresh))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::OutboundRouteFilter as u8 => {
|
||||||
|
let (cap_buf, afi) = be_u16(cap_buf)?;
|
||||||
|
let (cap_buf, _) = be_u8(cap_buf)?; // Move past reverved byte.
|
||||||
|
let (cap_buf, safi) = be_u8(cap_buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
|
||||||
|
let (cap_buf, _num_orfs) = be_u8(cap_buf)?;
|
||||||
|
|
||||||
|
let parse_orf = |buf: &'a [u8]| -> IResult<
|
||||||
|
&'a [u8],
|
||||||
|
OutboundRouteFilterEntry,
|
||||||
|
BgpParserError<&'a [u8]>,
|
||||||
|
> {
|
||||||
|
let (buf, orf_type) = be_u8(buf)?;
|
||||||
|
let (buf, send_recv) = be_u8(buf)?;
|
||||||
|
let orf_type = OrfType::from(orf_type);
|
||||||
|
let send_recv = OrfSendRecv::try_from(send_recv)
|
||||||
|
.map_err(|e| fail!("Failed to parse ORF send/recv: {}", e))?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
buf,
|
||||||
|
OutboundRouteFilterEntry {
|
||||||
|
r#type: orf_type,
|
||||||
|
send_recv,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cap_buf, orfs) = nom::multi::many0(parse_orf).parse(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "OutboundRouteFilter");
|
||||||
|
|
||||||
|
Ok((buf, Self::OutboundRouteFilter { afi, safi, orfs }))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::ExtendedNextHop as u8 => {
|
||||||
|
// parse_enh parses a single extended nexthop entry.
|
||||||
|
let parse_enh = |buf: &'a [u8]| -> IResult<
|
||||||
|
&'a [u8],
|
||||||
|
ExtendedNextHop,
|
||||||
|
BgpParserError<&'a [u8]>,
|
||||||
|
> {
|
||||||
|
let (buf, afi) = be_u16(buf)?;
|
||||||
|
let (buf, safi) = be_u16(buf)?;
|
||||||
|
let (buf, nh_afi) = be_u16(buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let nh_afi = AddressFamilyId::try_from(nh_afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
Ok((buf, ExtendedNextHop { afi, safi, nh_afi }))
|
||||||
|
};
|
||||||
|
let (cap_buf, entries) = nom::multi::many1(parse_enh).parse(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "ExtendedNextHop");
|
||||||
|
|
||||||
|
Ok((cap_buf, Self::ExtendedNextHop { entries }))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::ExtendedMessage as u8 => {
|
||||||
|
require_empty!(cap_buf, "ExtendedMessage");
|
||||||
|
Ok((cap_buf, Self::ExtendedMessage))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::BgpSec as u8 => {
|
||||||
|
let (cap_buf, header) = be_u8(cap_buf)?;
|
||||||
|
let (cap_buf, afi) = be_u16(cap_buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
require_empty!(cap_buf, "BgpSec");
|
||||||
|
Ok((
|
||||||
|
cap_buf,
|
||||||
|
Self::BgpSec {
|
||||||
|
header: BgpSecHeader(header),
|
||||||
|
afi,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::MultiLabelCompat as u8 => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::BgpRole as u8 => {
|
||||||
|
let (cap_buf, role) = be_u8(cap_buf)?;
|
||||||
|
let role = BgpRole::try_from(role).map_err(|e| fail!("{}", e))?;
|
||||||
|
require_empty!(cap_buf, "BgpRole");
|
||||||
|
Ok((cap_buf, Self::BgpRole { role }))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::GracefulRestart as u8 => {
|
||||||
|
// flags_restart_time is: (4 bits: restart flags, 12 bits: restart time (s)).
|
||||||
|
let (cap_buf, flags_restart_time) = be_u16(cap_buf)?;
|
||||||
|
let restart_state = ((1 << 15) & flags_restart_time) != 0;
|
||||||
|
let restart_time = flags_restart_time & 0xfff;
|
||||||
|
let parse_entry = |buf: &'a [u8]| -> IResult<
|
||||||
|
&'a [u8],
|
||||||
|
GracefulRestartEntry,
|
||||||
|
BgpParserError<&'a [u8]>,
|
||||||
|
> {
|
||||||
|
let (buf, afi) = be_u16(buf)?;
|
||||||
|
let (buf, safi) = be_u8(buf)?;
|
||||||
|
let (buf, flags) = be_u8(buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
Ok((
|
||||||
|
buf,
|
||||||
|
GracefulRestartEntry {
|
||||||
|
afi,
|
||||||
|
safi,
|
||||||
|
preserve_forwarding: (flags & (1 << 7)) != 0,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let (cap_buf, entries) = nom::multi::many0(parse_entry).parse(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "GracefulRestart");
|
||||||
|
Ok((
|
||||||
|
cap_buf,
|
||||||
|
Self::GracefulRestart {
|
||||||
|
restart_state,
|
||||||
|
restart_time,
|
||||||
|
entries,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::FourByteAsn as u8 => {
|
||||||
|
let (cap_buf, asn) = be_u32(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "FourByteAsn");
|
||||||
|
Ok((cap_buf, Self::FourByteAsn { asn }))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::AddPath as u8 => {
|
||||||
|
let (cap_buf, afi) = be_u16(cap_buf)?;
|
||||||
|
let (cap_buf, safi) = be_u8(cap_buf)?;
|
||||||
|
let (cap_buf, send_recv) = be_u8(cap_buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let send_recv = AddPathSendRecv::try_from(send_recv).map_err(|e| fail!("{}", e))?;
|
||||||
|
require_empty!(cap_buf, "AddPath");
|
||||||
|
Ok((
|
||||||
|
cap_buf,
|
||||||
|
Self::AddPath {
|
||||||
|
afi,
|
||||||
|
safi,
|
||||||
|
send_recv,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::EnhancedRouteRefresh as u8 => {
|
||||||
|
require_empty!(cap_buf, "EnhancedRouteRefresh");
|
||||||
|
Ok((cap_buf, Self::EnhancedRouteRefresh))
|
||||||
|
}
|
||||||
|
v if v == CapabilityDiscriminants::LongLivedGracefulRestart as u8 => {
|
||||||
|
let parse_llgr = |buf: &'a [u8]| -> IResult<
|
||||||
|
&'a [u8],
|
||||||
|
LongLivedGracefulRestart,
|
||||||
|
BgpParserError<&'a [u8]>,
|
||||||
|
> {
|
||||||
|
let (buf, afi) = be_u16(buf)?;
|
||||||
|
let (buf, safi) = be_u8(buf)?;
|
||||||
|
let (buf, flags) = be_u8(buf)?;
|
||||||
|
let (buf, stale_time) = be_u24(buf)?;
|
||||||
|
let afi = AddressFamilyId::try_from(afi).map_err(|e| fail!("{}", e))?;
|
||||||
|
let safi = SubsequentAfi::try_from(safi).map_err(|e| fail!("{}", e))?;
|
||||||
|
Ok((
|
||||||
|
buf,
|
||||||
|
LongLivedGracefulRestart {
|
||||||
|
afi,
|
||||||
|
safi,
|
||||||
|
flags,
|
||||||
|
stale_time,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let (cap_buf, entries) = nom::multi::many0(parse_llgr).parse(cap_buf)?;
|
||||||
|
require_empty!(cap_buf, "LongLivedGracefulRestart");
|
||||||
|
Ok((cap_buf, Self::LongLivedGracefulRestart(entries)))
|
||||||
|
}
|
||||||
|
type_code => {
|
||||||
|
// Handling an unknown capability, so just parse it out and store the value
|
||||||
|
// as bytes.
|
||||||
|
Ok((
|
||||||
|
&[],
|
||||||
|
Self::UnknownCapability {
|
||||||
|
type_code,
|
||||||
|
length: cap_buf.len() as u8,
|
||||||
|
value: cap_buf.to_vec(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_wire(&self, _ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
|
||||||
|
out.put_u8(self.discriminant());
|
||||||
|
out.put_u8(self.payload_len());
|
||||||
|
match self {
|
||||||
|
Capability::MultiProtocol { afi, safi } => {
|
||||||
|
out.put_u16(*afi as u16);
|
||||||
|
out.put_u8(0); // Reserved byte.
|
||||||
|
out.put_u8(*safi as u8);
|
||||||
|
}
|
||||||
|
Capability::RouteRefresh => {}
|
||||||
|
Capability::OutboundRouteFilter { afi, safi, orfs } => {
|
||||||
|
out.put_u16(*afi as u16);
|
||||||
|
out.put_u8(0); // Reserved byte.
|
||||||
|
out.put_u8(*safi as u8);
|
||||||
|
for orf in orfs {
|
||||||
|
out.put_u8(orf.r#type.clone().into());
|
||||||
|
out.put_u8(orf.send_recv.clone() as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Capability::ExtendedNextHop { entries } => {
|
||||||
|
for entry in entries {
|
||||||
|
out.put_u16(entry.afi as u16);
|
||||||
|
out.put_u16(entry.safi as u16);
|
||||||
|
out.put_u16(entry.nh_afi as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Capability::ExtendedMessage => {}
|
||||||
|
Capability::BgpSec { header, afi } => {
|
||||||
|
out.put_u8(header.0);
|
||||||
|
out.put_u16(*afi as u16);
|
||||||
|
}
|
||||||
|
Capability::MultiLabelCompat {} => todo!(),
|
||||||
|
Capability::BgpRole { role } => {
|
||||||
|
out.put_u8(role.into());
|
||||||
|
}
|
||||||
|
Capability::GracefulRestart {
|
||||||
|
restart_state,
|
||||||
|
restart_time,
|
||||||
|
entries,
|
||||||
|
} => {
|
||||||
|
let mut flags_restart_time = 0;
|
||||||
|
if *restart_state {
|
||||||
|
flags_restart_time |= 1 << 15;
|
||||||
|
}
|
||||||
|
if *restart_time > 0xfff {
|
||||||
|
bail!("Restart time too large to be encoded: {}", restart_time);
|
||||||
|
}
|
||||||
|
flags_restart_time |= restart_time;
|
||||||
|
out.put_u16(flags_restart_time);
|
||||||
|
for entry in entries {
|
||||||
|
out.put_u16(entry.afi as u16);
|
||||||
|
out.put_u8(entry.safi as u8);
|
||||||
|
let mut flags = 0;
|
||||||
|
if entry.preserve_forwarding {
|
||||||
|
flags |= 1 << 7;
|
||||||
|
}
|
||||||
|
out.put_u8(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Capability::FourByteAsn { asn } => {
|
||||||
|
out.put_u32(*asn);
|
||||||
|
}
|
||||||
|
Capability::AddPath {
|
||||||
|
afi,
|
||||||
|
safi,
|
||||||
|
send_recv,
|
||||||
|
} => {
|
||||||
|
out.put_u16(*afi as u16);
|
||||||
|
out.put_u8(*safi as u8);
|
||||||
|
out.put_u8(send_recv.into());
|
||||||
|
}
|
||||||
|
Capability::EnhancedRouteRefresh => {}
|
||||||
|
Capability::LongLivedGracefulRestart(llgrs) => {
|
||||||
|
for llgr in llgrs {
|
||||||
|
out.put_u16(llgr.afi as u16);
|
||||||
|
out.put_u8(llgr.safi as u8);
|
||||||
|
out.put_u8(llgr.flags as u8);
|
||||||
|
out.put(&llgr.stale_time.to_be_bytes()[1..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Capability::UnknownCapability { value, .. } => out.put(value.as_slice()),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wire_len(&self, _ctx: &ParserContext) -> Result<u16> {
|
||||||
|
Ok(2 + self.payload_len() as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn payload_len(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Capability::MultiProtocol { .. } => 4,
|
||||||
|
Capability::RouteRefresh => 0,
|
||||||
|
Capability::OutboundRouteFilter { orfs, .. } => 4 + 2 * orfs.len() as u8,
|
||||||
|
Capability::ExtendedNextHop { entries } => 6 * entries.len() as u8,
|
||||||
|
Capability::ExtendedMessage => 0,
|
||||||
|
Capability::BgpSec { .. } => 3,
|
||||||
|
Capability::MultiLabelCompat {} => todo!(),
|
||||||
|
Capability::BgpRole { .. } => 1,
|
||||||
|
Capability::GracefulRestart { entries, .. } => 2 + 4 * entries.len() as u8,
|
||||||
|
Capability::FourByteAsn { .. } => 4,
|
||||||
|
Capability::AddPath { .. } => 4,
|
||||||
|
Capability::EnhancedRouteRefresh => 0,
|
||||||
|
Capability::LongLivedGracefulRestart(long_lived_graceful_restarts) => {
|
||||||
|
8 * long_lived_graceful_restarts.len() as u8
|
||||||
|
}
|
||||||
|
Capability::UnknownCapability { value, .. } => value.len() as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an entry for the OutboundRouteFilter capability.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct OutboundRouteFilterEntry {
|
||||||
|
pub r#type: OrfType,
|
||||||
|
pub send_recv: OrfSendRecv,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum OrfType {
|
||||||
|
Reserved = 0,
|
||||||
|
Unassigned(u8),
|
||||||
|
AddressPrefix = 64,
|
||||||
|
CoveringPrefix = 65,
|
||||||
|
VpnPrefix = 66,
|
||||||
|
VendorSpecific(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for OrfType {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
v if v == OrfTypeDiscriminants::Reserved as u8 => Self::Reserved,
|
||||||
|
v if (1..64).contains(&v) => Self::Unassigned(v),
|
||||||
|
v if v == OrfTypeDiscriminants::AddressPrefix as u8 => Self::AddressPrefix,
|
||||||
|
v if v == OrfTypeDiscriminants::CoveringPrefix as u8 => Self::CoveringPrefix,
|
||||||
|
v if v == OrfTypeDiscriminants::VpnPrefix as u8 => Self::VpnPrefix,
|
||||||
|
other => Self::VendorSpecific(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OrfType> for u8 {
|
||||||
|
fn from(value: OrfType) -> Self {
|
||||||
|
match value {
|
||||||
|
OrfType::Reserved => OrfTypeDiscriminants::Reserved as u8,
|
||||||
|
OrfType::Unassigned(inner) => inner,
|
||||||
|
OrfType::AddressPrefix => OrfTypeDiscriminants::AddressPrefix as u8,
|
||||||
|
OrfType::CoveringPrefix => OrfTypeDiscriminants::CoveringPrefix as u8,
|
||||||
|
OrfType::VpnPrefix => OrfTypeDiscriminants::VpnPrefix as u8,
|
||||||
|
OrfType::VendorSpecific(inner) => inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum OrfSendRecv {
|
||||||
|
Receive = 1,
|
||||||
|
Send = 2,
|
||||||
|
Both = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for OrfSendRecv {
|
||||||
|
type Error = eyre::ErrReport;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
Ok(match value {
|
||||||
|
value if value == OrfSendRecv::Receive as u8 => OrfSendRecv::Receive,
|
||||||
|
value if value == OrfSendRecv::Send as u8 => OrfSendRecv::Send,
|
||||||
|
value if value == OrfSendRecv::Both as u8 => OrfSendRecv::Both,
|
||||||
|
other => bail!("Invalid ORF send/recv value: {}", other),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AddPathSendRecv {
|
||||||
|
Receive = 1,
|
||||||
|
Send = 2,
|
||||||
|
Both = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for AddPathSendRecv {
|
||||||
|
type Error = eyre::ErrReport;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
Ok(match value {
|
||||||
|
value if value == AddPathSendRecv::Receive as u8 => AddPathSendRecv::Receive,
|
||||||
|
value if value == AddPathSendRecv::Send as u8 => AddPathSendRecv::Send,
|
||||||
|
value if value == AddPathSendRecv::Both as u8 => AddPathSendRecv::Both,
|
||||||
|
other => bail!("Invalid AddPathSendRecv send/recv value: {}", other),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&AddPathSendRecv> for u8 {
|
||||||
|
fn from(value: &AddPathSendRecv) -> Self {
|
||||||
|
match value {
|
||||||
|
AddPathSendRecv::Receive => AddPathSendRecv::Receive as u8,
|
||||||
|
AddPathSendRecv::Send => AddPathSendRecv::Send as u8,
|
||||||
|
AddPathSendRecv::Both => AddPathSendRecv::Both as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `ExtendedNextHop` allows for the nexthop to be for a different address family
|
||||||
|
/// than the one being used to advertise prefixes for. E.g. it allows for sending
|
||||||
|
/// IPv4 prefixes with an IPv6 nexthop.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct ExtendedNextHop {
|
||||||
|
/// The Address Family to update the nexthop format for.
|
||||||
|
pub afi: AddressFamilyId,
|
||||||
|
/// The Subsequent Address Family to update the nexthop format for.
|
||||||
|
pub safi: SubsequentAfi,
|
||||||
|
/// The Address Family that nexthops will be encoded as.
|
||||||
|
pub nh_afi: AddressFamilyId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, EnumDiscriminants, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum BgpRole {
|
||||||
|
Provider = 0,
|
||||||
|
RouteServer = 1,
|
||||||
|
RouteServerClient = 2,
|
||||||
|
Customer = 3,
|
||||||
|
Peer = 4,
|
||||||
|
Unknown(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for BgpRole {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
v if v == BgpRoleDiscriminants::Provider as u8 => Self::Provider,
|
||||||
|
v if v == BgpRoleDiscriminants::RouteServer as u8 => Self::RouteServer,
|
||||||
|
v if v == BgpRoleDiscriminants::RouteServerClient as u8 => Self::RouteServerClient,
|
||||||
|
v if v == BgpRoleDiscriminants::Customer as u8 => Self::Customer,
|
||||||
|
v if v == BgpRoleDiscriminants::Peer as u8 => Self::Peer,
|
||||||
|
other => Self::Unknown(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&BgpRole> for u8 {
|
||||||
|
fn from(value: &BgpRole) -> Self {
|
||||||
|
match value {
|
||||||
|
BgpRole::Provider => BgpRoleDiscriminants::Provider as u8,
|
||||||
|
BgpRole::RouteServer => BgpRoleDiscriminants::RouteServer as u8,
|
||||||
|
BgpRole::RouteServerClient => BgpRoleDiscriminants::RouteServerClient as u8,
|
||||||
|
BgpRole::Customer => BgpRoleDiscriminants::Customer as u8,
|
||||||
|
BgpRole::Peer => BgpRoleDiscriminants::Peer as u8,
|
||||||
|
BgpRole::Unknown(other) => *other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct LongLivedGracefulRestart {
|
||||||
|
pub afi: AddressFamilyId,
|
||||||
|
pub safi: SubsequentAfi,
|
||||||
|
pub flags: u8,
|
||||||
|
/// stale_time is the time in seconds after which routes are considered stale.
|
||||||
|
/// Note that this is represented as a u24 on the wire.
|
||||||
|
pub stale_time: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitfield! {
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct BgpSecHeader(u8);
|
||||||
|
impl new;
|
||||||
|
u8, version, set_version : 3, 0; // 4 bits
|
||||||
|
bool, dir, set_dir : 4; // single bit (hi==lo)
|
||||||
|
u8, unassigned, set_unassigned : 7, 5; // 3 bits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an entry in the BGP Graceful Restart capability.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct GracefulRestartEntry {
|
||||||
|
afi: AddressFamilyId,
|
||||||
|
safi: SubsequentAfi,
|
||||||
|
/// Forwarding State (F) bit, which can be used to indicate whether the
|
||||||
|
/// forwarding state for routes that were advertised with the given AFI
|
||||||
|
/// and SAFI has indeed been preserved during the previous BGP restart.
|
||||||
|
preserve_forwarding: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
constants::{AddressFamilyId, SubsequentAfi},
|
||||||
|
open::{Capability, ExtendedNextHop, GracefulRestartEntry, OpenMessage, OpenOption},
|
||||||
|
parser::ParserContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! test_capability_roundtrip {
|
||||||
|
($name:ident, $input_bytes: expr, $expected: expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $name() -> Result<()> {
|
||||||
|
let ctx = &default_v6_context();
|
||||||
|
let (buf, parsed) = Capability::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(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_capability_roundtrip!(
|
||||||
|
test_multiptotocol,
|
||||||
|
&[0x01, 0x04, 0x00, 0x01, 0x00, 0x01],
|
||||||
|
Capability::MultiProtocol {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_capability_roundtrip!(test_route_refresh, &[0x02, 0x00], Capability::RouteRefresh);
|
||||||
|
|
||||||
|
test_capability_roundtrip!(
|
||||||
|
test_route_refresh_cisco_unsupported,
|
||||||
|
&[0x80, 0x00],
|
||||||
|
Capability::UnknownCapability {
|
||||||
|
type_code: 0x80,
|
||||||
|
length: 0x00,
|
||||||
|
value: vec![]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_capability_roundtrip!(
|
||||||
|
test_four_octet_asn,
|
||||||
|
&[0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8],
|
||||||
|
Capability::FourByteAsn { asn: 65000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
test_capability_roundtrip!(
|
||||||
|
test_graceful_restart,
|
||||||
|
&[0x40, 0x06, 0x01, 0x2c, 0x00, 0x01, 0x01, 0x00],
|
||||||
|
Capability::GracefulRestart {
|
||||||
|
restart_state: false,
|
||||||
|
restart_time: 300,
|
||||||
|
entries: vec![GracefulRestartEntry {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast,
|
||||||
|
preserve_forwarding: false,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_capability_roundtrip!(
|
||||||
|
test_extended_nexthop,
|
||||||
|
&[
|
||||||
|
0x05, 0x12, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02,
|
||||||
|
0x00, 0x01, 0x00, 0x80, 0x00, 0x02
|
||||||
|
],
|
||||||
|
Capability::ExtendedNextHop {
|
||||||
|
entries: vec![
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6,
|
||||||
|
},
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Multicast,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6,
|
||||||
|
},
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::MplsLabelledVpn,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_roundtrip() -> Result<()> {
|
||||||
|
let buf = &[
|
||||||
|
0x04, 0xfd, 0xe8, 0x00, 0xb4, 0x0a, 0x00, 0x00, 0x01, 0x38, 0x02, 0x06, 0x01, 0x04,
|
||||||
|
0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x06,
|
||||||
|
0x41, 0x04, 0x00, 0x00, 0xfd, 0xe8, 0x02, 0x08, 0x40, 0x06, 0x01, 0x2c, 0x00, 0x01,
|
||||||
|
0x01, 0x00, 0x02, 0x14, 0x05, 0x12, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01,
|
||||||
|
0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x80, 0x00, 0x02,
|
||||||
|
];
|
||||||
|
let expected = OpenMessage {
|
||||||
|
version: 4,
|
||||||
|
asn: 65000,
|
||||||
|
hold_time: 180,
|
||||||
|
identifier: Ipv4Addr::new(10, 0, 0, 1),
|
||||||
|
options: vec![
|
||||||
|
OpenOption::Capabilities(vec![Capability::MultiProtocol {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast,
|
||||||
|
}]),
|
||||||
|
OpenOption::Capabilities(vec![Capability::UnknownCapability {
|
||||||
|
type_code: 0x80,
|
||||||
|
length: 0x00,
|
||||||
|
value: vec![],
|
||||||
|
}]),
|
||||||
|
OpenOption::Capabilities(vec![Capability::RouteRefresh]),
|
||||||
|
OpenOption::Capabilities(vec![Capability::FourByteAsn { asn: 65000 }]),
|
||||||
|
OpenOption::Capabilities(vec![Capability::GracefulRestart {
|
||||||
|
restart_state: false,
|
||||||
|
restart_time: 300,
|
||||||
|
entries: vec![GracefulRestartEntry {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast,
|
||||||
|
preserve_forwarding: false,
|
||||||
|
}],
|
||||||
|
}]),
|
||||||
|
OpenOption::Capabilities(vec![Capability::ExtendedNextHop {
|
||||||
|
entries: vec![
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Unicast,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6,
|
||||||
|
},
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::Multicast,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6,
|
||||||
|
},
|
||||||
|
ExtendedNextHop {
|
||||||
|
afi: AddressFamilyId::Ipv4,
|
||||||
|
safi: SubsequentAfi::MplsLabelledVpn,
|
||||||
|
nh_afi: AddressFamilyId::Ipv6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = default_v6_context();
|
||||||
|
let (_, parsed) = OpenMessage::from_wire(&ctx, buf)?;
|
||||||
|
assert_eq!(parsed, expected);
|
||||||
|
let mut out = BytesMut::with_capacity(u16::MAX as usize);
|
||||||
|
parsed.to_wire(&ctx, &mut out)?;
|
||||||
|
assert_eq!(buf.to_vec(), out.to_vec());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,9 @@ impl std::error::Error for ToWireError {}
|
|||||||
// Custom error type for the parser.
|
// Custom error type for the parser.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BgpParserError<I> {
|
pub enum BgpParserError<I> {
|
||||||
|
/// ProtocolError encapsulates protocol level semantics problems that require
|
||||||
|
/// action from the caller of the parser.
|
||||||
|
ProtocolError(ProtocolErrorValues),
|
||||||
CustomText(&'static str),
|
CustomText(&'static str),
|
||||||
Eyre(eyre::ErrReport),
|
Eyre(eyre::ErrReport),
|
||||||
Nom(I, ErrorKind),
|
Nom(I, ErrorKind),
|
||||||
@ -47,3 +50,12 @@ impl<I> ParseError<I> for BgpParserError<I> {
|
|||||||
other
|
other
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ProtocolErrorValues {
|
||||||
|
/// `UnsupportedVersion` is returned when the BGP Open message received is for a
|
||||||
|
/// version other than one we support. The unsupported version number is the value.
|
||||||
|
UnsupportedVersion(u8),
|
||||||
|
/// `UnknownOpenOption` is returned with the open option type code that is unknown.
|
||||||
|
UnknownOpenOption(u8),
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user