Skip to content

Commit

Permalink
exploratory code for supporting icmp extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Oct 1, 2023
1 parent ad1aff2 commit 7826228
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/tracing/net/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl TracerChannel {
fn recv_icmp_probe(&mut self) -> TraceResult<Option<ProbeResponse>> {
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 {
Expand Down
117 changes: 100 additions & 17 deletions src/tracing/net/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Option<ProbeResponse>> {
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),
Expand Down Expand Up @@ -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<Option<ProbeResponse>> {
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()?;
Expand Down Expand Up @@ -362,25 +376,28 @@ fn extract_probe_resp(

#[instrument]
fn extract_time_exceeded(
packet: &TimeExceededPacket<'_>,
time_exceeded_packet: &TimeExceededPacket<'_>,
protocol: TracerProtocol,
) -> TraceResult<ProbeResponseSeq> {
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))
}
Expand All @@ -389,36 +406,102 @@ fn extract_time_exceeded(

#[instrument]
fn extract_dest_unreachable(
packet: &DestinationUnreachablePacket<'_>,
dest_unreachable_packet: &DestinationUnreachablePacket<'_>,
protocol: TracerProtocol,
) -> TraceResult<ProbeResponseSeq> {
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<EchoRequestPacket<'_>> {
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<EchoRequestPacket<'_>> {


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.
Expand Down
2 changes: 1 addition & 1 deletion src/tracing/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(" "))
}
Expand Down
10 changes: 10 additions & 0 deletions src/tracing/packet/icmpv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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();
}
Expand All @@ -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()]
Expand Down
45 changes: 32 additions & 13 deletions src/tracing/packet/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down Expand Up @@ -195,26 +196,44 @@ 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 {
(ipv4.get_header_length() as usize * 4).saturating_sub(Ipv4Packet::minimum_packet_size())
}

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<'_> {
Expand All @@ -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()
}
}
Expand Down

0 comments on commit 7826228

Please sign in to comment.