Compare commits

..

3 Commits

Author SHA1 Message Date
1d3c6aa977 Start of major rewrite/cleanup.
Some checks are pending
Rust / build (push) Waiting to run
2025-04-19 18:19:37 +02:00
4b130985da Fix cargo.toml package info 2025-04-19 17:28:27 +02:00
bea504ea8b Initial commit 2025-04-19 17:13:04 +02:00
12 changed files with 1913 additions and 124 deletions

455
Cargo.lock generated Normal file
View File

@ -0,0 +1,455 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bgp"
version = "0.0.1"
dependencies = [
"eyre",
"tokio",
]
[[package]]
name = "bgp-packet"
version = "0.0.1"
dependencies = [
"bitfield",
"bytes",
"eyre",
"nom",
"serde",
"serde_repr",
"thiserror",
]
[[package]]
name = "bitfield"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "786e53b0c071573a28956cec19a92653e42de34c683e2f6e86c197a349fba318"
dependencies = [
"bitfield-macros",
]
[[package]]
name = "bitfield-macros"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07805405d3f1f3a55aab895718b488821d40458f9188059909091ae0935c344a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "eyre"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@ -1,70 +1,30 @@
[workspace]
members = [
"bin",
"crates/bgp_packet",
"crates/route_client",
"crates/server",
# Tests
"tests/integration_tests",
]
resolver = "2"
default-members = ["bin/bgp"]
members = ["bin/*", "crates/*"]
resolver = "3"
[workspace.package]
authors = ["Rayhaan Jaufeerally <rayhaan@rayhaan.ch>"]
edition = "2021"
homepage = "https://github.com/net-control-plane/bgp"
license = "Apache 2"
repository = "https://github.com/net-control-plane/bgp"
rust-version = "1.76"
version = "0.1.1"
# [[bin]]
# name = "bgp_server"
# path = "src/main.rs"
# [[bin]]
# name = "route_client"
# path = "src/route_client/main.rs"
# [[bin]]
# name = "streamer_cli"
# path = "src/streamer_cli/main.rs"
[workspace.lints]
rust.unused_must_use = "deny"
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
description = "A Border Gateway Protocol implementation"
edition = "2024"
homepage = "https://rayhaan.ch"
license = "Apache-2.0"
name = "bgp"
repository = "https://github.com/net-control-plane/bgp"
version = "0.0.1"
[workspace.dependencies]
anyhow = "1.0.71"
async-trait = "0.1.80"
byteorder = "1.4.3"
bytes = "1.*"
clap = { version = "4.5.11", features = ["cargo", "derive"] }
eyre = "0.6.12"
futures = "0.3"
ip_network_table-deps-treebitmap = "0.5.0"
ipnet = "2.3.0"
libc = "0.2.126"
log = "0.4"
netlink = "0.1.1"
netlink-packet-route = "0.21.0"
netlink-packet-utils = "0.5.2"
nom = "7.1"
prost = "0.8"
rtnetlink = "0.14.1"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0.64"
stderrlog = "0.5.1"
tokio = { version = "1.13.0", features = ["full"] }
tokio-stream = { version = "0.1.7", features = ["net"] }
tokio-util = { version = "0.6.7", features = ["codec"] }
tonic = { version = "0.5", features = ["compression"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
warp = "0.3.5"
# --- Our crates ---
bgp-packet = { path = "crates/packet" }
# -- Local crates --
bgp_packet = { path = "crates/bgp_packet" }
bgp_server = { path = "crates/server" }
bin = { path = "bin" }
route_client = { path = "crates/route_client" }
# --- General ---
bitfield = "0.19.0"
bytes = "1.10.1"
eyre = "0.6.12"
nom = "8.0.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_repr = "0.1.20"
thiserror = "2.0.12"
tokio = { version = "1.44.2", features = ["full"] }
>>>>>>> ferrix/master

View File

@ -1,66 +1,12 @@
# BGP
This project implements a Border Gateway Protocol speaker as defined in [RFC4271](https://datatracker.ietf.org/doc/html/rfc4271) (not specification compliant yet, see status below), and provides programmatic access to the routes learned by the server.
This project is an implementation of the Border Gateway Protocol and associated functionality required to run an Autonomous System.
The aim of the project is to provide a fully programatic interface for configuring, filtering, and exporting routes from the server to other peers and to the forwarding plane.
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.
## Design
## Current status.
The actual protocol interface logic (serializing bytes to objects, and objects to bytes) is contained within `bgpd/src/bgp_packet`. In there several key things are defined:
* NLRIs - A Network Layer Reachability Information object is a fancy way to say IP prefix, and represents the bytes of the prefix and the prefix length. For example 2001:db8::/32 is `0x20 0x01 0x0d 0xb8` with a prefix length of 32.
* Path Attibutes - are carried within BGP UPDATE messages and contain a wealth of metadata about a particular route, and in cases such as IPv6 announcements over MP-BGP, contains the actual prefixes and their nexthops. The parsers for path attributes are in `bgpd/src/bgp_packet/path_attributes.rs`.
* BGP Messages and the associated top-level parsing logic is contained in `bgpd/src/bgp_packet/messages.rs`.
The server logic is contained in `bgpd/src/server` and is split roughly in the following way:
* `config.rs` - Defines the configuration object which is read from the configuration file on startup. The goal is to have a fully programmatic way to configure the daemon, so this is an interim state / backup mechanism while a more scalable solution is implemented. It would be nice to be able to read the state from something like etcd on startup and provide a gRPC API to modify the server configuration.
* `peer.rs` - Contains the `PeerStateMachine` object which implements a finite state machine modelling the state of a peer, and this is where all peer related events are processed (e.g. reading messages from TCPStream, parsing them, doing things with UPDATE messages etc).
* `rib_manager.rs` - Processes routes from peers and stores them in a tree-bitmap. Also exposes an API for streaming path updates to remote receivers (e.g. over gRPC).
* `route_server.rs` - Implements a gRPC service for dumping and streaming routes.
## Project Status
The current state of the code is a barely functional proof of concept. Rayhaan uses this daemon at home for his home network, AS210036, but apart from the basic functionality of connecting to a peer, announcing a static set of routes, and streaming received routes out via gRPC, it does not do much more.
There are an abundance of opportunities to contribute to the project, to make it fully standards compliant, and achieve the goals of full programmability. If you are interested please reach out to `rayhaan (at rayhaan (with ccTLD ch))`.
### 🔥 P0
* Implementation of `route_client` to be able to properly install routes into the kernel.
* Route filters for inbound routes.
* Forwarding routes to peers.
### 🕯️ P1
* Monitoring and status of sessions with peers (to be able to detect peer down etc)
* More comprehensive integration tests (to cover route acceptance / filters / propagation to RIB / forwarding / availability in the API etc).
### RFCs
The following are the RFCs that were consulted during the writing of the daemon so far, and there are certainly parts that are not yet covered, so this list will have to be revisited to check conformance / file bugs to track where the gaps are.
* RFC4271 - https://datatracker.ietf.org/doc/html/rfc4271
- BGP4 specification
* RFC4760 - https://datatracker.ietf.org/doc/html/rfc4760
- Multiprotocol extensions for BGP4
* RFC4693 - https://datatracker.ietf.org/doc/html/rfc6793
- 4 byte ASNs
#### RFC repository
Here's a list of interesting RFCs that we should look at eventually:
* RFC8212 - https://datatracker.ietf.org/doc/html/rfc8212
- Default External BGP (EBGP) Route Propagation Behavior without Policies
### TODOs
A very rough sketch of some major TODOs:
* Get `route_client` into a state where it actually works
* Support forwarding routes to other peers
* Implement filters comprehensively and design an API for setting them
* Implement programatic control plane via gRPC
* RPKI integration
* Design and implement monitoring interfaces
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.
>>>>>>> ferrix/master

11
bin/bgp/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
description = "A Border Gateway Protocol implementation"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "bgp"
version.workspace = true
[dependencies]
eyre.workspace = true
tokio.workspace = true

6
bin/bgp/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
use eyre::Result;
#[tokio::main]
pub async fn main() -> Result<()> {
Ok(())
}

18
crates/packet/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "bgp-packet"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
bitfield.workspace = true
bytes.workspace = true
eyre.workspace = true
nom.workspace = true
serde.workspace = true
serde_repr.workspace = true
thiserror.workspace = true

View File

@ -0,0 +1,70 @@
use std::fmt::Display;
use eyre::bail;
use serde_repr::{Deserialize_repr, Serialize_repr};
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum AddressFamilyId {
Ipv4 = 1,
Ipv6 = 2,
}
impl TryFrom<u16> for AddressFamilyId {
type Error = eyre::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Ok(match value {
x if x == Self::Ipv4 as u16 => Self::Ipv4,
x if x == Self::Ipv6 as u16 => Self::Ipv6,
other => bail!("Unknown AddressFamily: {}", other),
})
}
}
impl Display for AddressFamilyId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AddressFamilyId::Ipv4 => write!(f, "Ipv4"),
AddressFamilyId::Ipv6 => write!(f, "Ipv6"),
}
}
}
/// Represents a Subsequent Address Family Identifier.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum SubsequentAfi {
Unicast = 1,
Multicast = 2,
NlriWithMpls = 4,
MplsLabelledVpn = 128,
MulticastMplsVpn = 129,
}
impl TryFrom<u8> for SubsequentAfi {
type Error = eyre::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
x if x == Self::Unicast as u8 => Self::Unicast,
x if x == Self::Multicast as u8 => Self::Multicast,
x if x == Self::NlriWithMpls as u8 => Self::NlriWithMpls,
x if x == Self::MplsLabelledVpn as u8 => Self::MplsLabelledVpn,
x if x == Self::MulticastMplsVpn as u8 => Self::MulticastMplsVpn,
other => bail!("Unknown SubsequentAfi: {}", other),
})
}
}
impl Display for SubsequentAfi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubsequentAfi::Unicast => write!(f, "Unicast"),
SubsequentAfi::Multicast => write!(f, "Multicast"),
SubsequentAfi::NlriWithMpls => write!(f, "NlriWithMpls"),
SubsequentAfi::MplsLabelledVpn => write!(f, "MplsLabelledVpn"),
SubsequentAfi::MulticastMplsVpn => write!(f, "MulticastMplsVpn"),
}
}
}

