# HG changeset patch # User Sunil Nimmagadda # Date 1672554564 -19800 # Node ID e4cad4eba60b521864c5640f97ef62863be3fe0e # Parent e3058ceb81cc8401ef0e4d447815016d0f78c5f1 Implement a VRRPv2 packet parser. Use nom parser combinator crate to parse a VRRPv2 packet. TODO: checksum verification and checksum tests. diff -r e3058ceb81cc -r e4cad4eba60b Cargo.lock --- a/Cargo.lock Wed Dec 07 13:35:25 2022 +0530 +++ b/Cargo.lock Sun Jan 01 11:59:24 2023 +0530 @@ -67,6 +67,12 @@ 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 @@ ] [[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 @@ name = "vrrpd" version = "0.1.0" dependencies = [ + "nom", "tokio", ] diff -r e3058ceb81cc -r e4cad4eba60b Cargo.toml --- a/Cargo.toml Wed Dec 07 13:35:25 2022 +0530 +++ b/Cargo.toml Sun Jan 01 11:59:24 2023 +0530 @@ -7,3 +7,4 @@ [dependencies] tokio = { version = "1.22.0", features = ["full"] } +nom = "7" diff -r e3058ceb81cc -r e4cad4eba60b src/vrrpv2.rs --- a/src/vrrpv2.rs Wed Dec 07 13:35:25 2022 +0530 +++ b/src/vrrpv2.rs Sun Jan 01 11:59:24 2023 +0530 @@ -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 -pub enum VRRPVersion { - V2(u8), +#[derive(Debug, Clone, PartialEq)] +pub enum VRRPv2Error { + VRRPv2ParseError, } +type NomError<'a> = nom::Err>; +impl From> for VRRPv2Error { + fn from(_: NomError) -> Self { + Self::VRRPv2ParseError + } +} + +#[derive(Debug, PartialEq)] +pub enum VRRPVersion { + 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 ip_addrs: Vec, } -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 { + 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"); +}