diff options
-rw-r--r-- | Cargo.lock | 17 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/vrrpv2.rs | 143 |
3 files changed, 157 insertions, 4 deletions
@@ -67,6 +67,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] name = "mio" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -79,6 +85,16 @@ dependencies = [ ] [[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] name = "num_cpus" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -227,6 +243,7 @@ checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" name = "vrrpd" version = "0.1.0" dependencies = [ + "nom", "tokio", ] @@ -7,3 +7,4 @@ edition = "2021" [dependencies] tokio = { version = "1.22.0", features = ["full"] } +nom = "7" diff --git a/src/vrrpv2.rs b/src/vrrpv2.rs index 559b913..9f4ffea 100644 --- a/src/vrrpv2.rs +++ b/src/vrrpv2.rs @@ -1,21 +1,45 @@ +use nom::bits::{bits, streaming::take}; +use nom::combinator::map_res; +use nom::error::{Error, ErrorKind}; +use nom::multi::count; +use nom::number::complete::{be_u16, be_u32, u8}; +use nom::sequence::tuple; +use nom::{Err, IResult}; use std::net::Ipv4Addr; -pub enum VRRPv2Error {} +const VRRP_REQUIRED_VERSION: u8 = 2; +const VRRP_REQUIRED_TYPE: u8 = 1; // Advertisement +#[derive(Debug, Clone, PartialEq)] +pub enum VRRPv2Error { + VRRPv2ParseError, +} + +type NomError<'a> = nom::Err<nom::error::Error<&'a [u8]>>; +impl From<NomError<'_>> for VRRPv2Error { + fn from(_: NomError) -> Self { + Self::VRRPv2ParseError + } +} + +#[derive(Debug, PartialEq)] pub enum VRRPVersion { - V2(u8), + V2, } +#[derive(Debug, PartialEq)] pub enum VRRPv2Type { VRRPv2Advertisement, } +#[derive(Debug, PartialEq)] pub enum VRRPv2AuthType { VRRPv2AuthNoAuth = 0x00, VRRPv2AuthReserved1 = 0x01, VRRPv2AuthReserved2 = 0x02, } +#[derive(Debug, PartialEq)] pub struct VRRPv2 { pub version: VRRPVersion, pub type_: VRRPv2Type, @@ -28,6 +52,117 @@ pub struct VRRPv2 { pub ip_addrs: Vec<Ipv4Addr>, } -pub fn from_bytes(_bytes: &[u8]) -> Result<&VRRPv2, VRRPv2Error> { - unimplemented!() +fn two_nibbles(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits::<_, _, Error<(&[u8], usize)>, _, _>(tuple((take(4usize), take(4usize))))(input) +} + +fn parse_version_type(input: &[u8]) -> IResult<&[u8], (VRRPVersion, VRRPv2Type)> { + let (input, pair) = two_nibbles(input)?; + match pair { + (VRRP_REQUIRED_VERSION, VRRP_REQUIRED_TYPE) => { + Ok((input, (VRRPVersion::V2, VRRPv2Type::VRRPv2Advertisement))) + } + _ => Err(Err::Error(Error::new(input, ErrorKind::Alt))), + } +} + +fn parse_auth_type(input: &[u8]) -> IResult<&[u8], VRRPv2AuthType> { + map_res(u8, |auth_type| { + Ok(match auth_type { + 0 => VRRPv2AuthType::VRRPv2AuthNoAuth, + 1 => VRRPv2AuthType::VRRPv2AuthReserved1, + 2 => VRRPv2AuthType::VRRPv2AuthReserved2, + _ => return Err(Err::Error(Error::new(input, ErrorKind::Alt))), + }) + })(input) +} + +fn parse(input: &[u8]) -> IResult<&[u8], VRRPv2> { + let (input, (version, type_)) = parse_version_type(input)?; + let (input, virtual_router_id) = u8(input)?; + let (input, priority) = u8(input)?; + let (input, count_ip_addrs) = u8(input)?; + let (input, auth_type) = parse_auth_type(input)?; + let (input, advertisement_interval) = u8(input)?; + let (input, checksum) = be_u16(input)?; + // TODO verify checksum + let (input, xs) = count(be_u32, usize::from(count_ip_addrs))(input)?; + let ip_addrs = xs.into_iter().map(Ipv4Addr::from).collect(); + Ok(( + input, + VRRPv2 { + version, + type_, + virtual_router_id, + priority, + count_ip_addrs, + auth_type, + advertisement_interval, + checksum, + ip_addrs, + }, + )) +} + +pub fn from_bytes(bytes: &[u8]) -> Result<VRRPv2, VRRPv2Error> { + match parse(bytes) { + Ok((_, v)) => Ok(v), + Err(e) => Err(e.into()), + } +} + +#[test] +fn test_standard_bytes() { + let bytes = [ + 0x21, 0x01, 0x64, 0x01, 0x00, 0x01, 0xba, 0x52, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let got = from_bytes(&bytes).unwrap(); + let expected = VRRPv2 { + version: VRRPVersion::V2, + type_: VRRPv2Type::VRRPv2Advertisement, + virtual_router_id: 1, + priority: 100, + count_ip_addrs: 1, + auth_type: VRRPv2AuthType::VRRPv2AuthNoAuth, + checksum: 47698, + advertisement_interval: 1, + ip_addrs: vec![Ipv4Addr::from([192, 168, 0, 1])], + }; + assert_eq!(got, expected); +} + +#[test] +fn test_incomplete_bytes() { + let bytes = [0x21, 0x01]; + let got = from_bytes(&bytes); + assert_eq!(got.is_err(), true); + assert_eq!(got.err(), Some(VRRPv2Error::VRRPv2ParseError)); +} + +#[test] +fn test_invalid_version_type() { + let bytes = [ + 0x00, 0x01, 0x64, 0x01, 0x00, 0x01, 0xba, 0x52, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let got = from_bytes(&bytes); + assert_eq!(got.is_err(), true); + assert_eq!(got.err(), Some(VRRPv2Error::VRRPv2ParseError)); +} + +#[test] +fn test_invalid_auth_type() { + let bytes = [ + 0x21, 0x01, 0x64, 0x01, 0x03, 0x01, 0xba, 0x52, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let got = from_bytes(&bytes); + assert_eq!(got.is_err(), true); + assert_eq!(got.err(), Some(VRRPv2Error::VRRPv2ParseError)); +} + +#[test] +fn test_invalid_checksum() { + panic!("fix me"); } |