From 16b5ecd8440419a1986f22b0558c9b631c4924fc Mon Sep 17 00:00:00 2001 From: biandratti Date: Sun, 23 Feb 2025 14:51:11 +0100 Subject: [PATCH 1/2] calculate ip extension lenght based on version --- src/tcp_process.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/tcp_process.rs b/src/tcp_process.rs index 5568573..a08fc7c 100644 --- a/src/tcp_process.rs +++ b/src/tcp_process.rs @@ -73,7 +73,7 @@ pub fn process_tcp_ipv4( let version = IpVersion::V4; let ttl_observed: u8 = packet.get_ttl(); let ttl: Ttl = ttl::calculate_ttl(ttl_observed); - let olen: u8 = packet.get_options_raw().len() as u8; + let olen: u8 = calculate_ipv4_options_length(packet); let mut quirks = vec![]; if (packet.get_ecn() & (IP_TOS_CE | IP_TOS_ECT)) != 0 { @@ -128,7 +128,7 @@ pub fn process_tcp_ipv6( let version = IpVersion::V6; let ttl_observed: u8 = packet.get_hop_limit(); let ttl: Ttl = ttl::calculate_ttl(ttl_observed); - let olen = 0; // TODO handle extensions + let olen: u8 = calculate_ipv6_extension_length(packet); let mut quirks = vec![]; if packet.get_flow_label() != 0 { @@ -384,6 +384,43 @@ fn visit_tcp( }) } +fn calculate_ipv4_options_length(packet: &Ipv4Packet) -> u8 { + // IHL (Internet Header Length) is in 32-bit words + // Subtract minimum header length (20 bytes = 5 words) + let ihl = packet.get_header_length(); + let options_length: u8 = if ihl > 5 { + (ihl - 5) * 4 // Convert words to bytes + } else { + 0 // No options: standard header only + }; + options_length +} + +fn calculate_ipv6_extension_length(packet: &Ipv6Packet) -> u8 { + // Most packets will be direct TCP + if packet.get_next_header() == IpNextHeaderProtocols::Tcp { + return 0; + } + + // Check if we have any extension headers + let payload = packet.payload(); + if payload.is_empty() { + return 0; + } + + // Calculate extension length if they exist + let len = match packet.get_next_header() { + IpNextHeaderProtocols::Ipv6Frag => 8, + _ => if payload.len() >= 2 { + (payload[1] as usize + 1) * 8 + } else { + 0 + } + }; + + len as u8 +} + #[cfg(test)] mod tests { use super::*; @@ -420,4 +457,52 @@ mod tests { ); assert_eq!(is_valid(TcpFlags::SYN, 0), false); } + + #[test] + fn test_ipv4_options_length() { + // Create test packet with IHL = 6 (24 bytes header) + let mut data = vec![0u8; 24]; + data[0] = 0x46; // Version 4, IHL 6 + let packet = Ipv4Packet::new(&data).unwrap(); + + assert_eq!(calculate_ipv4_options_length(&packet), 4); + } + + #[test] + fn test_ipv6_direct_tcp() { + let mut data = vec![0u8; 40]; // IPv6 base header + data[0] = 0x60; // Version 6 + data[6] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP + + let packet = Ipv6Packet::new(&data).unwrap(); + assert_eq!(calculate_ipv6_extension_length(&packet), 0); + } + + #[test] + fn test_ipv6_fragment() { + let mut data = vec![0u8; 48]; // IPv6 header + fragment header + data[0] = 0x60; // Version 6 + data[6] = IpNextHeaderProtocols::Ipv6Frag.0; // Next Header = Fragment + + // Set payload length (8 bytes for fragment header) + data[4] = 0; // Length high byte + data[5] = 8; // Length low byte + + // Add fragment header + data[40] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP + // data[41..48] can remain zero (fragment header fields) + + let packet = Ipv6Packet::new(&data).unwrap(); + assert_eq!(calculate_ipv6_extension_length(&packet), 8); + } + + #[test] + fn test_ipv6_empty_payload() { + let mut data = vec![0u8; 40]; // IPv6 header only + data[0] = 0x60; // Version 6 + data[6] = IpNextHeaderProtocols::Hopopt.0; // Next Header = Hop-by-hop + + let packet = Ipv6Packet::new(&data).unwrap(); + assert_eq!(calculate_ipv6_extension_length(&packet), 0); + } } From bc0da52138c3e5144a718273d214cefe0005872d Mon Sep 17 00:00:00 2001 From: biandratti Date: Sun, 23 Feb 2025 15:02:40 +0100 Subject: [PATCH 2/2] move ip options headers to a new rs file --- src/ip_options.rs | 84 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/tcp_process.rs | 90 ++-------------------------------------------- 3 files changed, 88 insertions(+), 87 deletions(-) create mode 100644 src/ip_options.rs diff --git a/src/ip_options.rs b/src/ip_options.rs new file mode 100644 index 0000000..0759755 --- /dev/null +++ b/src/ip_options.rs @@ -0,0 +1,84 @@ +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::ipv4::Ipv4Packet; +use pnet::packet::ipv6::Ipv6Packet; + +pub struct IpOptions; +use pnet::packet::Packet; + +/// Utility struct for handling IP header options and extension headers. +/// Provides methods to calculate the length of optional headers in both IPv4 and IPv6 packets. +impl IpOptions { + pub fn calculate_ipv4_length(packet: &Ipv4Packet) -> u8 { + // IHL (Internet Header Length) is in 32-bit words + // Subtract minimum header length (20 bytes = 5 words) + let ihl = packet.get_header_length(); + let options_length: u8 = if ihl > 5 { + (ihl - 5) * 4 // Convert words to bytes + } else { + 0 // No options: standard header only + }; + options_length + } + + pub fn calculate_ipv6_length(packet: &Ipv6Packet) -> u8 { + // Most packets will be direct TCP + if packet.get_next_header() == IpNextHeaderProtocols::Tcp { + return 0; + } + + let payload = packet.payload(); + if payload.is_empty() { + return 0; + } + + let len = match packet.get_next_header() { + IpNextHeaderProtocols::Ipv6Frag => 8, + _ => { + if payload.len() >= 2 { + (payload[1] as usize + 1) * 8 + } else { + 0 + } + } + }; + + len as u8 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ipv4_options_length() { + let mut data = vec![0u8; 24]; + data[0] = 0x46; // Version 4, IHL 6 + let packet = Ipv4Packet::new(&data).unwrap(); + + assert_eq!(IpOptions::calculate_ipv4_length(&packet), 4); + } + + #[test] + fn test_ipv6_direct_tcp() { + let mut data = vec![0u8; 40]; + data[0] = 0x60; // Version 6 + data[6] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP + + let packet = Ipv6Packet::new(&data).unwrap(); + assert_eq!(IpOptions::calculate_ipv6_length(&packet), 0); + } + + #[test] + fn test_ipv6_fragment() { + let mut data = vec![0u8; 48]; + data[0] = 0x60; // Version 6 + data[6] = IpNextHeaderProtocols::Ipv6Frag.0; // Next Header = Fragment + data[4] = 0; // Length high byte + data[5] = 8; // Length low byte + data[40] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP + + let packet = Ipv6Packet::new(&data).unwrap(); + assert_eq!(IpOptions::calculate_ipv6_length(&packet), 8); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0268b18..96624b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod display; mod http; mod http_languages; mod http_process; +mod ip_options; mod mtu; mod p0f_output; mod process; diff --git a/src/tcp_process.rs b/src/tcp_process.rs index a08fc7c..e517165 100644 --- a/src/tcp_process.rs +++ b/src/tcp_process.rs @@ -1,3 +1,4 @@ +use crate::ip_options::IpOptions; use crate::mtu::ObservableMtu; use crate::process::IpPort; use crate::tcp; @@ -73,7 +74,7 @@ pub fn process_tcp_ipv4( let version = IpVersion::V4; let ttl_observed: u8 = packet.get_ttl(); let ttl: Ttl = ttl::calculate_ttl(ttl_observed); - let olen: u8 = calculate_ipv4_options_length(packet); + let olen: u8 = IpOptions::calculate_ipv4_length(packet); let mut quirks = vec![]; if (packet.get_ecn() & (IP_TOS_CE | IP_TOS_ECT)) != 0 { @@ -128,7 +129,7 @@ pub fn process_tcp_ipv6( let version = IpVersion::V6; let ttl_observed: u8 = packet.get_hop_limit(); let ttl: Ttl = ttl::calculate_ttl(ttl_observed); - let olen: u8 = calculate_ipv6_extension_length(packet); + let olen: u8 = IpOptions::calculate_ipv6_length(packet); let mut quirks = vec![]; if packet.get_flow_label() != 0 { @@ -384,43 +385,6 @@ fn visit_tcp( }) } -fn calculate_ipv4_options_length(packet: &Ipv4Packet) -> u8 { - // IHL (Internet Header Length) is in 32-bit words - // Subtract minimum header length (20 bytes = 5 words) - let ihl = packet.get_header_length(); - let options_length: u8 = if ihl > 5 { - (ihl - 5) * 4 // Convert words to bytes - } else { - 0 // No options: standard header only - }; - options_length -} - -fn calculate_ipv6_extension_length(packet: &Ipv6Packet) -> u8 { - // Most packets will be direct TCP - if packet.get_next_header() == IpNextHeaderProtocols::Tcp { - return 0; - } - - // Check if we have any extension headers - let payload = packet.payload(); - if payload.is_empty() { - return 0; - } - - // Calculate extension length if they exist - let len = match packet.get_next_header() { - IpNextHeaderProtocols::Ipv6Frag => 8, - _ => if payload.len() >= 2 { - (payload[1] as usize + 1) * 8 - } else { - 0 - } - }; - - len as u8 -} - #[cfg(test)] mod tests { use super::*; @@ -457,52 +421,4 @@ mod tests { ); assert_eq!(is_valid(TcpFlags::SYN, 0), false); } - - #[test] - fn test_ipv4_options_length() { - // Create test packet with IHL = 6 (24 bytes header) - let mut data = vec![0u8; 24]; - data[0] = 0x46; // Version 4, IHL 6 - let packet = Ipv4Packet::new(&data).unwrap(); - - assert_eq!(calculate_ipv4_options_length(&packet), 4); - } - - #[test] - fn test_ipv6_direct_tcp() { - let mut data = vec![0u8; 40]; // IPv6 base header - data[0] = 0x60; // Version 6 - data[6] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP - - let packet = Ipv6Packet::new(&data).unwrap(); - assert_eq!(calculate_ipv6_extension_length(&packet), 0); - } - - #[test] - fn test_ipv6_fragment() { - let mut data = vec![0u8; 48]; // IPv6 header + fragment header - data[0] = 0x60; // Version 6 - data[6] = IpNextHeaderProtocols::Ipv6Frag.0; // Next Header = Fragment - - // Set payload length (8 bytes for fragment header) - data[4] = 0; // Length high byte - data[5] = 8; // Length low byte - - // Add fragment header - data[40] = IpNextHeaderProtocols::Tcp.0; // Next Header = TCP - // data[41..48] can remain zero (fragment header fields) - - let packet = Ipv6Packet::new(&data).unwrap(); - assert_eq!(calculate_ipv6_extension_length(&packet), 8); - } - - #[test] - fn test_ipv6_empty_payload() { - let mut data = vec![0u8; 40]; // IPv6 header only - data[0] = 0x60; // Version 6 - data[6] = IpNextHeaderProtocols::Hopopt.0; // Next Header = Hop-by-hop - - let packet = Ipv6Packet::new(&data).unwrap(); - assert_eq!(calculate_ipv6_extension_length(&packet), 0); - } }