Implement a VRRPv2 packet parser.
authorSunil Nimmagadda <sunil@nimmagadda.net>
Sun, 01 Jan 2023 11:59:24 +0530
changeset 8 e4cad4eba60b
parent 7 e3058ceb81cc
child 9 bde5cce81a8a
Implement a VRRPv2 packet parser. Use nom parser combinator crate to parse a VRRPv2 packet. TODO: checksum verification and checksum tests.
Cargo.lock
Cargo.toml
src/vrrpv2.rs
--- 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",
 ]
 
--- 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"
--- 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<nom::error::Error<&'a [u8]>>;
+impl From<NomError<'_>> 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<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");
+}