149
crates/packet/src/errors.rs Normal file
View File

@ -0,0 +1,149 @@
use thiserror::Error;
#[derive(Debug, Error)]
#[repr(u8)]
pub enum BgpError {
#[error("Message header error")]
MsgHeader(MsgHeaderSubcode) = 1,
#[error("Open message error")]
OpenMsg(OpenMsgSubcode) = 2,
#[error("Update message error")]
UpdateMsg(UpdateMsgSubcode) = 3,
#[error("Hold timer expired")]
HoldTimer = 4,
#[error("Finite state machine error")]
FsmError(FsmSubcode) = 5,
#[error("Cease")]
Cease(CeaseSubcode) = 6,
#[error("Route refresh message error")]
RouteRefresh(RouteRefreshSubcode) = 7,
#[error("Send hold timer expired")]
SendHoldTimer = 8,
}
#[derive(Debug, Error)]
pub enum MsgHeaderSubcode {
#[error("Connection not synchronized")]
ConnNotSynchronized = 1,
#[error("Bad message length")]
BadMessageLength = 2,
#[error("Bad message type")]
BadMessagType = 3,
}
#[derive(Debug, Error)]
pub enum OpenMsgSubcode {
#[error("Unsupported BGP version number")]
UnsupportedVersion = 1,
#[error("Bad peer AS number")]
BadPeerAs = 2,
#[error("Bad BGP identifier")]
BadBgpId = 3,
#[error("Unsupported optional parameter")]
UnsupportedOptionalParam = 4,
#[error("Unacceptable hold time")]
UnacceptableHoldTime = 6,
#[error("Unsupported capability")]
UnsupportedCapability = 7,
#[error("Role mismatch")]
RoleMismatch = 8,
}
#[derive(Debug, Error)]
pub enum UpdateMsgSubcode {
#[error("Malformed attribute list")]
MalforedAttrs = 1,
#[error("Unrecognized well known attribute")]
UnrecognizedWellKnownAttr = 2,
#[error("Missing well known attribute")]
MissingWellKnown = 3,
#[error("Attribute flags error")]
AttributeFlags = 4,
#[error("Attribute length error")]
AttributeLength = 5,
#[error("Invalid origin")]
InvalidOrigin = 6,
#[error("Invalid next hop")]
InvalidNextHop = 8,
#[error("Optional attribute error")]
OptionalAttribute = 9,
#[error("Invalid network field")]
InvalidNetworkField = 10,
#[error("Malformed AS path")]
MalformedAsPath = 11,
}
#[derive(Debug, Error)]
pub enum FsmSubcode {
#[error("Received an unexpected message in OpenSent state")]
UnexpectedOpenSent = 1,
#[error("Received an unexpected message in OpenConfirm state")]
UnexpectedOpenConfirm = 2,
#[error("Received an unexpected message in Established state")]
UnexpectedEstablished = 3,
}
#[derive(Debug, Error)]
pub enum CeaseSubcode {
#[error("Maximum number of prefixes reached")]
MaxPrefixes = 1,
#[error("Administrative shutdown")]
AdminShutdown = 2,
#[error("Peer deconfigured")]
PeerDeconf = 3,
#[error("Administrative reset")]
AdminReset = 4,
#[error("Connection rejected")]
ConnRejected = 5,
#[error("Configuration change")]
ConfChange = 6,
#[error("Connection collision resolution")]
ConnCollisionResolution = 7,
#[error("Out of resources")]
OutOfResources = 8,
#[error("Hard reset")]
HardReset = 9,
#[error("BFD down")]
BfdDown = 10,
}
#[derive(Debug, Error)]
pub enum RouteRefreshSubcode {
#[error("Invalid message length")]
InvalidLength = 1,
}

