From d00f47d00469f1b6a7cec5c9f881d86f0f2796b8 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Wed, 31 Jan 2024 00:59:42 -0800 Subject: [PATCH] Initial commit. Kinda does something. --- .gitignore | 1 + Cargo.lock | 559 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 + src/main.rs | 664 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1237 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..27fc342 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,559 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff943d68b88d0b87a6e0d58615e8fa07f9fd5a1319fa0a72efc1f62275c79a7" +dependencies = [ + "nom", + "nom-derive-impl", + "rustversion", +] + +[[package]] +name = "nom-derive-impl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b9a93a84b0d3ec3e70e02d332dc33ac6dfac9cde63e17fcb77172dededa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-bitfield" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77336166f33f8d15afb6d06cec94829b764988876363b1751bbde5b06fef6d4" +dependencies = [ + "proc-bitfield-macros", + "static_assertions", +] + +[[package]] +name = "proc-bitfield-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90d78755a79b5711d5e2140c36e10dac2f06f555b1a9995141b54f72ae129515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rust-bfd" +version = "0.1.0" +dependencies = [ + "byteorder", + "nom", + "nom-derive", + "proc-bitfield", + "rand", + "tokio", + "tokio-timer", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils", + "futures", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-timer" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils", + "futures", + "slab", + "tokio-executor", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..881c193 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust-bfd" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +nom = "7.1.3" +nom-derive = "0.10.1" +proc-bitfield = "0.3.1" +rand = "0.8.5" +tokio = { version = "1.35.1", features = ["net", "full"] } +tokio-timer = "0.2.13" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d4b32b3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,664 @@ +use std::{ + collections::HashMap, error::Error, fmt::Display, fs::read, io::Cursor, net::{IpAddr, SocketAddr}, str::FromStr, sync::Arc +}; + +use nom::{bytes::complete::take, multi::many_m_n, number::complete::be_u8, IResult}; +use nom_derive::{NomBE, Parse}; +use proc_bitfield::*; +use rand::prelude::*; +use tokio::task; +use tokio::time; +use tokio::{io, join, task::JoinHandle}; +use tokio::{net::UdpSocket, sync::RwLock}; +use tokio::{sync::mpsc, time::Instant}; +use byteorder::{BigEndian, WriteBytesExt}; + +const CONTROL_PORT: u16 = 3784; +const ECHO_PORT: u16 = 3785; + +#[repr(u8)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy)] +pub enum BfdDiagnostic { + None = 0, + TimeExpired = 1, + EchoFailed = 2, + NeighborDown = 3, + FwdPlaneReset = 4, + PathDown = 5, + ConcatPathDown = 6, + AdminDown = 7, + RevConcatPathDown = 8, + Reserved, +} +#[repr(u8)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Default, Clone, Copy)] +pub enum BfdState { + AdminDown = 0, + #[default] + Down = 1, + Init = 2, + Up = 3, +} +impl Display for BfdState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::AdminDown => "AdminDown", + Self::Down => "Down", + Self::Init => "Init", + Self::Up => "Up", + }) + } +} + +#[repr(u8)] +#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy)] +pub enum BfdAuthType { + None = 0, + SimplePassword = 1, + KeyedMD5 = 2, + MetKeyedMD5 = 3, + KeyedSHA1 = 4, + MetKeyedSHA1 = 5, + Reserved, +} + +#[derive(Debug)] +pub enum BfdError { + // field, value + InvalidFieldValue(&'static str, &'static str), +} +impl Display for BfdError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidFieldValue(field, value) => { + write!(f, "invalid value `{}` for field `{}`", field, value) + } + } + } +} +impl Error for BfdError {} + +#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy)] +pub struct BfdDiscriminator(u32); +impl Display for BfdDiscriminator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub struct BfdInterval(u32); +impl From for time::Duration { + fn from(value: BfdInterval) -> Self { + time::Duration::from_micros(value.0 as u64) + } +} + +bitfield! { + #[derive(NomBE)] + pub struct BfdFlags(pub u32): Debug { + pub vers: u8 @ 29..=31, + pub diag: u8 [try_get BfdDiagnostic] @ 24..=28, + pub state: u8 [try_get BfdState] @ 22..=23, + pub poll: bool @ 21, + pub final_: bool @ 20, + pub cpi: bool @ 19, + pub auth_present: bool @ 18, + pub demand: bool @ 17, + pub multipoint: bool @ 16, + pub detect_mult: u8 @ 8..=15, + pub length: u8 @ 0..=7 + } +} + +#[derive(Debug)] +pub struct BfdAuthSimplePassword(Vec); +impl<'a> Parse<&'a [u8]> for BfdAuthSimplePassword { + fn parse(i: &'a [u8]) -> IResult<&'a [u8], Self, nom::error::Error<&'a [u8]>> { + let (i, res) = many_m_n(1, 16, be_u8)(i)?; + Ok((i, Self(res))) + } +} + +#[derive(Debug, NomBE)] +pub struct BfdAuthKeyedMD5 { + key_id: u8, + _reserved: u8, + seq: u32, + digest: [u8; 16], +} + +#[derive(Debug, NomBE)] +pub struct BfdAuthKeyedSHA1 { + key_id: u8, + _reserved: u8, + seq: u32, + hash: [u8; 20], +} + +#[derive(Debug, NomBE)] +#[nom(Selector = "BfdAuthType", Complete)] +pub enum BfdAuthData { + #[nom(Selector = "BfdAuthType::SimplePassword")] + SimplePassword(BfdAuthSimplePassword), + #[nom(Selector = "BfdAuthType::KeyedMD5")] + KeyedMD5(BfdAuthKeyedMD5), + #[nom(Selector = "BfdAuthType::MetKeyedMD5")] + MetKeyedMD5(BfdAuthKeyedMD5), + #[nom(Selector = "BfdAuthType::KeyedSHA1")] + KeyedSHA1(BfdAuthKeyedSHA1), + #[nom(Selector = "BfdAuthType::MetKeyedSHA1")] + MetKeyedSHA1(BfdAuthKeyedSHA1), +} + +impl BfdAuthData { + fn parse_be_with_length( + i: &[u8], + auth_type: BfdAuthType, + auth_len: u8, + ) -> IResult<&[u8], Self> { + let (new_i, data) = take(auth_len)(i)?; + let (_leftovers, retval) = BfdAuthData::parse_be(data, auth_type)?; + Ok((new_i, retval)) + } +} + +#[derive(Debug, NomBE)] +pub struct BfdAuth { + auth_type: BfdAuthType, + auth_len: u8, + #[nom(Parse = "{ |i| BfdAuthData::parse_be_with_length(i, auth_type, auth_len) }")] + auth_data: BfdAuthData, +} + +#[derive(Debug, NomBE)] +pub struct BfdPacket { + flags: BfdFlags, + my_disc: BfdDiscriminator, + your_disc: BfdDiscriminator, + desired_min_tx: BfdInterval, + required_min_rx: BfdInterval, + required_min_echo_rx: BfdInterval, + #[nom(Cond = "flags.auth_present()")] + auth: Option, +} + +impl BfdPacket { + fn serialize(&self) -> Result, std::io::Error> { + // TODO: serialize auth + let buf = [0u8; 24]; + let mut wtr = Cursor::new(buf); + wtr.write_u32::(self.flags.0)?; + wtr.write_u32::(self.my_disc.0)?; + wtr.write_u32::(self.your_disc.0)?; + wtr.write_u32::(self.desired_min_tx.0)?; + wtr.write_u32::(self.required_min_rx.0)?; + wtr.write_u32::(self.required_min_echo_rx.0)?; + + Ok(Box::new(wtr.into_inner())) + } +} + +#[derive(Debug, Clone)] +struct BfdSessionState { + control_sock: Arc, + peer_addr: IpAddr, + session_state: BfdState, + remote_session_state: BfdState, + local_discr: BfdDiscriminator, + remote_discr: BfdDiscriminator, + local_diag: BfdDiagnostic, + desired_min_tx_interval: BfdInterval, + required_min_rx_interval: BfdInterval, + remote_min_rx_interval: BfdInterval, + demand_mode: bool, + remote_demand_mode: bool, + detect_mult: u8, + auth_type: BfdAuthType, + rcv_auth_seq: u32, + xmit_auth_seq: u32, + auth_seq_known: bool, + + periodic_cmd_channel: mpsc::Sender, + detection_time: time::Duration, + poll_mode: bool, +} + +struct BfdSession { + state: Arc>, +} + +enum PeriodicControlCommand { + Stop, + Start, + Quit, + SetMinInterval(BfdInterval), +} + +enum SessionControlCommand { + RxPacket(Vec), + Quit, +} + +impl BfdSession { + async fn new( + local_addr: IpAddr, + remote_addr: IpAddr, + ) -> Result> { + let mut rng = rand::thread_rng(); + + //TODO: select a random unused port instead of pure random + let source_port: u16 = rng.gen_range(49152..=65535); + let control_sock = UdpSocket::bind(SocketAddr::new(local_addr, source_port)).await?; + // control_sock + // .connect(SocketAddr::new(remote_addr, CONTROL_PORT)) + // .await?; + + // Incoming packets will come over the channel from the mux, since they don't send to the reciprocal port + + Ok(Self { + state: Arc::new(RwLock::new(BfdSessionState { + control_sock: Arc::new(control_sock), + peer_addr: remote_addr, + session_state: BfdState::default(), + remote_session_state: BfdState::default(), + local_discr: BfdDiscriminator(rng.gen()), + remote_discr: BfdDiscriminator(0), + local_diag: BfdDiagnostic::None, + desired_min_tx_interval: BfdInterval(1_000_000), + required_min_rx_interval: BfdInterval(300_000), + remote_min_rx_interval: BfdInterval(1), + demand_mode: false, + remote_demand_mode: false, + detect_mult: 3, + auth_type: BfdAuthType::None, + rcv_auth_seq: 0, + xmit_auth_seq: rng.gen(), + auth_seq_known: false, + periodic_cmd_channel: mpsc::channel(1).0, + detection_time: time::Duration::ZERO, + poll_mode: false, + })), + }) + } + + async fn spawn_control_thread( + self: Arc, + mut rx: mpsc::Receiver, + ) -> JoinHandle<()> { + task::spawn(async move { + while let Some(cmd) = rx.recv().await { + match cmd { + SessionControlCommand::Quit => return, + SessionControlCommand::RxPacket(buf) => { + if let Ok((_leftover, packet)) = BfdPacket::parse(buf.as_slice()) { + println!("packet: {:?}", packet); + self.clone().receive_control_packet(&packet).await + } else { + eprintln!("Failed to parse packet"); + } + } + } + } + }) + } + + async fn transmit_periodic_packet(self: Arc) { + let read_guard = self.state.read().await; + let packet = BfdPacket { + flags: BfdFlags(0).with_vers(1).with_diag(read_guard.local_diag.into()).with_state(read_guard.session_state.into()).with_poll(read_guard.poll_mode).with_cpi(true).with_demand(read_guard.session_state == BfdState::Up && read_guard.remote_session_state == BfdState::Up).with_detect_mult(read_guard.detect_mult).with_length(24), + my_disc: read_guard.local_discr, + your_disc: read_guard.remote_discr, + desired_min_tx: read_guard.desired_min_tx_interval, + required_min_rx: read_guard.required_min_rx_interval, + required_min_echo_rx: BfdInterval(0), + auth: None + }; + let socket = read_guard.control_sock.clone(); + let dest = read_guard.peer_addr; + drop(read_guard); + socket.send_to(packet.serialize().unwrap().as_ref(), SocketAddr::new(dest, CONTROL_PORT)).await.unwrap(); + } + + async fn spawn_periodic_thread( + self: Arc, + mut rx: mpsc::Receiver, + interval: BfdInterval, + ) -> JoinHandle<()> { + task::spawn(async move { + let mut running = true; + let base_interval = time::Duration::from_micros(interval.0 as u64 * 3 / 4); + let mut clock = time::interval(base_interval); + 'MAIN: loop { + if running { + // Get and action all pending commands then wait for interval to tick + while let Ok(cmd) = rx.try_recv() { + match cmd { + PeriodicControlCommand::Quit => return, + PeriodicControlCommand::Stop => { + running = false; + continue 'MAIN; + } + PeriodicControlCommand::Start => running = true, + PeriodicControlCommand::SetMinInterval(i) => { + running = true; + let base_interval = time::Duration::from_micros(i.0 as u64 * 3 / 4); + clock = time::interval_at( + time::Instant::now() + base_interval.into(), + base_interval.into(), + ); + } + } + } + // The periodic transmission of BFD Control packets MUST be jittered on a per-packet basis by up to + // 25%, that is, the interval MUST be reduced by a random value of 0 to 25% + // + // We do the equivalent inverse, we wait 75%, then add an additional 0-25%. + let jitter = time::Duration::from_micros( + rand::thread_rng() + .gen_range(0..clock.period().as_micros() / 3) + .try_into() + .unwrap(), + ); + clock.tick().await; + time::sleep(jitter).await; + self.clone().transmit_periodic_packet().await; + // + } else { + // Instead we block on incoming commands + if let Some(cmd) = rx.recv().await { + match cmd { + PeriodicControlCommand::Start => { + running = true; + clock.reset_after(clock.period()) + } + PeriodicControlCommand::SetMinInterval(i) => { + running = true; + let base_interval = time::Duration::from_micros(i.0 as u64 * 3 / 4); + clock = time::interval_at( + time::Instant::now() + base_interval.into(), + base_interval.into(), + ); + } + _ => {} // Other commands don't mutate state or start the clock + } + } + } + } + }) + } + + // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.6 + async fn receive_control_packet(self: Arc, p: &BfdPacket) { + let received_state = match p.flags.state() { + Err(_) => { + eprintln!("Invalid state, discarding"); + return; + } + Ok(v) => v, + }; + // If the version number is not correct (1), the packet MUST be discarded. + if p.flags.vers() != 1 { + eprintln!("Invalid version {}, discarding", p.flags.vers()); + return; + } + // If the Length field is less than the minimum correct value (24 if the A bit is clear, or 26 if the A bit is + // set), the packet MUST be discarded. + if p.flags.length() < 24 || (p.flags.length() < 26 && p.flags.auth_present()) { + eprintln!("Invalid packet length {}, discarding", p.flags.length()); + return; + } + // TODO: If the Length field is greater than the payload of the encapsulating protocol, the packet MUST be + // discarded. + + // If the Detect Mult field is zero, the packet MUST be discarded. + if p.flags.detect_mult() == 0 { + eprintln!("Invalid detect mult {}, discarding", p.flags.detect_mult()); + return; + } + + //If the Multipoint (M) bit is nonzero, the packet MUST be discarded. + if p.flags.multipoint() { + eprintln!("Invalid multipoint enabled, discarding"); + return; + } + + // If the My Discriminator field is zero, the packet MUST be discarded. + if p.my_disc == BfdDiscriminator(0) { + eprintln!("Invalid my discriminator {:?}, discarding", p.my_disc); + return; + } + + let state_read = self.state.read().await; + + // If the Your Discriminator field is nonzero, it MUST be used to select the session with which this BFD packet + // is associated. If no session is found, the packet MUST be discarded. + // + // TODO: actually implement multiplexing + if p.your_disc != BfdDiscriminator(0) && p.your_disc != state_read.local_discr { + eprintln!( + "Received unexpected discriminator {:?}, discarding", + p.your_disc + ); + return; + } + + // If the Your Discriminator field is zero and the State field is not Down or AdminDown, the packet MUST be + // discarded. + if p.your_disc == BfdDiscriminator(0) + && (received_state != BfdState::Down && received_state != BfdState::AdminDown) + { + eprintln!( + "Got packet with zero discriminator and invalid state {:?}, discarding", + received_state + ); + return; + } + + // If the A bit is set and no authentication is in use (bfd.AuthType is zero), the packet MUST be discarded. + if p.flags.auth_present() && state_read.auth_type == BfdAuthType::None { + eprintln!("Got packet with auth enabled when we disagree, discarding"); + return; + } + + // If the A bit is clear and authentication is in use (bfd.AuthType is nonzero), the packet MUST be discarded. + if !p.flags.auth_present() && state_read.auth_type != BfdAuthType::None { + eprintln!("Got packet without auth when we expect it, discarding"); + return; + } + + // If the A bit is set, the packet MUST be authenticated under the rules of section 6.7, based on the + // authentication type in use (bfd.AuthType). This may cause the packet to be discarded. + if p.flags.auth_present() { + unimplemented!("Authentication is not implemented"); + } + + drop(state_read); + let mut state_write = self.state.write().await; + // Set bfd.RemoteDiscr to the value of My Discriminator. + state_write.remote_discr = p.my_disc; + // Set bfd.RemoteState to the value of the State (Sta) field. + state_write.remote_session_state = received_state; + // Set bfd.RemoteDemandMode to the value of the Demand (D) bit. + state_write.remote_demand_mode = p.flags.demand(); + // Set bfd.RemoteMinRxInterval to the value of Required Min RX Interval. + state_write.remote_min_rx_interval = p.required_min_rx; + drop(state_write); + // If the Required Min Echo RX Interval field is zero, the transmission of Echo packets, if any, MUST cease. + if p.required_min_echo_rx == BfdInterval(0) { + // TODO: implement echo thread + } + // If a Poll Sequence is being transmitted by the local system and the Final (F) bit in the received packet is + // set, the Poll Sequence MUST be terminated. + // + // TODO: poll stuff + + // Update the transmit interval as described in section 6.8.2. + self.clone().update_transmit_interval().await; + + // Update the Detection Time as described in section 6.8.4. + self.clone().update_detection_time(&p).await; + + // There's not much actual work to do here so just hold a write lock for all of it + let mut state_write = self.state.write().await; + // If bfd.SessionState is AdminDown + // Discard the packet + if state_write.session_state == BfdState::AdminDown { + return; + } + + // If received state is AdminDown + if received_state == BfdState::AdminDown { + // If bfd.SessionState is not Down + if state_write.session_state != BfdState::Down { + // Set bfd.LocalDiag to 3 (Neighbor signaled session down) + state_write.local_diag = BfdDiagnostic::NeighborDown; + // Set bfd.SessionState to Down + state_write.session_state = BfdState::Down; + } + } else { + // If bfd.SessionState ... + match state_write.session_state { + BfdState::Down => { + // If received State is Down + if received_state == BfdState::Down { + // Set bfd.SessionState to Init + state_write.session_state = BfdState::Init; + // Else if received State is Init + } else if received_state == BfdState::Init { + // Set bfd.SessionState to Up + state_write.session_state = BfdState::Up; + } + } + BfdState::Init => { + // If received State is Init or Up + if received_state == BfdState::Init || received_state == BfdState::Up { + // Set bfd.SessionState to Up + state_write.session_state = BfdState::Up; + } + } + BfdState::Up => { + // If received State is Down + if received_state == BfdState::Down { + // Set bfd.LocalDiag to 3 (Neighbor signaled session down) + state_write.local_diag = BfdDiagnostic::NeighborDown; + // Set bfd.SessionState to Down + state_write.session_state = BfdState::Down; + } + } + BfdState::AdminDown => unreachable!("unexpected AdminDown"), // AdminDown is discarded earlier + } + + drop(state_write); + + // Check to see if Demand mode should become active or not (see section 6.6). + + // If bfd.RemoteDemandMode is 1, bfd.SessionState is Up, and bfd.RemoteSessionState is Up, Demand mode is + // active on the remote system and the local system MUST cease the periodic transmission of BFD Control + // packets (see section 6.8.7). + + // TODO: implement ceasing/restarting of control packets due to demand mode + if p.flags.demand() { + eprintln!("WARNING: Demand mode requested but not implemented"); + } + + // If the Poll (P) bit is set, send a BFD Control packet to the remote system with the Poll (P) bit clear, + // and the Final (F) bit set (see section 6.8.7). + if p.flags.poll() { + + // TODO: Implement sending stuff + } + // If the packet was not discarded, it has been received for purposes of the Detection Time expiration rules + // in section 6.8.4. + } + } + + // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.2 + async fn update_transmit_interval(self: Arc) { + let state = self.state.read().await; + state + .periodic_cmd_channel + .send(PeriodicControlCommand::SetMinInterval(std::cmp::max( + state.desired_min_tx_interval, + state.remote_min_rx_interval, + ))) + .await + .unwrap() + } + + // https://datatracker.ietf.org/doc/html/rfc5880#section-6.8.4 + async fn update_detection_time(self: Arc, p: &BfdPacket) { + let mut state = self.state.write().await; + state.detection_time = if !state.demand_mode { + time::Duration::from_micros( + p.flags.detect_mult() as u64 + * std::cmp::max(state.required_min_rx_interval, p.desired_min_tx).0 as u64, + ) + } else { + time::Duration::from_micros( + state.detect_mult as u64 + * std::cmp::max(state.desired_min_tx_interval, state.remote_min_rx_interval).0 + as u64, + ) + } + } + + async fn run(self: Arc, rx: mpsc::Receiver) { + let (cmd_tx, cmd_rx) = mpsc::channel(32); + self.state.write().await.periodic_cmd_channel = cmd_tx; + + let rxt = self.clone().spawn_control_thread(rx).await; + let pxt = self + .clone() + .spawn_periodic_thread(cmd_rx, self.state.read().await.desired_min_tx_interval) + .await; + join!(rxt, pxt).0.unwrap(); + } +} + +#[tokio::main] +async fn main() -> io::Result<()> { + let local = SocketAddr::from_str("192.168.65.224:3784").unwrap(); + let peers = vec![SocketAddr::from_str("127.0.0.1:3784").unwrap()]; + let mut sessions = HashMap::new(); + + for peer in peers { + let (tx, rx) = mpsc::channel(32); + let session = Arc::new(BfdSession::new(local.ip(), peer.ip()).await.unwrap()); + let handle = task::spawn(session.clone().run(rx)); + + sessions.insert((local.ip(), peer.ip()), (tx, session, handle)); + } + + let control_sock = Arc::new(UdpSocket::bind(local).await?); + // If BFD authentication is not in use on a session, all BFD Control packets for the session MUST be sent with a + // Time to Live (TTL) or Hop Limit value of 255. + control_sock.set_ttl(255).unwrap(); + let echo_socket = Arc::new(UdpSocket::bind(SocketAddr::new(local.ip(), ECHO_PORT)).await?); + + let rx_thread = task::spawn(async move { + let mut buf = [0; 1024]; + loop { + // TODO: All received BFD Control packets that are demultiplexed to the session MUST be discarded if the + // received TTL or Hop Limit is not equal to 255. + let (len, addr) = control_sock.recv_from(&mut buf).await.unwrap(); // TODO: fallibility? + println!("{:?} bytes received from {:?}", len, addr); + if let Some(session) = + sessions.get(&(control_sock.local_addr().unwrap().ip(), addr.ip())) + { + println!("matched to session"); + session + .0 + .send(SessionControlCommand::RxPacket(buf[0..len].to_vec())) + .await + .unwrap(); + } + } + }); + + tokio::join!(rx_thread) + .0 + .expect("Unable to join on the receive thread"); + + Ok(()) +}