From 7826228d9c4e5af168075ac2562dcf7c872c35eb Mon Sep 17 00:00:00 2001 From: FujiApple Date: Sun, 1 Oct 2023 10:51:17 +0800 Subject: [PATCH] exploratory code for supporting icmp extensions --- src/tracing/net/channel.rs | 2 +- src/tracing/net/ipv4.rs | 117 ++++++++++++++++++++++++++++++----- src/tracing/packet.rs | 2 +- src/tracing/packet/icmpv4.rs | 10 +++ src/tracing/packet/ipv4.rs | 45 ++++++++++---- 5 files changed, 144 insertions(+), 32 deletions(-) diff --git a/src/tracing/net/channel.rs b/src/tracing/net/channel.rs index 035fe0629..e3c4d1a0d 100644 --- a/src/tracing/net/channel.rs +++ b/src/tracing/net/channel.rs @@ -180,7 +180,7 @@ impl TracerChannel { fn recv_icmp_probe(&mut self) -> TraceResult> { if self.recv_socket.is_readable(self.read_timeout)? { match self.dest_addr { - IpAddr::V4(_) => ipv4::recv_icmp_probe(&mut self.recv_socket, self.protocol), + IpAddr::V4(_) => ipv4::recv_icmp_probe(&mut self.recv_socket, self.protocol, self.ipv4_length_order), IpAddr::V6(_) => ipv6::recv_icmp_probe(&mut self.recv_socket, self.protocol), } } else { diff --git a/src/tracing/net/ipv4.rs b/src/tracing/net/ipv4.rs index 3f4c2f5be..1c3a3f9df 100644 --- a/src/tracing/net/ipv4.rs +++ b/src/tracing/net/ipv4.rs @@ -13,7 +13,7 @@ use crate::tracing::packet::icmpv4::{IcmpCode, IcmpPacket, IcmpType}; use crate::tracing::packet::ipv4::Ipv4Packet; use crate::tracing::packet::tcp::TcpPacket; use crate::tracing::packet::udp::UdpPacket; -use crate::tracing::packet::IpProtocol; +use crate::tracing::packet::{fmt_payload, IpProtocol}; use crate::tracing::probe::{ ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp, ProbeResponseSeqTcp, ProbeResponseSeqUdp, @@ -187,12 +187,13 @@ pub fn dispatch_tcp_probe( pub fn recv_icmp_probe( recv_socket: &mut Socket, protocol: TracerProtocol, + ipv4_byte_order: platform::PlatformIpv4FieldByteOrder, ) -> TraceResult> { let mut buf = [0_u8; MAX_PACKET_SIZE]; match recv_socket.read(&mut buf) { Ok(_bytes_read) => { let ipv4 = Ipv4Packet::new_view(&buf).req()?; - Ok(extract_probe_resp(protocol, &ipv4)?) + Ok(extract_probe_resp(protocol, ipv4_byte_order, &ipv4)?) } Err(err) => match err.kind() { ErrorKind::WouldBlock => Ok(None), @@ -324,11 +325,24 @@ fn udp_payload_size(packet_size: usize) -> usize { #[instrument] fn extract_probe_resp( protocol: TracerProtocol, + ipv4_byte_order: platform::PlatformIpv4FieldByteOrder, ipv4: &Ipv4Packet<'_>, ) -> TraceResult> { let recv = SystemTime::now(); let src = IpAddr::V4(ipv4.get_source()); - let icmp_v4 = IcmpPacket::new_view(ipv4.payload()).req()?; + + + + + + let total_length = ipv4_byte_order.adjust_length(ipv4.get_total_length()); + let payload_length = (total_length as usize).saturating_sub(ipv4.get_header_length() as usize * 4); + let payload = &ipv4.payload_new()[..payload_length]; + + assert!(payload.len() >= 36, "ipv4 payload must be at least 36 bytes (icmp + nested ip/icmp) but was {}, tla={}, tl={}, pl={}", payload.len(), total_length, ipv4.get_total_length(), fmt_payload(payload)); + + + let icmp_v4 = IcmpPacket::new_view(payload).req()?; Ok(match icmp_v4.get_icmp_type() { IcmpType::TimeExceeded => { let packet = TimeExceededPacket::new_view(icmp_v4.packet()).req()?; @@ -362,25 +376,28 @@ fn extract_probe_resp( #[instrument] fn extract_time_exceeded( - packet: &TimeExceededPacket<'_>, + time_exceeded_packet: &TimeExceededPacket<'_>, protocol: TracerProtocol, ) -> TraceResult { Ok(match protocol { TracerProtocol::Icmp => { - let echo_request = extract_echo_request(packet.payload())?; + let payload = time_exceeded_packet.payload(); + let echo_request = extract_echo_request(payload, time_exceeded_packet.get_length())?; let identifier = echo_request.get_identifier(); let sequence = echo_request.get_sequence(); ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp::new(identifier, sequence)) } TracerProtocol::Udp => { - let packet = TimeExceededPacket::new_view(packet.packet()).req()?; + // TODO needless... + let packet = TimeExceededPacket::new_view(time_exceeded_packet.packet()).req()?; let (src_port, dest_port, checksum, identifier) = extract_udp_packet(packet.payload())?; ProbeResponseSeq::Udp(ProbeResponseSeqUdp::new( identifier, src_port, dest_port, checksum, )) } TracerProtocol::Tcp => { - let packet = TimeExceededPacket::new_view(packet.packet()).req()?; + // TODO needless... + let packet = TimeExceededPacket::new_view(time_exceeded_packet.packet()).req()?; let (src_port, dest_port) = extract_tcp_packet(packet.payload())?; ProbeResponseSeq::Tcp(ProbeResponseSeqTcp::new(src_port, dest_port)) } @@ -389,36 +406,102 @@ fn extract_time_exceeded( #[instrument] fn extract_dest_unreachable( - packet: &DestinationUnreachablePacket<'_>, + dest_unreachable_packet: &DestinationUnreachablePacket<'_>, protocol: TracerProtocol, ) -> TraceResult { Ok(match protocol { TracerProtocol::Icmp => { - let echo_request = extract_echo_request(packet.payload())?; + let echo_request = extract_echo_request(dest_unreachable_packet.payload(), 0 /* TODO */)?; let identifier = echo_request.get_identifier(); let sequence = echo_request.get_sequence(); ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp::new(identifier, sequence)) } TracerProtocol::Udp => { - let (src_port, dest_port, checksum, identifier) = extract_udp_packet(packet.payload())?; + let (src_port, dest_port, checksum, identifier) = extract_udp_packet(dest_unreachable_packet.payload())?; ProbeResponseSeq::Udp(ProbeResponseSeqUdp::new( identifier, src_port, dest_port, checksum, )) } TracerProtocol::Tcp => { - let (src_port, dest_port) = extract_tcp_packet(packet.payload())?; + let (src_port, dest_port) = extract_tcp_packet(dest_unreachable_packet.payload())?; ProbeResponseSeq::Tcp(ProbeResponseSeqTcp::new(src_port, dest_port)) } }) } #[instrument] -fn extract_echo_request(payload: &[u8]) -> TraceResult> { - let ip4 = Ipv4Packet::new_view(payload).req()?; - let header_len = usize::from(ip4.get_header_length() * 4); - let nested_icmp = &payload[header_len..]; - let nested_echo = EchoRequestPacket::new_view(nested_icmp).req()?; - Ok(nested_echo) +fn extract_echo_request(payload: &[u8], length_field: u8) -> TraceResult> { + + + if payload.len() < 20 { + // the nested ICMP payload must be large enough for the IP header (20 bytes) + 8 bytes of the original ICMP packet + // so why do we get less sometimes? + println!("payload to small: {}", payload.len()); + } + + // interpret the payload an an IPv4 packet so we can determine the header length + let ip4 = Ipv4Packet::new_view(payload).expect("foo"); + + + + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Code | Checksum | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | unused | Length | unused | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Internet Header + leading octets of original datagram | + // | | + // | // | + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // the payload here is the ICMP TimeExceeded payload (i.e. the original IPv4 packet header and first 8 octets of the original ICMP packet + + let length_field = length_field as usize; + + let header_len = usize::from(ip4.get_header_length() * 4); + + // the implied length of the original datagram + let actual_length = payload.len() - header_len; + + + let original_datagram_length = + if length_field > 0 { + // extension present, compliant message + length_field + } else if length_field == 0 && actual_length == 16 { + // extension present, non-compliant message, assumed to be 128 octets + 16 + } else { + // no extension present, so the original_datagram_length is the actual length + actual_length + }; + + // Read the original echo request from after the IP header to the end of the original_datagram_length + let nested_echo = EchoRequestPacket::new_view(&payload[header_len..header_len + original_datagram_length]).req()?; + + // now we can read the extension from [header_len + original_datagram_length..header_len + actual_length] + // i.e. until the end of the ICMP payload + // let extension = &payload[header_len + original_datagram_length..actual_length]; + + + + // then have to grab the ICMP extensions + + Ok(nested_echo) + + + // } else { + // let ip4 = Ipv4Packet::new_view(payload).req()?; + // let header_len = usize::from(ip4.get_header_length() * 4); + // let nested_icmp = &payload[header_len..]; + // let nested_echo = EchoRequestPacket::new_view(nested_icmp).req()?; + // Ok(nested_echo) + // } + + } /// Get the src and dest ports from the original `UdpPacket` packet embedded in the payload. diff --git a/src/tracing/packet.rs b/src/tracing/packet.rs index ab9f56160..3bba1566f 100644 --- a/src/tracing/packet.rs +++ b/src/tracing/packet.rs @@ -21,7 +21,7 @@ pub mod udp; /// `TCP` packets. pub mod tcp; -fn fmt_payload(bytes: &[u8]) -> String { +pub fn fmt_payload(bytes: &[u8]) -> String { use itertools::Itertools as _; format!("{:02x}", bytes.iter().format(" ")) } diff --git a/src/tracing/packet/icmpv4.rs b/src/tracing/packet/icmpv4.rs index 577e41ec0..a1b2c9e22 100644 --- a/src/tracing/packet/icmpv4.rs +++ b/src/tracing/packet/icmpv4.rs @@ -637,6 +637,7 @@ pub mod time_exceeded { const TYPE_OFFSET: usize = 0; const CODE_OFFSET: usize = 1; const CHECKSUM_OFFSET: usize = 2; + const LENGTH_OFFSET: usize = 5; /// Represents an ICMP `TimeExceeded` packet. /// @@ -689,6 +690,11 @@ pub mod time_exceeded { u16::from_be_bytes(self.buf.get_bytes(CHECKSUM_OFFSET)) } + #[must_use] + pub fn get_length(&self) -> u8 { + self.buf.read(LENGTH_OFFSET) + } + pub fn set_icmp_type(&mut self, val: IcmpType) { *self.buf.write(TYPE_OFFSET) = val.id(); } @@ -701,6 +707,10 @@ pub mod time_exceeded { self.buf.set_bytes(CHECKSUM_OFFSET, val.to_be_bytes()); } + pub fn set_length(&mut self, val: u8) { + *self.buf.write(LENGTH_OFFSET) = val; + } + pub fn set_payload(&mut self, vals: &[u8]) { let current_offset = Self::minimum_packet_size(); self.buf.as_slice_mut()[current_offset..current_offset + vals.len()] diff --git a/src/tracing/packet/ipv4.rs b/src/tracing/packet/ipv4.rs index f8a48db11..603ef6804 100644 --- a/src/tracing/packet/ipv4.rs +++ b/src/tracing/packet/ipv4.rs @@ -73,6 +73,7 @@ impl<'a> Ipv4Packet<'a> { #[must_use] pub fn get_total_length(&self) -> u16 { + // TODO this is in the wrong byte order u16::from_be_bytes(self.buf.get_bytes(TOTAL_LENGTH_OFFSET)) } @@ -195,18 +196,35 @@ impl<'a> Ipv4Packet<'a> { self.buf.as_slice() } + // payload that includes trailing garbage #[must_use] - pub fn payload(&self) -> &[u8] { - let start = Self::minimum_packet_size() + ipv4_options_length(self); - let end = std::cmp::min( - Self::minimum_packet_size() + ipv4_options_length(self) + ipv4_payload_length(self), - self.buf.as_slice().len(), - ); - if self.buf.as_slice().len() <= start { - return &[]; - } - &self.buf.as_slice()[start..end] - } + pub fn payload_new(&self) -> &[u8] { + let start = Ipv4Packet::minimum_packet_size() + ipv4_options_length(self); + &self.buf.as_slice()[start..] + } + + + // #[must_use] + // pub fn payload(&self) -> &[u8] { + // + // println!("{:?}", self); + // println!("{}", fmt_payload(self.buf.as_slice())); + // + // + // let opt_len = ipv4_options_length(self); + // let payload_len = ipv4_payload_length(self); + // + // let start = Self::minimum_packet_size() + opt_len; + // let opt_len = ipv4_options_length(self); + // let end = std::cmp::min( + // Self::minimum_packet_size() + opt_len + payload_len, + // self.buf.as_slice().len(), + // ); + // if self.buf.as_slice().len() <= start { + // return &[]; + // } + // &self.buf.as_slice()[start..end] + // } } fn ipv4_options_length(ipv4: &Ipv4Packet<'_>) -> usize { @@ -214,7 +232,8 @@ fn ipv4_options_length(ipv4: &Ipv4Packet<'_>) -> usize { } fn ipv4_payload_length(ipv4: &Ipv4Packet<'_>) -> usize { - (ipv4.get_total_length() as usize).saturating_sub(ipv4.get_header_length() as usize * 4) + let total = ipv4.get_total_length(); + (total as usize).saturating_sub(ipv4.get_header_length() as usize * 4) } impl Debug for Ipv4Packet<'_> { @@ -236,7 +255,7 @@ impl Debug for Ipv4Packet<'_> { .field("source", &self.get_source()) .field("destination", &self.get_destination()) .field("options_raw", &self.get_options_raw()) - .field("payload", &fmt_payload(self.payload())) + .field("payload", &fmt_payload(self.payload_new())) .finish() } }