From 600894f22b91567aa00dc0ad7c207f19c0f2c3e5 Mon Sep 17 00:00:00 2001 From: harshit Date: Wed, 21 Feb 2024 23:30:38 +0530 Subject: [PATCH 1/5] [ICMP] Implement ICMP Header dissection Signed-off-by: harshit --- build.rs | 2 +- src/layers/icmp.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++ src/layers/mod.rs | 1 + 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/layers/icmp.rs diff --git a/build.rs b/build.rs index becd2ed..9383626 100644 --- a/build.rs +++ b/build.rs @@ -40,7 +40,7 @@ fn main() -> std::io::Result<()> { .to_str() .unwrap() .to_string() - .replace('/', "::") + .replace(std::path::MAIN_SEPARATOR, "::") .replace("mod.rs", "") .replace(".rs", "::"); diff --git a/src/layers/icmp.rs b/src/layers/icmp.rs new file mode 100644 index 0000000..4d3c516 --- /dev/null +++ b/src/layers/icmp.rs @@ -0,0 +1,107 @@ +//! ICMP Datagram + +use std::convert::TryInto; + +use serde::Serialize; + +use crate::errors::Error; +use crate::layers::ipv4; +use crate::types::IPv4Address; +use crate::Layer; + +/// IANA Assigned protocol number for ICMP +pub const IPPROTO_ICMP: u8 = 1_u8; + +// Register ICMP with Protocol Handler in IPv4 +pub(crate) fn register_defaults() -> Result<(), Error> { + ipv4::register_protocol(IPPROTO_ICMP, ICMP::creator) +} + +#[derive(Default, Debug, Serialize)] +#[serde(untagged)] +pub enum IcmpType { + #[default] + Unused, + Echo(IcmpEcho), + Redirect(IcmpRedirect), +} + +#[derive(Default, Debug, Serialize)] +pub struct IcmpEcho { + identifier: u16, + sequence_number: u16, +} + +#[derive(Default, Debug, Serialize)] +pub struct IcmpRedirect { + gateway_address: IPv4Address, +} + +/// Structure representing the ICMP Header +#[derive(Default, Debug, Serialize)] +pub struct ICMP { + #[serde(rename = "type")] + icmp_type: u8, + code: u8, + checksum: u16, + #[serde(flatten)] + rest_of_header: IcmpType, +} + +impl ICMP { + pub(crate) fn creator() -> Box { + Box::::default() + } +} + +impl Layer for ICMP { + fn decode_bytes( + &mut self, + bytes: &[u8], + ) -> Result<(Option>, usize), Error> { + let decoded; + + if bytes.len() < 8 { + return Err(Error::TooShort { + required: 8, + available: bytes.len(), + data: hex::encode(bytes) + }) + } + + // decode type, code and checksum + self.icmp_type = u8::from_be(bytes[0]); + self.code = u8::from_be(bytes[1]); + self.checksum = (bytes[2] as u16) << 8 | (bytes[3] as u16); + + // process the next 4 bytes depending on the type of ICMP packet + self.rest_of_header = match self.icmp_type { + 0 | 8 | 13 | 14 | 15 | 16 => { + let identifier = (bytes[4] as u16) << 8 | (bytes[5] as u16); + let sequence_number = (bytes[6] as u16) << 8 | (bytes[7] as u16); + IcmpType::Echo(IcmpEcho { + identifier, + sequence_number, + }) + } + 5 => { + IcmpType::Redirect(IcmpRedirect { + gateway_address: bytes[4..8].try_into().unwrap(), + }) + } + 3 | 4 | 11 | _ => { + IcmpType::Unused + } + }; + decoded = 8; + Ok((None, decoded)) + } + + fn name(&self) -> &'static str { + "ICMP" + } + + fn short_name(&self) -> &'static str { + "icmp" + } +} diff --git a/src/layers/mod.rs b/src/layers/mod.rs index af8a139..b960f78 100644 --- a/src/layers/mod.rs +++ b/src/layers/mod.rs @@ -10,6 +10,7 @@ pub mod tcp; pub mod udp; pub mod dns; +pub mod icmp; pub mod arp; From 88bac3638123fb4e906cbd48923719afa773f7a7 Mon Sep 17 00:00:00 2001 From: harshit Date: Fri, 23 Feb 2024 12:07:39 +0530 Subject: [PATCH 2/5] [ICMP] Replace type values with constants Signed-off-by: harshit --- src/layers/icmp.rs | 81 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/src/layers/icmp.rs b/src/layers/icmp.rs index 4d3c516..b03aa15 100644 --- a/src/layers/icmp.rs +++ b/src/layers/icmp.rs @@ -11,21 +11,39 @@ use crate::Layer; /// IANA Assigned protocol number for ICMP pub const IPPROTO_ICMP: u8 = 1_u8; +/// ICMP header length +pub const ICMP_HEADER_LENGTH: usize = 8_usize; + +/// ICMP types +pub const ICMP_ECHO_REPLY: u8 = 0_u8; +pub const ICMP_ECHO_REQUEST: u8 = 8_u8; +pub const ICMP_DESTINATION_UNREACHABLE: u8 = 3_u8; +pub const ICMP_SOURCE_QUENCH: u8 = 4_u8; +pub const ICMP_REDIRECT: u8 = 5_u8; +pub const ICMP_TIME_EXCEEDED: u8 = 11_u8; // Register ICMP with Protocol Handler in IPv4 pub(crate) fn register_defaults() -> Result<(), Error> { ipv4::register_protocol(IPPROTO_ICMP, ICMP::creator) } -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize)] #[serde(untagged)] pub enum IcmpType { - #[default] - Unused, - Echo(IcmpEcho), + #[serde[rename = "unsupported"]] + Unsupported(IcmpUnsupported), + Unused(IcmpUnused), + EchoRequest(IcmpEcho), + EchoReply(IcmpEcho), Redirect(IcmpRedirect), } +impl Default for IcmpType { + fn default() -> Self { + IcmpType::Unsupported(IcmpUnsupported{unsupported: 0}) + } +} + #[derive(Default, Debug, Serialize)] pub struct IcmpEcho { identifier: u16, @@ -37,13 +55,25 @@ pub struct IcmpRedirect { gateway_address: IPv4Address, } +#[derive(Default, Debug, Serialize)] +pub struct IcmpUnused { + unused: u32, +} + +#[derive(Default, Debug, Serialize)] +pub struct IcmpUnsupported { + unsupported: u32, +} + + + /// Structure representing the ICMP Header #[derive(Default, Debug, Serialize)] pub struct ICMP { #[serde(rename = "type")] icmp_type: u8, code: u8, - checksum: u16, + checksum: String, #[serde(flatten)] rest_of_header: IcmpType, } @@ -61,37 +91,54 @@ impl Layer for ICMP { ) -> Result<(Option>, usize), Error> { let decoded; - if bytes.len() < 8 { + if bytes.len() < ICMP_HEADER_LENGTH { return Err(Error::TooShort { - required: 8, + required: ICMP_HEADER_LENGTH, available: bytes.len(), - data: hex::encode(bytes) - }) + data: hex::encode(bytes), + }); } // decode type, code and checksum self.icmp_type = u8::from_be(bytes[0]); self.code = u8::from_be(bytes[1]); - self.checksum = (bytes[2] as u16) << 8 | (bytes[3] as u16); + self.checksum = hex::encode(&bytes[2..4]); // process the next 4 bytes depending on the type of ICMP packet self.rest_of_header = match self.icmp_type { - 0 | 8 | 13 | 14 | 15 | 16 => { + ICMP_ECHO_REPLY => { let identifier = (bytes[4] as u16) << 8 | (bytes[5] as u16); let sequence_number = (bytes[6] as u16) << 8 | (bytes[7] as u16); - IcmpType::Echo(IcmpEcho { + IcmpType::EchoReply(IcmpEcho { identifier, sequence_number, }) } - 5 => { - IcmpType::Redirect(IcmpRedirect { - gateway_address: bytes[4..8].try_into().unwrap(), + ICMP_ECHO_REQUEST => { + let identifier = (bytes[4] as u16) << 8 | (bytes[5] as u16); + let sequence_number = (bytes[6] as u16) << 8 | (bytes[7] as u16); + IcmpType::EchoRequest(IcmpEcho { + identifier, + sequence_number, }) } - 3 | 4 | 11 | _ => { - IcmpType::Unused + ICMP_REDIRECT => IcmpType::Redirect(IcmpRedirect { + gateway_address: bytes[4..8].try_into().unwrap(), + }), + ICMP_DESTINATION_UNREACHABLE | ICMP_SOURCE_QUENCH | ICMP_TIME_EXCEEDED => { + IcmpType::Unused(IcmpUnused { + unused: (bytes[4] as u32) << 24 + | (bytes[5] as u32) << 16 + | (bytes[6] as u32) << 8 + | (bytes[7] as u32), + }) } + _ => IcmpType::Unsupported(IcmpUnsupported{ + unsupported: (bytes[4] as u32) << 24 + | (bytes[5] as u32) << 16 + | (bytes[6] as u32) << 8 + | (bytes[7] as u32) + }) }; decoded = 8; Ok((None, decoded)) From f29893278a1623dd060aa761d679f2f4fd3c70b6 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 24 Feb 2024 19:19:49 +0530 Subject: [PATCH 3/5] [ICMP] Capture bytes for unsupported types and minor changes Signed-off-by: harshit --- src/layers/icmp.rs | 61 ++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/layers/icmp.rs b/src/layers/icmp.rs index b03aa15..fe0e90e 100644 --- a/src/layers/icmp.rs +++ b/src/layers/icmp.rs @@ -27,23 +27,18 @@ pub(crate) fn register_defaults() -> Result<(), Error> { ipv4::register_protocol(IPPROTO_ICMP, ICMP::creator) } -#[derive(Debug, Serialize)] +#[derive(Default, Debug, Serialize)] #[serde(untagged)] pub enum IcmpType { - #[serde[rename = "unsupported"]] + #[default] + Empty, Unsupported(IcmpUnsupported), - Unused(IcmpUnused), + #[serde[rename = "unsupported"]] EchoRequest(IcmpEcho), EchoReply(IcmpEcho), Redirect(IcmpRedirect), } -impl Default for IcmpType { - fn default() -> Self { - IcmpType::Unsupported(IcmpUnsupported{unsupported: 0}) - } -} - #[derive(Default, Debug, Serialize)] pub struct IcmpEcho { identifier: u16, @@ -55,25 +50,23 @@ pub struct IcmpRedirect { gateway_address: IPv4Address, } -#[derive(Default, Debug, Serialize)] -pub struct IcmpUnused { - unused: u32, -} - #[derive(Default, Debug, Serialize)] pub struct IcmpUnsupported { - unsupported: u32, + #[serde( + skip_serializing_if = "Vec::is_empty", + serialize_with = "hex::serde::serialize" + )] + unsupported: Vec, } - - /// Structure representing the ICMP Header #[derive(Default, Debug, Serialize)] pub struct ICMP { #[serde(rename = "type")] icmp_type: u8, code: u8, - checksum: String, + #[serde(serialize_with = "crate::types::hex::serialize_lower_hex_u16")] + checksum: u16, #[serde(flatten)] rest_of_header: IcmpType, } @@ -102,13 +95,14 @@ impl Layer for ICMP { // decode type, code and checksum self.icmp_type = u8::from_be(bytes[0]); self.code = u8::from_be(bytes[1]); - self.checksum = hex::encode(&bytes[2..4]); + self.checksum = (bytes[2] as u16) << 8 | (bytes[3] as u16); // process the next 4 bytes depending on the type of ICMP packet self.rest_of_header = match self.icmp_type { ICMP_ECHO_REPLY => { let identifier = (bytes[4] as u16) << 8 | (bytes[5] as u16); let sequence_number = (bytes[6] as u16) << 8 | (bytes[7] as u16); + decoded = 8; IcmpType::EchoReply(IcmpEcho { identifier, sequence_number, @@ -117,30 +111,29 @@ impl Layer for ICMP { ICMP_ECHO_REQUEST => { let identifier = (bytes[4] as u16) << 8 | (bytes[5] as u16); let sequence_number = (bytes[6] as u16) << 8 | (bytes[7] as u16); + decoded = 8; IcmpType::EchoRequest(IcmpEcho { identifier, sequence_number, }) } - ICMP_REDIRECT => IcmpType::Redirect(IcmpRedirect { - gateway_address: bytes[4..8].try_into().unwrap(), - }), - ICMP_DESTINATION_UNREACHABLE | ICMP_SOURCE_QUENCH | ICMP_TIME_EXCEEDED => { - IcmpType::Unused(IcmpUnused { - unused: (bytes[4] as u32) << 24 - | (bytes[5] as u32) << 16 - | (bytes[6] as u32) << 8 - | (bytes[7] as u32), + ICMP_REDIRECT => { + decoded = 8; + IcmpType::Redirect(IcmpRedirect { + gateway_address: bytes[4..8].try_into().unwrap(), }) } - _ => IcmpType::Unsupported(IcmpUnsupported{ - unsupported: (bytes[4] as u32) << 24 - | (bytes[5] as u32) << 16 - | (bytes[6] as u32) << 8 - | (bytes[7] as u32) + ICMP_DESTINATION_UNREACHABLE | ICMP_SOURCE_QUENCH | ICMP_TIME_EXCEEDED => { + decoded = 8; + IcmpType::Empty + } + _ => { + decoded = bytes.len(); + IcmpType::Unsupported(IcmpUnsupported { + unsupported: bytes[4..].to_vec(), }) + }, }; - decoded = 8; Ok((None, decoded)) } From a796fda11dc82a7d76c77d1b826c2414fafd3e69 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 24 Feb 2024 21:45:31 +0530 Subject: [PATCH 4/5] [ICMP] Add tests Signed-off-by: harshit --- src/layers/icmp.rs | 48 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/layers/icmp.rs b/src/layers/icmp.rs index fe0e90e..e0506e4 100644 --- a/src/layers/icmp.rs +++ b/src/layers/icmp.rs @@ -130,9 +130,9 @@ impl Layer for ICMP { _ => { decoded = bytes.len(); IcmpType::Unsupported(IcmpUnsupported { - unsupported: bytes[4..].to_vec(), - }) - }, + unsupported: bytes[4..].to_vec(), + }) + } }; Ok((None, decoded)) } @@ -145,3 +145,45 @@ impl Layer for ICMP { "icmp" } } + +#[cfg(test)] +mod tests { + use crate::layers; + use crate::{Layer, Packet, ENCAP_TYPE_ETH}; + + #[test] + fn parse_valid_icmp_packet() { + let _ = layers::register_defaults(); + + let icmp_packet = vec![ + 0x00, 0x20, 0x78, 0xe1, 0x5a, 0x80, 0x00, 0x10, 0x7b, 0x81, 0x43, 0xe3, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x38, 0x62, 0x3d, 0x00, 0x00, 0xff, 0x01, 0xd8, 0x1e, 0x0a, 0x02, + 0x63, 0x63, 0x0a, 0x02, 0x0a, 0x02, 0x05, 0x01, 0x38, 0x3e, 0x0a, 0x02, 0x63, 0x62, + 0x45, 0x00, 0x00, 0x3c, 0x3a, 0x00, 0x00, 0x00, 0x1f, 0x01, 0xfc, 0xb3, 0x0a, 0x02, + 0x0a, 0x02, 0x0a, 0x03, 0x47, 0x07, 0x08, 0x00, 0x1a, 0x5c, 0x02, 0x00, 0x31, 0x00, + ]; + + let mut icmp: Box = Box::new(super::ICMP::default()); + + let p = icmp.decode_bytes(&icmp_packet[34..]); + assert!(p.is_ok(), "{:#?}", icmp); + } + + #[test] + fn test_icmp_parse_regression() { + let _ = layers::register_defaults(); + + let test_icmp_packet = vec![ + 0x00, 0x20, 0x78, 0xe1, 0x5a, 0x80, 0x00, 0x10, 0x7b, 0x81, 0x43, 0xe3, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x38, 0x62, 0x3d, 0x00, 0x00, 0xff, 0x01, 0xd8, 0x1e, 0x0a, 0x02, + 0x63, 0x63, 0x0a, 0x02, 0x0a, 0x02, 0x05, 0x01, 0x38, 0x3e, 0x0a, 0x02, 0x63, 0x62, + 0x45, 0x00, 0x00, 0x3c, 0x3a, 0x00, 0x00, 0x00, 0x1f, 0x01, 0xfc, 0xb3, 0x0a, 0x02, + 0x0a, 0x02, 0x0a, 0x03, 0x47, 0x07, 0x08, 0x00, 0x1a, 0x5c, 0x02, 0x00, 0x31, 0x00, + ]; + + let p = Packet::from_bytes(&test_icmp_packet, ENCAP_TYPE_ETH); + assert!(p.is_ok()); + let p = p.unwrap(); + assert!(p.layers.len() == 3, "{:#?}", p); + } +} From 2be23e6bf5ff05385dd81accb9f51cfe5c5af3b9 Mon Sep 17 00:00:00 2001 From: harshit Date: Sat, 24 Feb 2024 23:46:09 +0530 Subject: [PATCH 5/5] [ICMP] Add test for different types Signed-off-by: harshit --- src/layers/icmp.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/layers/icmp.rs b/src/layers/icmp.rs index e0506e4..db88a09 100644 --- a/src/layers/icmp.rs +++ b/src/layers/icmp.rs @@ -148,6 +148,8 @@ impl Layer for ICMP { #[cfg(test)] mod tests { + use serde_json::json; + use crate::layers; use crate::{Layer, Packet, ENCAP_TYPE_ETH}; @@ -186,4 +188,103 @@ mod tests { let p = p.unwrap(); assert!(p.layers.len() == 3, "{:#?}", p); } + + #[test] + fn parse_icmp_redirect_packet() { + let _ = layers::register_defaults(); + + let icmp_redirect_packet = vec![ + 0x00, 0x20, 0x78, 0xe1, 0x5a, 0x80, 0x00, 0x10, 0x7b, 0x81, 0x43, 0xe3, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x38, 0x62, 0x3d, 0x00, 0x00, 0xff, 0x01, 0xd8, 0x1e, 0x0a, 0x02, + 0x63, 0x63, 0x0a, 0x02, 0x0a, 0x02, 0x05, 0x01, 0x38, 0x3e, 0x0a, 0x02, 0x63, 0x62, + 0x45, 0x00, 0x00, 0x3c, 0x3a, 0x00, 0x00, 0x00, 0x1f, 0x01, 0xfc, 0xb3, 0x0a, 0x02, + 0x0a, 0x02, 0x0a, 0x03, 0x47, 0x07, 0x08, 0x00, 0x1a, 0x5c, 0x02, 0x00, 0x31, 0x00, + ]; + + let p = Packet::from_bytes(&icmp_redirect_packet, ENCAP_TYPE_ETH); + assert!(p.is_ok()); + let p = p.unwrap(); + + let icmp_packet = serde_json::to_value(&p.layers[2]).unwrap(); + assert_eq!(icmp_packet.get("type"), Some(&json!(5))); + assert_eq!(icmp_packet.get("code"), Some(&json!(1))); + assert_eq!(icmp_packet.get("checksum"), Some(&json!("0x383e"))); + assert_eq!( + icmp_packet.get("gateway_address"), + Some(&json!("10.2.99.98")) + ); + } + + #[test] + fn parse_icmp_echo_packet() { + let _ = layers::register_defaults(); + + let icmp_echo_packet = vec![ + 0x50, 0xeb, 0x1a, 0x90, 0x61, 0x32, 0xd4, 0x6a, 0x6a, 0xb4, 0x62, 0x0d, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x54, 0x92, 0xe0, 0x40, 0x00, 0x40, 0x01, 0x49, 0x56, 0x0a, 0x64, + 0x01, 0xb0, 0x8e, 0xfa, 0xc3, 0x64, 0x08, 0x00, 0x98, 0xff, 0x00, 0x02, 0x00, 0x02, + 0x9d, 0x25, 0xda, 0x65, 0x00, 0x00, 0x00, 0x00, 0x20, 0x9e, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + ]; + + let p = Packet::from_bytes(&icmp_echo_packet, ENCAP_TYPE_ETH); + assert!(p.is_ok()); + let p = p.unwrap(); + + let icmp_packet = serde_json::to_value(&p.layers[2]).unwrap(); + assert_eq!(icmp_packet.get("type"), Some(&json!(8))); + assert_eq!(icmp_packet.get("code"), Some(&json!(0))); + assert_eq!(icmp_packet.get("checksum"), Some(&json!("0x98ff"))); + assert_eq!(icmp_packet.get("identifier"), Some(&json!(2))); + assert_eq!(icmp_packet.get("sequence_number"), Some(&json!(2))); + } + + #[test] + fn parse_icmp_destination_unreachable_packet() { + let _ = layers::register_defaults(); + + let icmp_destination_unreachable_packet = vec![ + 0xca, 0x01, 0x0c, 0x68, 0x00, 0x08, 0xca, 0x00, 0x0c, 0x68, 0x00, 0x08, 0x08, 0x00, + 0x45, 0x00, 0x00, 0x38, 0x00, 0x03, 0x00, 0x00, 0xff, 0x01, 0xa3, 0xbd, 0x0a, 0x01, + 0x02, 0x01, 0x0a, 0x01, 0x02, 0x02, 0x03, 0x01, 0x2e, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x64, 0x00, 0x05, 0x00, 0x00, 0xfe, 0x01, 0xae, 0x8f, 0x0a, 0x01, + 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x08, 0x00, 0xc6, 0x01, 0x00, 0x01, 0x00, 0x00, + ]; + + let p = Packet::from_bytes(&icmp_destination_unreachable_packet, ENCAP_TYPE_ETH); + assert!(p.is_ok()); + let p = p.unwrap(); + + let icmp_packet = serde_json::to_value(&p.layers[2]).unwrap(); + assert_eq!(icmp_packet.get("type"), Some(&json!(3))); + assert_eq!(icmp_packet.get("code"), Some(&json!(1))); + assert_eq!(icmp_packet.get("checksum"), Some(&json!("0x2efc"))); + } + + #[test] + fn parse_unsupported_icmp_type(){ + let _ = layers::register_defaults(); + + let icmp_destination_unreachable_packet = vec![ + 0x00, 0xa0, 0xd1, 0xbe, 0x97, 0xdd, 0x00, 0x11, + 0x2f, 0x36, 0x8c, 0xda, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x28, 0x8d, 0xff, 0x00, 0x00, 0x80, 0x01, + 0x2a, 0xb8, 0xc0, 0xa8, 0x00, 0x66, 0xc0, 0xa8, + 0x00, 0x67, 0x0d, 0x00, 0x94, 0xe3, 0x39, 0x30, + 0x00, 0x00, 0x00, 0xf6, 0x23, 0xf6, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + + let p = Packet::from_bytes(&icmp_destination_unreachable_packet, ENCAP_TYPE_ETH); + assert!(p.is_ok()); + let p = p.unwrap(); + + let icmp_packet = serde_json::to_value(&p.layers[2]).unwrap(); + assert_eq!(icmp_packet.get("type"), Some(&json!(13))); + assert_eq!(icmp_packet.get("code"), Some(&json!(0))); + assert_eq!(icmp_packet.get("checksum"), Some(&json!("0x94e3"))); + assert_eq!(icmp_packet.get("unsupported"), Some(&json!("3930000000f623f60000000000000000"))); + } }