rust-bfd/src/bfd.rs
Keenan Tims 953f37d1c7
Lots of refactoring and improvements. Script hooks.
Can now establish and hold a session with BIRD, successfully detect Down
state, and call scripts on state change via an event dispatch engine.

Can't maintain a session with VyOS. I think it is calculating the timers
incorrectly, but don't trust my own implementation of course...
2024-10-07 23:56:33 -07:00

254 lines
6.9 KiB
Rust

use std::{ffi::{OsStr, OsString}, fmt::{self, Display}, io::Cursor, ops::{Div, Mul}, time};
use bytemuck::NoUninit;
use byteorder::{BigEndian, WriteBytesExt};
use nom::{bytes::complete::take, multi::many_m_n, number::complete::be_u8, IResult};
use nom_derive::{NomBE, Parse};
use proc_bitfield::*;
#[repr(u8)]
#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)]
pub enum BfdDiagnostic {
None = 0,
TimeExpired = 1,
EchoFailed = 2,
NeighborDown = 3,
FwdPlaneReset = 4,
PathDown = 5,
ConcatPathDown = 6,
AdminDown = 7,
RevConcatPathDown = 8,
Reserved,
}
impl Display for BfdDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::None => "None",
Self::TimeExpired => "TimeExpired",
Self::EchoFailed => "EchoFailed",
Self::NeighborDown => "NeighborDown",
Self::FwdPlaneReset => "FwdPlaneReset",
Self::PathDown => "PathDown",
Self::ConcatPathDown => "ConcatPathDown",
Self::AdminDown => "AdminDown",
Self::RevConcatPathDown => "RevConcatPathDown",
Self::Reserved => "Reserved",
})
}
}
#[repr(u8)]
#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Default, Clone, Copy, NoUninit)]
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",
})
}
}
impl Into<&OsStr> for BfdState {
fn into(self) -> &'static OsStr {
match self {
Self::AdminDown => OsStr::new("AdminDown"),
Self::Init => OsStr::new("Init"),
Self::Down => OsStr::new("Down"),
Self::Up => OsStr::new("Up"),
}
}
}
#[repr(u8)]
#[derive(ConvRaw, Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit)]
pub enum BfdAuthType {
None = 0,
SimplePassword = 1,
KeyedMD5 = 2,
MetKeyedMD5 = 3,
KeyedSHA1 = 4,
MetKeyedSHA1 = 5,
Reserved,
}
#[repr(transparent)]
#[derive(Debug, NomBE, PartialEq, Eq, Clone, Copy, NoUninit, Hash)]
pub struct BfdDiscriminator(pub u32);
impl Display for BfdDiscriminator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, NoUninit, NomBE)]
/// All intervals in BFD are specified in microseconds
pub struct BfdInterval(u32);
impl BfdInterval {
pub fn from_micros(micros: u32) -> Self {
Self(micros)
}
pub fn from_millis(millis: u32) -> Self {
Self(millis * 1000)
}
pub fn from_secs(secs: u32) -> Self {
Self(secs * 1000000)
}
pub fn from_secs_f32(secs: f32) -> Self {
Self((secs * 1000000.0) as u32)
}
}
impl From<BfdInterval> for time::Duration {
fn from(value: BfdInterval) -> Self {
time::Duration::from_micros(value.0 as u64)
}
}
impl From<time::Duration> for BfdInterval {
fn from(value: time::Duration) -> Self {
BfdInterval(value.as_micros() as u32)
}
}
impl Display for BfdInterval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}us", self.0)
}
}
impl Mul<u32> for BfdInterval {
type Output = BfdInterval;
fn mul(self, rhs: u32) -> Self::Output {
BfdInterval(self.0 * rhs)
}
}
impl Mul<BfdInterval> for u32 {
type Output = BfdInterval;
fn mul(self, rhs: BfdInterval) -> Self::Output {
BfdInterval(self * rhs.0)
}
}
impl Div<u32> for BfdInterval {
type Output = BfdInterval;
fn div(self, rhs: u32) -> Self::Output {
BfdInterval(self.0 / rhs)
}
}
impl Div<BfdInterval> for u32 {
type Output = BfdInterval;
fn div(self, rhs: BfdInterval) -> Self::Output {
BfdInterval(self / rhs.0)
}
}
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<u8>);
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 {
pub flags: BfdFlags,
pub my_disc: BfdDiscriminator,
pub your_disc: BfdDiscriminator,
pub desired_min_tx: BfdInterval,
pub required_min_rx: BfdInterval,
pub required_min_echo_rx: BfdInterval,
#[nom(Cond = "flags.auth_present()")]
pub auth: Option<BfdAuth>,
}
impl BfdPacket {
pub fn serialize(&self) -> Result<Box<[u8]>, std::io::Error> {
// TODO: serialize auth
let buf = [0u8; 24];
let mut wtr = Cursor::new(buf);
wtr.write_u32::<BigEndian>(self.flags.0)?;
wtr.write_u32::<BigEndian>(self.my_disc.0)?;
wtr.write_u32::<BigEndian>(self.your_disc.0)?;
wtr.write_u32::<BigEndian>(self.desired_min_tx.0)?;
wtr.write_u32::<BigEndian>(self.required_min_rx.0)?;
wtr.write_u32::<BigEndian>(self.required_min_echo_rx.0)?;
Ok(Box::new(wtr.into_inner()))
}
}