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...
254 lines
6.9 KiB
Rust
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()))
|
|
}
|
|
}
|