Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
455
Cargo.lock
generated
Normal file
455
Cargo.lock
generated
Normal 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 = "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 = "ferrix-bgp-packet"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bitfield",
|
||||||
|
"bytes",
|
||||||
|
"eyre",
|
||||||
|
"nom",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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"
|
||||||
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[workspace]
|
||||||
|
default-members = ["bin/bgp"]
|
||||||
|
members = ["bin/*", "crates/*"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
authors = ["Rayhaan Jaufeerally <rayhaan+git@rayhaan.ch>"]
|
||||||
|
description = "A Border Gateway Protocol implementation"
|
||||||
|
edition = "2024"
|
||||||
|
license = "Apache 2"
|
||||||
|
name = "bgp"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
# --- Our crates ---
|
||||||
|
bgp-packet = { path = "crates/packet" }
|
||||||
|
|
||||||
|
# --- 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"] }
|
||||||
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# BGP
|
||||||
|
|
||||||
|
This project is an implementation of the Border Gateway Protocol and associated functionality required to run an Autonomous System.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Current status.
|
||||||
|
|
||||||
|
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.
|
||||||
8
bin/bgp/Cargo.toml
Normal file
8
bin/bgp/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
edition.workspace = true
|
||||||
|
name = "bgp"
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eyre.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
6
bin/bgp/src/main.rs
Normal file
6
bin/bgp/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use eyre::Result;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
13
crates/packet/Cargo.toml
Normal file
13
crates/packet/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
edition = "2024"
|
||||||
|
name = "ferrix-bgp-packet"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitfield.workspace = true
|
||||||
|
bytes.workspace = true
|
||||||
|
eyre.workspace = true
|
||||||
|
nom.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_repr.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
70
crates/packet/src/constants.rs
Normal file
70
crates/packet/src/constants.rs
Normal 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
149
crates/packet/src/errors.rs
Normal 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,
|
||||||
|
}
|
||||||
215
crates/packet/src/ip_prefix.rs
Normal file
215
crates/packet/src/ip_prefix.rs
Normal 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
5
crates/packet/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod constants;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod ip_prefix;
|
||||||
|
pub mod message;
|
||||||
|
pub mod parser;
|
||||||
905
crates/packet/src/message.rs
Normal file
905
crates/packet/src/message.rs
Normal 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 {}
|
||||||
49
crates/packet/src/parser.rs
Normal file
49
crates/packet/src/parser.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user