From ff06515400156e481b7d74bac48b90fc9399196f Mon Sep 17 00:00:00 2001 From: FujiApple Date: Sat, 20 Apr 2024 17:41:06 +0800 Subject: [PATCH] feat: record icmp packet code (#734) --- src/frontend/render/table.rs | 6 ++--- src/tracing/net/ipv4.rs | 50 ++++++++++++++++++++++++---------- src/tracing/net/ipv6.rs | 52 ++++++++++++++++++++++++++---------- src/tracing/probe.rs | 16 ++++++----- src/tracing/tracer.rs | 50 ++++++++++++++++++++++++---------- 5 files changed, 123 insertions(+), 51 deletions(-) diff --git a/src/frontend/render/table.rs b/src/frontend/render/table.rs index 5e3b53eb..c2d2a3aa 100644 --- a/src/frontend/render/table.rs +++ b/src/frontend/render/table.rs @@ -213,9 +213,9 @@ fn render_status_cell(hop: &Hop, is_target: bool) -> Cell<'static> { fn render_icmp_packet_type_cell(icmp_packet_type: Option) -> Cell<'static> { match icmp_packet_type { None => Cell::from("n/a"), - Some(IcmpPacketType::TimeExceeded) => Cell::from("TE"), - Some(IcmpPacketType::EchoReply) => Cell::from("ER"), - Some(IcmpPacketType::Unreachable) => Cell::from("DU"), + Some(IcmpPacketType::TimeExceeded(_)) => Cell::from("TE"), + Some(IcmpPacketType::EchoReply(_)) => Cell::from("ER"), + Some(IcmpPacketType::Unreachable(_)) => Cell::from("DU"), Some(IcmpPacketType::NotApplicable) => Cell::from("NA"), } } diff --git a/src/tracing/net/ipv4.rs b/src/tracing/net/ipv4.rs index c9c88952..239236b6 100644 --- a/src/tracing/net/ipv4.rs +++ b/src/tracing/net/ipv4.rs @@ -15,8 +15,8 @@ use crate::tracing::packet::tcp::TcpPacket; use crate::tracing::packet::udp::UdpPacket; use crate::tracing::packet::IpProtocol; use crate::tracing::probe::{ - Extensions, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp, - ProbeResponseSeqTcp, ProbeResponseSeqUdp, + Extensions, IcmpPacketCode, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq, + ProbeResponseSeqIcmp, ProbeResponseSeqTcp, ProbeResponseSeqUdp, }; use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId, TypeOfService}; use crate::tracing::{Flags, Port, PrivilegeMode, Protocol}; @@ -258,6 +258,7 @@ pub fn recv_tcp_socket( let error_addr = tcp_socket.icmp_error_info()?; return Ok(Some(ProbeResponse::TimeExceeded( ProbeResponseData::new(SystemTime::now(), error_addr, resp_seq), + IcmpPacketCode(1), None, ))); } @@ -376,6 +377,7 @@ fn extract_probe_resp( extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| { ProbeResponse::TimeExceeded( ProbeResponseData::new(recv, src, resp_seq), + IcmpPacketCode(icmp_code.0), extension, ) }) @@ -395,6 +397,7 @@ fn extract_probe_resp( extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| { ProbeResponse::DestinationUnreachable( ProbeResponseData::new(recv, src, resp_seq), + IcmpPacketCode(icmp_code.0), extension, ) }) @@ -405,9 +408,10 @@ fn extract_probe_resp( let id = packet.get_identifier(); let seq = packet.get_sequence(); let resp_seq = ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp::new(id, seq)); - Some(ProbeResponse::EchoReply(ProbeResponseData::new( - recv, src, resp_seq, - ))) + Some(ProbeResponse::EchoReply( + ProbeResponseData::new(recv, src, resp_seq), + IcmpPacketCode(icmp_code.0), + )) } Protocol::Udp | Protocol::Tcp => None, }, @@ -1039,15 +1043,18 @@ mod tests { )? .unwrap(); - let ProbeResponse::EchoReply(ProbeResponseData { - addr, - resp_seq: - ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp { - identifier, - sequence, - }), - .. - }) = resp + let ProbeResponse::EchoReply( + ProbeResponseData { + addr, + resp_seq: + ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp { + identifier, + sequence, + }), + .. + }, + icmp_code, + ) = resp else { panic!("expected EchoReply") }; @@ -1057,6 +1064,7 @@ mod tests { ); assert_eq!(30167, identifier); assert_eq!(33049, sequence); + assert_eq!(IcmpPacketCode(0), icmp_code); Ok(()) } @@ -1095,6 +1103,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1106,6 +1115,7 @@ mod tests { ); assert_eq!(30167, identifier); assert_eq!(33047, sequence); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1142,6 +1152,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1150,6 +1161,7 @@ mod tests { assert_eq!(IpAddr::V4(Ipv4Addr::from_str("20.0.0.254").unwrap()), addr); assert_eq!(31489, identifier); assert_eq!(33060, sequence); + assert_eq!(IcmpPacketCode(1), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1190,6 +1202,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1206,6 +1219,7 @@ mod tests { assert_eq!(58571, checksum); assert_eq!(56, payload_len); assert!(!has_magic); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1246,6 +1260,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1262,6 +1277,7 @@ mod tests { assert_eq!(10913, checksum); assert_eq!(56, payload_len); assert!(!has_magic); + assert_eq!(IcmpPacketCode(10), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1297,6 +1313,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1312,6 +1329,7 @@ mod tests { ); assert_eq!(33021, src_port); assert_eq!(80, dest_port); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1347,6 +1365,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1359,6 +1378,7 @@ mod tests { ); assert_eq!(33010, src_port); assert_eq!(10011, dest_port); + assert_eq!(IcmpPacketCode(10), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1535,6 +1555,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1543,6 +1564,7 @@ mod tests { assert_eq!(dest_addr, addr); assert_eq!(33000, src_port); assert_eq!(80, dest_port); + assert_eq!(IcmpPacketCode(1), icmp_code); assert_eq!(None, extensions); Ok(()) } diff --git a/src/tracing/net/ipv6.rs b/src/tracing/net/ipv6.rs index 31af3d42..d411dffe 100644 --- a/src/tracing/net/ipv6.rs +++ b/src/tracing/net/ipv6.rs @@ -14,8 +14,8 @@ use crate::tracing::packet::tcp::TcpPacket; use crate::tracing::packet::udp::UdpPacket; use crate::tracing::packet::IpProtocol; use crate::tracing::probe::{ - Extensions, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp, - ProbeResponseSeqTcp, ProbeResponseSeqUdp, + Extensions, IcmpPacketCode, Probe, ProbeResponse, ProbeResponseData, ProbeResponseSeq, + ProbeResponseSeqIcmp, ProbeResponseSeqTcp, ProbeResponseSeqUdp, }; use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId}; use crate::tracing::{Flags, Port, PrivilegeMode, Protocol}; @@ -248,6 +248,7 @@ pub fn recv_tcp_socket( let error_addr = tcp_socket.icmp_error_info()?; return Ok(Some(ProbeResponse::TimeExceeded( ProbeResponseData::new(SystemTime::now(), error_addr, resp_seq), + IcmpPacketCode(1), None, ))); } @@ -338,6 +339,7 @@ fn extract_probe_resp( extract_probe_resp_seq(&nested_ipv6, protocol)?.map(|resp_seq| { ProbeResponse::TimeExceeded( ProbeResponseData::new(recv, ip, resp_seq), + IcmpPacketCode(icmp_code.0), extension, ) }) @@ -357,6 +359,7 @@ fn extract_probe_resp( extract_probe_resp_seq(&nested_ipv6, protocol)?.map(|resp_seq| { ProbeResponse::DestinationUnreachable( ProbeResponseData::new(recv, ip, resp_seq), + IcmpPacketCode(icmp_code.0), extension, ) }) @@ -367,9 +370,10 @@ fn extract_probe_resp( let id = packet.get_identifier(); let seq = packet.get_sequence(); let resp_seq = ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp::new(id, seq)); - Some(ProbeResponse::EchoReply(ProbeResponseData::new( - recv, ip, resp_seq, - ))) + Some(ProbeResponse::EchoReply( + ProbeResponseData::new(recv, ip, resp_seq), + IcmpPacketCode(icmp_code.0), + )) } Protocol::Udp | Protocol::Tcp => None, }, @@ -1014,21 +1018,25 @@ mod tests { )? .unwrap(); - let ProbeResponse::EchoReply(ProbeResponseData { - addr, - resp_seq: - ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp { - identifier, - sequence, - }), - .. - }) = resp + let ProbeResponse::EchoReply( + ProbeResponseData { + addr, + resp_seq: + ProbeResponseSeq::Icmp(ProbeResponseSeqIcmp { + identifier, + sequence, + }), + .. + }, + icmp_code, + ) = resp else { panic!("expected EchoReply") }; assert_eq!(recv_from_addr, addr); assert_eq!(21945, identifier); assert_eq!(33062, sequence); + assert_eq!(IcmpPacketCode(0), icmp_code); Ok(()) } @@ -1071,6 +1079,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1079,6 +1088,7 @@ mod tests { assert_eq!(recv_from_addr, addr); assert_eq!(21945, identifier); assert_eq!(33056, sequence); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1122,6 +1132,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1130,6 +1141,7 @@ mod tests { assert_eq!(recv_from_addr, addr); assert_eq!(22437, identifier); assert_eq!(33005, sequence); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1174,6 +1186,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1190,6 +1203,7 @@ mod tests { assert_eq!(53489, checksum); assert_eq!(36, payload_len); assert!(!has_magic); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1234,6 +1248,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1250,6 +1265,7 @@ mod tests { assert_eq!(37906, checksum); assert_eq!(36, payload_len); assert!(!has_magic); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1300,6 +1316,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1316,6 +1333,7 @@ mod tests { assert_eq!(39490, checksum); assert_eq!(5, payload_len); assert!(has_magic); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1356,6 +1374,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1368,6 +1387,7 @@ mod tests { ); assert_eq!(33038, src_port); assert_eq!(80, dest_port); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1408,6 +1428,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1420,6 +1441,7 @@ mod tests { ); assert_eq!(33060, src_port); assert_eq!(123, dest_port); + assert_eq!(IcmpPacketCode(0), icmp_code); assert_eq!(None, extensions); Ok(()) } @@ -1609,6 +1631,7 @@ mod tests { }), .. }, + icmp_code, extensions, ) = resp else { @@ -1617,6 +1640,7 @@ mod tests { assert_eq!(dest_addr, addr); assert_eq!(33000, src_port); assert_eq!(80, dest_port); + assert_eq!(IcmpPacketCode(1), icmp_code); assert_eq!(None, extensions); Ok(()) } diff --git a/src/tracing/probe.rs b/src/tracing/probe.rs index 2234017e..0c2ddf83 100644 --- a/src/tracing/probe.rs +++ b/src/tracing/probe.rs @@ -120,21 +120,25 @@ pub struct ProbeComplete { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IcmpPacketType { /// TimeExceeded packet. - TimeExceeded, + TimeExceeded(IcmpPacketCode), /// EchoReply packet. - EchoReply, + EchoReply(IcmpPacketCode), /// Unreachable packet. - Unreachable, + Unreachable(IcmpPacketCode), /// Non-ICMP response (i.e. for some `UDP` & `TCP` probes). NotApplicable, } +/// The code of `TimeExceeded`, `EchoReply` and `Unreachable` ICMP packets. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IcmpPacketCode(pub u8); + /// The response to a probe. #[derive(Debug, Clone)] pub enum ProbeResponse { - TimeExceeded(ProbeResponseData, Option), - DestinationUnreachable(ProbeResponseData, Option), - EchoReply(ProbeResponseData), + TimeExceeded(ProbeResponseData, IcmpPacketCode, Option), + DestinationUnreachable(ProbeResponseData, IcmpPacketCode, Option), + EchoReply(ProbeResponseData, IcmpPacketCode), TcpReply(ProbeResponseData), TcpRefused(ProbeResponseData), } diff --git a/src/tracing/tracer.rs b/src/tracing/tracer.rs index a87901fb..2ee688eb 100644 --- a/src/tracing/tracer.rs +++ b/src/tracing/tracer.rs @@ -149,25 +149,25 @@ impl)> Tracer { fn recv_response(&self, network: &mut N, st: &mut TracerState) -> TraceResult<()> { let next = network.recv_probe()?; match next { - Some(ProbeResponse::TimeExceeded(data, extensions)) => { + Some(ProbeResponse::TimeExceeded(data, icmp_code, extensions)) => { let (trace_id, sequence, received, host) = self.extract(&data); let is_target = host == self.config.target_addr; if self.check_trace_id(trace_id) && st.in_round(sequence) && self.validate(&data) { st.complete_probe_time_exceeded( - sequence, host, received, is_target, extensions, + sequence, host, received, is_target, icmp_code, extensions, ); } } - Some(ProbeResponse::DestinationUnreachable(data, extensions)) => { + Some(ProbeResponse::DestinationUnreachable(data, icmp_code, extensions)) => { let (trace_id, sequence, received, host) = self.extract(&data); if self.check_trace_id(trace_id) && st.in_round(sequence) && self.validate(&data) { - st.complete_probe_unreachable(sequence, host, received, extensions); + st.complete_probe_unreachable(sequence, host, received, icmp_code, extensions); } } - Some(ProbeResponse::EchoReply(data)) => { + Some(ProbeResponse::EchoReply(data, icmp_code)) => { let (trace_id, sequence, received, host) = self.extract(&data); if self.check_trace_id(trace_id) && st.in_round(sequence) && self.validate(&data) { - st.complete_probe_echo_reply(sequence, host, received); + st.complete_probe_echo_reply(sequence, host, received, icmp_code); } } Some(ProbeResponse::TcpReply(data) | ProbeResponse::TcpRefused(data)) => { @@ -346,6 +346,7 @@ impl)> Tracer { mod tests { use super::*; use crate::tracing::net::MockNetwork; + use crate::tracing::probe::IcmpPacketCode; use crate::tracing::{MaxRounds, Port}; use std::net::Ipv4Addr; use std::num::NonZeroUsize; @@ -377,6 +378,7 @@ mod tests { target_addr, ProbeResponseSeq::Tcp(ProbeResponseSeqTcp::new(target_addr, sequence, 80)), ), + IcmpPacketCode(1), None, ))) }); @@ -415,7 +417,7 @@ mod tests { /// the `TracerState` struct. mod state { use crate::tracing::constants::MAX_SEQUENCE_PER_ROUND; - use crate::tracing::probe::{Extensions, IcmpPacketType, Probe, ProbeState}; + use crate::tracing::probe::{Extensions, IcmpPacketCode, IcmpPacketType, Probe, ProbeState}; use crate::tracing::types::{MaxRounds, Port, Round, Sequence, TimeToLive, TraceId}; use crate::tracing::{Config, Flags, MultipathStrategy, PortDirection, Protocol}; use std::array::from_fn; @@ -718,11 +720,12 @@ mod state { host: IpAddr, received: SystemTime, is_target: bool, + icmp_code: IcmpPacketCode, extensions: Option, ) { self.complete_probe( sequence, - IcmpPacketType::TimeExceeded, + IcmpPacketType::TimeExceeded(icmp_code), host, received, is_target, @@ -737,11 +740,12 @@ mod state { sequence: Sequence, host: IpAddr, received: SystemTime, + icmp_code: IcmpPacketCode, extensions: Option, ) { self.complete_probe( sequence, - IcmpPacketType::Unreachable, + IcmpPacketType::Unreachable(icmp_code), host, received, true, @@ -756,10 +760,11 @@ mod state { sequence: Sequence, host: IpAddr, received: SystemTime, + icmp_code: IcmpPacketCode, ) { self.complete_probe( sequence, - IcmpPacketType::EchoReply, + IcmpPacketType::EchoReply(icmp_code), host, received, true, @@ -942,7 +947,14 @@ mod state { // Update the state of the probe 1 after receiving a TimeExceeded let received_1 = SystemTime::now(); let host = IpAddr::V4(Ipv4Addr::LOCALHOST); - state.complete_probe_time_exceeded(Sequence(33000), host, received_1, false, None); + state.complete_probe_time_exceeded( + Sequence(33000), + host, + received_1, + false, + IcmpPacketCode(1), + None, + ); // Validate the state of the probe 1 after the update let probe_1_fetch = state.probe_at(Sequence(33000)).try_into_complete().unwrap(); @@ -952,7 +964,10 @@ mod state { assert_eq!(probe_1_fetch.received, received_1); assert_eq!(probe_1_fetch.host, host); assert_eq!(probe_1_fetch.sent, sent_1); - assert_eq!(probe_1_fetch.icmp_packet_type, IcmpPacketType::TimeExceeded); + assert_eq!( + probe_1_fetch.icmp_packet_type, + IcmpPacketType::TimeExceeded(IcmpPacketCode(1)) + ); // Validate the TracerState after the update assert_eq!(state.round, Round(0)); @@ -1004,7 +1019,14 @@ mod state { // Update the state of probe 2 after receiving a TimeExceeded let received_2 = SystemTime::now(); let host = IpAddr::V4(Ipv4Addr::LOCALHOST); - state.complete_probe_time_exceeded(Sequence(33001), host, received_2, false, None); + state.complete_probe_time_exceeded( + Sequence(33001), + host, + received_2, + false, + IcmpPacketCode(1), + None, + ); let probe_2_recv = state.probe_at(Sequence(33001)); // Validate the TracerState after the update to probe 2 @@ -1029,7 +1051,7 @@ mod state { // Update the state of probe 3 after receiving a EchoReply let received_3 = SystemTime::now(); let host = IpAddr::V4(Ipv4Addr::LOCALHOST); - state.complete_probe_echo_reply(Sequence(33002), host, received_3); + state.complete_probe_echo_reply(Sequence(33002), host, received_3, IcmpPacketCode(0)); let probe_3_recv = state.probe_at(Sequence(33002)); // Validate the TracerState after the update to probe 3