View File

@ -0,0 +1,215 @@
use std::fmt::Display;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use bytes::{Buf, BufMut, BytesMut};
use eyre::{Result, bail};
use serde::{Deserialize, Serialize};
use crate::constants::AddressFamilyId;
use crate::parser::{ParserContext, ToWireError};
/// IpPrefix represents some IP address prefix, for a specific AddressFamilyId.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct IpPrefix {
pub address_family: AddressFamilyId,
pub prefix: Vec<u8>,
pub length: u8,
}
impl IpPrefix {
pub fn new(address_family: AddressFamilyId, prefix: Vec<u8>, length: u8) -> Result<Self> {
// Ensure that the prefix we are given contains the right number of bytes corresponding to the prefix length.
if prefix.len() < ((length + 7) / 8).into() {
bail!(
"Mismatched prefix {:?} for given prefix length: {}",
prefix,
length
);
}
Ok(Self {
address_family,
prefix,
length,
})
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<(), ToWireError> {
// Verify that there is enough space to write the IpPrefix.
if out.remaining() < (self.prefix.len() + 1) {
Err(ToWireError::OutBufferOverflow)?;
}
// Write length and prefix.
out.put_u8(self.length);
out.put(self.prefix.as_slice());
Ok(())
}
}
impl TryFrom<&str> for IpPrefix {
type Error = eyre::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let parts: Vec<&str> = value.split("/").collect();
if parts.len() != 2 {
bail!(
"Expected IpPrefix in format prefix/length but got: {}",
value
);
}
let length: u8 = u8::from_str_radix(parts[1], 10).map_err(eyre::Error::from)?;
let mut octets;
let afi: AddressFamilyId;
if parts[0].contains(":") {
afi = AddressFamilyId::Ipv6;
let addr: Ipv6Addr = Ipv6Addr::from_str(parts[0]).map_err(eyre::Error::from)?;
octets = addr.octets().to_vec();
} else if parts[0].contains(".") {
afi = AddressFamilyId::Ipv4;
let addr: Ipv4Addr = Ipv4Addr::from_str(parts[0]).map_err(eyre::Error::from)?;
octets = addr.octets().to_vec();
} else {
bail!("Could not figure out address type")
}
// Truncate the octets we have to the right number of bytes to match the prefix length..
if length % 8 == 0 {
// We can cleanly truncate the number of bytes since we are at a byte boundary.
octets.truncate((length / 8).into());
} else {
// We need to keep length % 8 bits of the last byte.
let num_bytes = (length / 8) + 1;
let mask = u8::MAX << (8 - (length % 8));
octets.truncate(num_bytes.into());
// Fix up the last byte.
let last_pos = octets.len() - 1;
octets[last_pos] &= mask;
}
Ok(IpPrefix {
address_family: afi,
prefix: octets,
length,
})
}
}
impl Display for IpPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.address_family {
AddressFamilyId::Ipv4 => {
// Pad the prefix with 0 bytes if it's less than 4 bytes long.
let bytes = &mut self.prefix.clone();
if bytes.len() < 4 {
bytes.extend(std::iter::repeat(0).take(4 - bytes.len()));
}
let four_bytes: [u8; 4] = bytes
.as_slice()
.try_into()
.map_err(|_| std::fmt::Error {})?;
let ipv4_addr = Ipv4Addr::from(four_bytes);
write!(f, "{}/{}", ipv4_addr, self.length)
}
AddressFamilyId::Ipv6 => {
let bytes = &mut self.prefix.clone();
if bytes.len() < 16 {
bytes.extend(std::iter::repeat(0).take(16 - bytes.len()));
}
let sixteen_bytes: [u8; 16] = bytes
.as_slice()
.try_into()
.map_err(|_| std::fmt::Error {})?;
let ipv6_addr = Ipv6Addr::from(sixteen_bytes);
write!(f, "{}/{}", ipv6_addr, self.length)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::constants::AddressFamilyId;
use super::IpPrefix;
macro_rules! verify_roundtrip {
($name:ident, $prefix_str:expr, $afi:expr, $bytes:expr, $length:expr) => {
#[test]
fn $name() {
let ip_prefix = IpPrefix::try_from($prefix_str).unwrap();
assert_eq!(ip_prefix.address_family, $afi);
assert_eq!(ip_prefix.prefix, $bytes);
assert_eq!(ip_prefix.length, $length);
let to_str: &str = &ip_prefix.to_string();
assert_eq!(IpPrefix::try_from(to_str).unwrap(), ip_prefix);
}
};
}
verify_roundtrip!(
verify_roundtrip_ipv4_24,
"10.1.2.0/24",
AddressFamilyId::Ipv4,
vec![10, 1, 2],
24
);
// Verify truncation.
verify_roundtrip!(
verify_roundtrip_ipv4_19,
"10.245.123.0/19",
AddressFamilyId::Ipv4,
vec![10, 245, 96],
19
);
// Verify truncation.
verify_roundtrip!(
verify_roundtrip_ipv4_3,
"192.168.1.0/3",
AddressFamilyId::Ipv4,
vec![192],
3
);
// Verify default address.
verify_roundtrip!(
verify_roundtrip_ipv4_0,
"0.0.0.0/0",
AddressFamilyId::Ipv4,
vec![],
0
);
verify_roundtrip!(
verify_roundtrip_ipv6_48,
"2001:db8:cafe::/48",
AddressFamilyId::Ipv6,
vec![32, 1, 13, 184, 202, 254],
48
);
// Verify truncation.
verify_roundtrip!(
verify_roundtrip_ipv6_32,
"2001:db8:cafe::/32",
AddressFamilyId::Ipv6,
vec![32, 1, 13, 184],
32
);
verify_roundtrip!(
verify_roundtrip_ipv6_0,
"::/0",
AddressFamilyId::Ipv6,
vec![],
0
);
}

5
crates/packet/src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod constants;
pub mod errors;
pub mod ip_prefix;
pub mod message;
pub mod parser;

View File

@ -0,0 +1,905 @@
use std::net::{Ipv4Addr, Ipv6Addr};
use bitfield::bitfield;
use bytes::BufMut;
use bytes::BytesMut;
use eyre::Context;
use eyre::Result;
use eyre::bail;
use eyre::eyre;
use nom::Err::Failure;
use nom::IResult;
use nom::Parser;
use nom::number::complete::be_u8;
use nom::number::complete::be_u16;
use nom::number::complete::be_u32;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::constants::AddressFamilyId;
use crate::constants::SubsequentAfi;
use crate::ip_prefix::IpPrefix;
use crate::parser::BgpParserError;
use crate::parser::ParserContext;
/// A sentinel AS number used to denote that the actual AS number is provided as
/// a 4 byte value in the capabilities instead.
pub const AS_TRANS: u16 = 23456;
/// Version number used in the BGP4 protocol.
pub const BGP4_VERSION: u8 = 4;
/// Message represents the top-level messages in the BGP protocol.
#[derive(Debug, Serialize, Deserialize)]
pub enum Message {
Open(OpenMessage),
Update(UpdateMessage),
Notification(NotificationMessage),
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,
/// BGP Route Refresh message (RFC 2918).
RouteRefresh = 5,
}
#[derive(Debug, Serialize, Deserialize)]
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.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateMessage {}
bitfield! {
pub struct PathAttributeFlags(u8);
impl Debug;
u8;
optional, set_optional: 0;
transitive, set_transitive: 1;
partial, set_partial: 2;
extended_length, set_extended_length: 3;
}
#[repr(u8)]
pub enum PathAttribute {
Origin(OriginPathAttribute) = 1,
ASPath(AsPathAttribute) = 2,
NextHop(NextHopPathAttribute) = 3,
MultiExitDisc(MultiExitDiscPathAttribute) = 4,
LocalPref(LocalPrefPathAttribute) = 5,
AtomicAggregate(AtomicAggregatePathAttribute) = 6,
Aggregator(AggregatorPathAttribute) = 7,
Communitites(CommunitiesPathAttribute) = 8,
MpReachNlri(MpReachNlriPathAttribute) = 14,
MpUnreachNlri(MpUnreachNlriPathAttribute) = 15,
ExtendedCommunities(ExtendedCommunitiesPathAttribute) = 16,
LargeCommunities(LargeCommunitiesPathAttribute) = 32,
UnknownPathAttribute {
flags: PathAttributeFlags,
type_code: u8,
payload: Vec<u8>,
},
}
impl PathAttribute {
/// The from_wire parser for `PathAttribute` consumes type and length which it uses to
/// determine how many bytes to take and pass down to the corresponding sub-parser.
pub fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, attr_flags) = be_u8(buf).map(|(buf, b)| (buf, PathAttributeFlags(b)))?;
let (buf, type_code) = be_u8(buf)?;
let (buf, length): (_, u16) = if attr_flags.extended_length() {
be_u16(buf)?
} else {
be_u8(buf).map(|(buf, b)| (buf, b as u16))?
};
todo!();
}
}
/// Origin path attribute is a mandatory attribute defined in RFC4271.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum OriginPathAttribute {
IGP = 0,
EGP = 1,
INCOMPLETE = 2,
}
impl TryFrom<u8> for OriginPathAttribute {
type Error = eyre::Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::IGP),
1 => Ok(Self::EGP),
2 => Ok(Self::INCOMPLETE),
other => bail!("Unexpected origin code {}", other),
}
}
}
/// 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::ASPath(AsPathAttribute {
segments: vec![segment],
})
}
pub 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).parse(buf)?
}
Some(false) => nom::multi::many_m_n(len as usize, len as usize, be_u16)
.parse(buf)
.map(|(buf, asns)| (buf, asns.iter().map(|asn| *asn as u32).collect()))?,
None => {
return Err(nom::Err::Failure(BgpParserError::CustomText(
"Context must set four_octet_asn before being used",
)));
}
};
Ok((
buf,
AsPathSegment {
ordered: typ == 2,
path: asns,
},
))
};
let (buf, segments) =
nom::multi::many0(|buf: &'a [u8]| parse_segment(ctx, buf)).parse(buf)?;
Ok((buf, Self { segments }))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
if ctx.four_octet_asn.is_none_or(|val| !val) {
bail!("AsPathAttribute can only be sent for four_octet_asn enabled peers");
}
for segment in &self.segments {
// Segment type.
out.put_u8(if segment.ordered { 2 } else { 1 });
// Segment AS length.
out.put_u16(
segment
.path
.len()
.try_into()
.wrap_err("AS Path length too long")?,
);
// AS numbers.
for asn in &segment.path {
out.put_u32(*asn);
}
}
Ok(())
}
pub fn wire_len(&self, ctx: &ParserContext) -> Result<u16> {
let mut counter = 0;
for segment in &self.segments {
counter += match ctx.four_octet_asn {
Some(true) => 2 + (4 * segment.path.len()),
Some(false) => 2 + (2 * segment.path.len()),
None => bail!("ParserContext needs four_octet_asn set"),
};
counter += 2 + (4 * segment.path.len());
}
Ok(counter as u16)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct NextHopPathAttribute(pub Ipv4Addr);
impl NextHopPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, ip_u32) = be_u32(buf)?;
Ok((buf, Self(Ipv4Addr::from(ip_u32))))
}
pub fn to_wire(&self, out: &mut BytesMut) -> Result<()> {
out.put_u32(self.0.into());
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u32> {
Ok(4)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct MultiExitDiscPathAttribute(pub u32);
impl MultiExitDiscPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, val) = be_u32(buf)?;
Ok((buf, Self(val)))
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u32(self.0);
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(4)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct LocalPrefPathAttribute(pub u32);
impl LocalPrefPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, val) = be_u32(buf)?;
Ok((buf, Self(val)))
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u32(self.0);
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(4)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct AtomicAggregatePathAttribute {}
impl AtomicAggregatePathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
Ok((buf, Self {}))
}
pub fn to_wire(&self, _: &ParserContext, _: &mut BytesMut) -> Result<()> {
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(0)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct AggregatorPathAttribute {
pub asn: u32,
pub ip: Ipv4Addr,
}
impl AggregatorPathAttribute {
pub fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
if ctx.four_octet_asn.is_none_or(|val| !val) {
return Err(nom::Err::Failure(BgpParserError::CustomText(
"AggregatorPathAttribute can only be parsed for four_octet_asn enabled peers",
)));
}
let (buf, asn) = be_u32(buf)?;
let (buf, ip) = be_u32(buf)?;
Ok((
buf,
Self {
asn,
ip: Ipv4Addr::from(ip),
},
))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
if ctx.four_octet_asn.is_none_or(|val| !val) {
bail!("AggregatorPathAttribute can only be sent for four_octet_asn enabled peers");
}
out.put_u32(self.asn);
out.put_u32(self.ip.into());
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(8)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct CommunitiesPathAttribute(Vec<(u16, u16)>);
impl CommunitiesPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, values) = nom::multi::many0(|i| (be_u16, be_u16).parse(i)).parse(buf)?;
Ok((buf, CommunitiesPathAttribute(values)))
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
for value in &self.0 {
out.put_u16(value.0);
out.put_u16(value.1);
}
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok((self.0.len() * 4) as u16)
}
}
/// Extended Communities as defined in https://www.rfc-editor.org/rfc/rfc4360.html.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct ExtendedCommunitiesPathAttribute {
pub extended_communities: Vec<ExtendedCommunity>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum ExtendedCommunity {
/// AS Specific Extended Community as specified in Section 3.1 of RFC4360.
AsSpecific {
typ: u8,
sub_typ: u8,
global_admin: u16,
local_admin: u32,
},
/// Ipv4 Address Specific Extended Community as specified in Section 3.2 of RFC4360.
Ipv4AddrSpecific {
typ: u8,
sub_typ: u8,
global_admin: u32,
local_admin: u16,
},
/// Opaque Extended Community as specified in Section 3.3 of RFC4360.
Opaque {
typ: u8,
sub_typ: u8,
value: [u8; 5],
},
/// Route Target Community as specified in Section 4 of RFC4360.
RouteTarget {
typ: u8,
sub_typ: u8,
global_admin: u32,
local_admin: u16,
},
/// Route Origin Community as specified in Section 5 of RFC4360.
RouteOrigin {
typ: u8,
sub_typ: u8,
global_admin: u16,
local_admin: u32,
},
}
impl ExtendedCommunity {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, typ) = be_u8(buf)?;
let (buf, sub_typ) = be_u8(buf)?;
let (buf, parsed) = match (typ, sub_typ) {
// Route Target Extended Community.
(0x00 | 0x01 | 0x02, 0x02) => {
let (buf, global_admin) = be_u32(buf)?;
let (buf, local_admin) = be_u16(buf)?;
(
buf,
Self::RouteTarget {
typ,
sub_typ,
global_admin,
local_admin,
},
)
}
// Route Origin Extended Community.
(0x00 | 0x01 | 0x02, 0x03) => {
let (buf, global_admin) = be_u16(buf)?;
let (buf, local_admin) = be_u32(buf)?;
(
buf,
Self::RouteOrigin {
typ,
sub_typ,
global_admin,
local_admin,
},
)
}
// AS specific Extended Community.
(0x00 | 0x40, _) => {
let (buf, global_admin) = be_u16(buf)?;
let (buf, local_admin) = be_u32(buf)?;
(
buf,
Self::AsSpecific {
typ,
sub_typ,
global_admin,
local_admin,
},
)
}
// IPv4 Address Specific Extended Community.
(0x01 | 0x41, _) => {
let (buf, global_admin) = be_u32(buf)?;
let (buf, local_admin) = be_u16(buf)?;
(
buf,
Self::Ipv4AddrSpecific {
typ,
sub_typ,
global_admin,
local_admin,
},
)
}
_ => {
let (buf, payload) = nom::bytes::take(5_usize).parse(buf)?;
let value: [u8; 5] = payload.try_into().map_err(|_| {
Failure(BgpParserError::CustomText(
"Expected exactly 5 bytes from the parser",
))
})?;
(
buf,
Self::Opaque {
typ,
sub_typ,
value,
},
)
}
};
return Ok((buf, parsed));
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
match self {
ExtendedCommunity::AsSpecific {
typ,
sub_typ,
global_admin,
local_admin,
} => {
out.put_u8(*typ);
out.put_u8(*sub_typ);
out.put_u16(*global_admin);
out.put_u32(*local_admin);
}
ExtendedCommunity::Ipv4AddrSpecific {
typ,
sub_typ,
global_admin,
local_admin,
} => {
out.put_u8(*typ);
out.put_u8(*sub_typ);
out.put_u32(*global_admin);
out.put_u16(*local_admin);
}
ExtendedCommunity::Opaque {
typ,
sub_typ,
value,
} => {
out.put_u8(*typ);
out.put_u8(*sub_typ);
out.put(&value[..]);
}
ExtendedCommunity::RouteTarget {
typ,
sub_typ,
global_admin,
local_admin,
} => {
out.put_u8(*typ);
out.put_u8(*sub_typ);
out.put_u32(*global_admin);
out.put_u16(*local_admin);
}
ExtendedCommunity::RouteOrigin {
typ,
sub_typ,
global_admin,
local_admin,
} => {
out.put_u8(*typ);
out.put_u8(*sub_typ);
out.put_u16(*global_admin);
out.put_u32(*local_admin);
}
}
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(8)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LargeCommunitiesPathAttribute {
pub communities: Vec<LargeCommunity>,
}
impl LargeCommunitiesPathAttribute {
pub fn from_wire<'a>(
ctx: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, communities) =
nom::multi::many1(|buf| LargeCommunity::from_wire(ctx, buf)).parse(buf)?;
Ok((buf, Self { communities }))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
for community in &self.communities {
community.to_wire(ctx, out)?;
}
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(12_u16 * self.communities.len() as u16)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LargeCommunity {
pub global_admin: u32,
pub data_1: u32,
pub data_2: u32,
}
impl LargeCommunity {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, global_admin) = be_u32(buf)?;
let (buf, data_1) = be_u32(buf)?;
let (buf, data_2) = be_u32(buf)?;
Ok((
buf,
Self {
global_admin,
data_1,
data_2,
},
))
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u32(self.global_admin);
out.put_u32(self.data_1);
out.put_u32(self.data_2);
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(12)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NlriNextHop {
/// Represents an IPv4 address nexthop.
Ipv4(Ipv4Addr),
/// Represents a global IPv6 nexthop.
Ipv6(Ipv6Addr),
/// Represents a IPv6 Link Local and global address pair nexthop.
Ipv6WithLl {
global: Ipv6Addr,
link_local: Ipv6Addr,
},
}
impl NlriNextHop {
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
match self {
NlriNextHop::Ipv4(ipv4_addr) => out.put(&ipv4_addr.octets()[..]),
NlriNextHop::Ipv6(ipv6_addr) => out.put(&ipv6_addr.octets()[..]),
NlriNextHop::Ipv6WithLl { global, link_local } => {
out.put(&global.octets()[..]);
out.put(&link_local.octets()[..])
}
}
Ok(())
}
pub fn wire_len(&self) -> u8 {
match self {
NlriNextHop::Ipv4(_) => 4,
NlriNextHop::Ipv6(_) => 16,
NlriNextHop::Ipv6WithLl { .. } => 32,
}
}
}
// parse_prefix is a helper function that implements an NLRI parser for the given AFI.
fn parse_prefix<'a>(
afi: AddressFamilyId,
buf: &'a [u8],
) -> IResult<&'a [u8], IpPrefix, BgpParserError<&'a [u8]>> {
let (buf, prefix_len) = be_u8(buf)?;
let byte_len = (prefix_len + 7) / 8;
let (buf, prefix_bytes) = nom::bytes::take(byte_len as usize).parse(buf)?;
let prefix = IpPrefix::new(afi, prefix_bytes.to_vec(), byte_len)
.map_err(|e| Failure(BgpParserError::Eyre(e)))?;
Ok((buf, prefix))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MpReachNlriPathAttribute {
pub afi: AddressFamilyId,
pub safi: SubsequentAfi,
/// Next hop address (either IPv4 or IPv6 for now).
pub next_hop: NlriNextHop,
pub prefixes: Vec<IpPrefix>,
}
impl MpReachNlriPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, raw_afi) = be_u16(buf)?;
let afi =
AddressFamilyId::try_from(raw_afi).map_err(|e| Failure(BgpParserError::Eyre(e)))?;
let (buf, raw_safi) = be_u8(buf)?;
let safi =
SubsequentAfi::try_from(raw_safi).map_err(|e| Failure(BgpParserError::Eyre(e)))?;
let (buf, nh_len) = be_u8(buf)?;
let (buf, next_hop, prefixes) = match afi {
AddressFamilyId::Ipv4 => {
// Read the length of the nexthop which should equal 4.
if nh_len != 4 {
return Err(Failure(BgpParserError::Eyre(eyre!(
"Got nexthop address length {} when expected 4 for IPv4 AFI",
nh_len
))));
}
// Read the nexthop address which should now be an IPv4 address.
let (buf, nh_bytes) = be_u32(buf)?;
let next_hop = NlriNextHop::Ipv4(Ipv4Addr::from(nh_bytes));
let (buf, prefixes) =
nom::multi::many0(|buf| parse_prefix(AddressFamilyId::Ipv4, buf)).parse(buf)?;
(buf, next_hop, prefixes)
}
AddressFamilyId::Ipv6 => {
// https://datatracker.ietf.org/doc/html/rfc2545 defines that the nexthop address may be 16 or 32 bytes long.
let (buf, nh_bytes) = nom::bytes::take(nh_len as usize).parse(buf)?;
let nexthop = match nh_bytes.len() {
16 => {
// unwrap should never fire since we have explicitly checked the length.
let slice: [u8; 16] = nh_bytes.try_into().unwrap();
NlriNextHop::Ipv6(Ipv6Addr::from(slice))
}
32 => {
// unwrap should never fire since we have explicitly checked the length.
let slice: [u8; 32] = nh_bytes.try_into().unwrap();
let link_local_bytes: [u8; 16] = slice[0..16].try_into().unwrap();
let link_local = Ipv6Addr::from(link_local_bytes);
let global_bytes: [u8; 16] = slice[16..32].try_into().unwrap();
let global = Ipv6Addr::from(global_bytes);
NlriNextHop::Ipv6WithLl { global, link_local }
}
_ => {
return Err(Failure(BgpParserError::Eyre(eyre!(
"Mismatched IPv6 nexthop length, got {}, want 16 or 32",
nh_bytes.len()
))));
}
};
let (buf, prefixes) =
nom::multi::many0(|buf| parse_prefix(AddressFamilyId::Ipv6, buf)).parse(buf)?;
(buf, nexthop, prefixes)
}
};
Ok((
buf,
Self {
afi,
safi,
next_hop,
prefixes,
},
))
}
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u16(self.afi as u16);
out.put_u8(self.safi as u8);
out.put_u8(self.next_hop.wire_len());
self.next_hop.to_wire(ctx, out)?;
for prefix in &self.prefixes {
out.put_u8(prefix.length);
out.put(&prefix.prefix[..]);
}
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(4_u16
+ self.next_hop.wire_len() as u16
+ self
.prefixes
.iter()
.map(|p| 1 + p.prefix.len() as u16)
.sum::<u16>())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MpUnreachNlriPathAttribute {
pub afi: AddressFamilyId,
pub safi: SubsequentAfi,
pub prefixes: Vec<IpPrefix>,
}
impl MpUnreachNlriPathAttribute {
pub fn from_wire<'a>(
_: &ParserContext,
buf: &'a [u8],
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
let (buf, raw_afi) = be_u16(buf)?;
let afi =
AddressFamilyId::try_from(raw_afi).map_err(|e| Failure(BgpParserError::Eyre(e)))?;
let (buf, raw_safi) = be_u8(buf)?;
let safi =
SubsequentAfi::try_from(raw_safi).map_err(|e| Failure(BgpParserError::Eyre(e)))?;
let (buf, prefixes) = nom::multi::many0(|buf| parse_prefix(afi, buf)).parse(buf)?;
Ok((
buf,
MpUnreachNlriPathAttribute {
afi,
safi,
prefixes,
},
))
}
pub fn to_wire(&self, _: &ParserContext, out: &mut BytesMut) -> Result<()> {
out.put_u16(self.afi as u16);
out.put_u8(self.safi as u8);
for prefix in &self.prefixes {
out.put_u8(prefix.length);
out.put(&prefix.prefix[..]);
}
Ok(())
}
pub fn wire_len(&self, _: &ParserContext) -> Result<u16> {
Ok(3 + self
.prefixes
.iter()
.map(|p| 1 + p.prefix.len() as u16)
.sum::<u16>())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationMessage {}

View File

@ -0,0 +1,49 @@
use std::fmt::Display;
use nom::error::{ErrorKind, ParseError};
use crate::constants::AddressFamilyId;
#[derive(Debug, Default)]
pub struct ParserContext {
/// Whether thi parser is being run with a peer that is RFC6793 compliant.
pub four_octet_asn: Option<bool>,
/// Which address family should be parsed by default with this parser.
pub address_family: Option<AddressFamilyId>,
}
#[derive(Debug)]
pub enum ToWireError {
/// There was not enough space in the output buffer to serialize the data into.
OutBufferOverflow,
/// Another error.
Other(eyre::Error),
}
impl Display for ToWireError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToWireError::OutBufferOverflow => write!(f, "OutBufferOverflow"),
ToWireError::Other(report) => report.fmt(f),
}
}
}
impl std::error::Error for ToWireError {}
// Custom error type for the parser.
#[derive(Debug)]
pub enum BgpParserError<I> {
CustomText(&'static str),
Eyre(eyre::ErrReport),
Nom(I, ErrorKind),
}
impl<I> ParseError<I> for BgpParserError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
BgpParserError::Nom(input, kind)
}
fn append(_: I, _: ErrorKind, other: Self) -> Self {
other
}
}