Skip to content

Commit

Permalink
Implement IPv4 options: MTUP, MTUR and write tests
Browse files Browse the repository at this point in the history
- Fix bug when reading IP packets without options
- Write tests to test different packets with options
- Implement From traits for IPv4Address and IPv6Address

Signed-off-by: Mohammad Aadil Shabier <aadilshabier1@gmail.com>
  • Loading branch information
aadilshabier committed Mar 5, 2024
1 parent f54739f commit 92601fe
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 22 deletions.
156 changes: 135 additions & 21 deletions src/layers/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,24 @@ struct IPOthers {
pub enum IPOption {
EOOL,
NOP,
RR { ptr: u8, route: Vec<IPv4Address> },
// MTUP,
// MTUR,
Other { value: u8, len: u8, data: Vec<u8> },
RR {
len: u8,
ptr: u8,
route: Vec<IPv4Address>,
},
MTUP {
len: u8,
value: u16,
},
MTUR {
len: u8,
value: u16,
},
Other {
value: u8,
len: u8,
data: Vec<u8>,
},
}

#[derive(Debug, Default, Serialize)]
Expand Down Expand Up @@ -99,13 +113,14 @@ impl IPv4 {
impl IPv4 {
fn options_from_bytes(&mut self, bytes: &[u8], mut remaining: usize) -> Result<usize, Error> {
let mut i = 0_usize;
let mut done = false;
let max_option_bytes = self.hdr_len as usize * 4 - IPV4_BASE_HEADER_LENGTH;
let mut done = i >= max_option_bytes;

while !done {
let (option, consumed) = Self::option_from_bytes(&bytes[i..], remaining)?;
i += consumed;
remaining -= consumed;
if remaining == 0 || option == IPOption::EOOL {
if i >= max_option_bytes || option == IPOption::EOOL {
done = true;
}
self.options.push(option);
Expand All @@ -114,26 +129,35 @@ impl IPv4 {
Ok(i)
}

fn option_from_bytes(bytes: &[u8], mut remaining: usize) -> Result<(IPOption, usize), Error> {
fn option_from_bytes(bytes: &[u8], remaining: usize) -> Result<(IPOption, usize), Error> {
let mut i = 0_usize;

if remaining < 1 {
return Err(Error::TooShort {
required: 2,
available: remaining,
data: hex::encode(bytes),
});
}

let value = bytes[0];

// from: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
let option = match value {
0 => {
i += 2;
remaining -= 2;
i += 1;
// remaining -= 1;
IPOption::EOOL
}
1 => {
i += 2;
remaining -= 2;
i += 1;
// remaining -= 1;
IPOption::NOP
}
7 => {
let ((len, data), consumed) = Self::option_data_from_bytes(&bytes[i..], remaining)?;
i += consumed;
remaining -= consumed;
// remaining -= consumed;

let ptr = data[0];
let mut route = Vec::new();
Expand All @@ -150,18 +174,33 @@ impl IPv4 {
}
}

IPOption::RR { ptr, route }
IPOption::RR { len, ptr, route }
}
11 => {
todo!("MTUP")
}
12 => {
todo!("MTUR")
11 | 12 => {
let ((len, data), consumed) = Self::option_data_from_bytes(&bytes[i..], remaining)?;
i += consumed;
// remaining -= consumed;

if len < 4 {
return Err(Error::TooShort {
required: 4,
available: len.into(),
data: hex::encode(data),
});
}

let mtu = u16::from_be_bytes(data[0..2].try_into().unwrap());

match value {
11 => IPOption::MTUP { len, value: mtu },
12 => IPOption::MTUR { len, value: mtu },
_ => unreachable!("Value should either be 11 or 12"),
}
}
value => {
let ((len, data), consumed) = Self::option_data_from_bytes(&bytes[i..], remaining)?;
i += consumed;
remaining -= consumed;
// remaining -= consumed;

IPOption::Other {
value,
Expand Down Expand Up @@ -203,7 +242,7 @@ impl IPv4 {
}
let data = &bytes[i..i + len - 2];
i += len - 2;
remaining -= len - 2;
// remaining -= len - 2;

Ok(((len as u8, data), i))
}
Expand Down Expand Up @@ -253,7 +292,7 @@ impl Layer for IPv4 {

let consumed = self.options_from_bytes(&bytes[decoded..], remaining)?;
decoded += consumed;
remaining -= consumed;
// remaining -= consumed;

let map = PROTOCOLS_MAP.read().unwrap();
let layer = map.get(&self.proto);
Expand All @@ -272,3 +311,78 @@ impl Layer for IPv4 {
"ip"
}
}

#[cfg(test)]
mod tests {
use crate::{layers, Layer};

fn test_options(packet: &[u8], options: &[super::IPOption]) {
let _ = layers::register_defaults();

let mut ipv4 = Box::new(super::IPv4::default());
let p = ipv4.decode_bytes(packet);
assert!(p.is_ok(), "{:#?}", ipv4);

assert_eq!(ipv4.options.as_slice(), options);
}

#[test]
fn parse_ipv4_option_packet_1() {
let ipv4_packet = hex::decode("08003715e6bc00123f4a33d208004600004caa1d0000801111caac1f1336ac1f1349010101003e3000a10034fa4e302a02010004067075626c6963a01d02012a02010002010030123010060c2b060102012b0e01010601050500").unwrap();
let options = [
super::IPOption::NOP,
super::IPOption::NOP,
super::IPOption::NOP,
super::IPOption::EOOL,
];

test_options(&ipv4_packet[14..], &options);
}

#[test]
fn parse_ipv4_option_packet_2() {
let ipv4_packet = hex::decode("08003715e6bc00123f4a33d208004600004caa1d0000801111caac1f1336ac1f13495f03ff003e3000a10034fa4e302a02010004067075626c6963a01d02012a02010002010030123010060c2b060102012b0e01010601050500").unwrap();
let options = [
super::IPOption::Other {
value: 0x5f,
len: 3,
data: vec![0xff],
},
super::IPOption::EOOL,
];

test_options(&ipv4_packet[14..], &options);
}

#[test]
fn parse_ipv4_option_packet_3() {
let ipv4_packet = hex::decode("08003715e6bc00123f4a33d2080047000050aa1d0000801111caac1f1336ac1f1349070707deadbeef003e3000a10034fa4e302a02010004067075626c6963a01d02012a02010002010030123010060c2b060102012b0e01010601050500").unwrap();
let options = [
super::IPOption::RR {
len: 7,
ptr: 7,
route: vec![[0xde, 0xad, 0xbe, 0xef].into()],
},
super::IPOption::EOOL,
];

test_options(&ipv4_packet[14..], &options);
}

#[test]
fn parse_ipv4_option_packet_4() {
let ipv4_packet = hex::decode("08003715e6bc00123f4a33d2080047000050aa1d0000801111caac1f1336ac1f13490b04dead0c04beef3e3000a10034fa4e302a02010004067075626c6963a01d02012a02010002010030123010060c2b060102012b0e01010601050500").unwrap();
let options = [
super::IPOption::MTUP {
len: 4,
value: 57005,
},
super::IPOption::MTUR {
len: 4,
value: 48879,
},
];

test_options(&ipv4_packet[14..], &options);
}
}
14 changes: 13 additions & 1 deletion src/types/ipaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! This module defines types for IPv4 and IPv6 which are simply based on the u8 arrays.
use core::convert::TryFrom;
use core::convert::{From, TryFrom};
use core::fmt;
use core::fmt::Write;

Expand All @@ -13,6 +13,12 @@ use crate::errors::Error as CrateError;
#[derive(Default, Clone, PartialEq)]
pub struct IPv4Address([u8; 4]);

impl From<[u8; 4]> for IPv4Address {
fn from(value: [u8; 4]) -> Self {
Self(value)
}
}

impl TryFrom<&'_ [u8]> for IPv4Address {
type Error = CrateError;

Expand Down Expand Up @@ -65,6 +71,12 @@ impl Serialize for IPv4Address {
#[derive(Default, Clone, PartialEq)]
pub struct IPv6Address([u16; 8]);

impl From<[u16; 8]> for IPv6Address {
fn from(value: [u16; 8]) -> Self {
Self(value)
}
}

impl TryFrom<&'_ [u8]> for IPv6Address {
type Error = CrateError;

Expand Down

0 comments on commit 92601fe

Please sign in to comment.