Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Icmp Extensions v2 #755

Merged
merged 13 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ windows-sys = { version = "0.48.0", features = [
] }

[dev-dependencies]
hex-literal = "0.4.1"
rand = "0.8.5"
test-case = "3.2.1"

Expand Down
9 changes: 8 additions & 1 deletion src/backend/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::collections::HashMap;
use std::iter::once;
use std::net::{IpAddr, Ipv4Addr};
use std::time::Duration;
use trippy::tracing::{Probe, ProbeStatus, TracerRound};
use trippy::tracing::{Extensions, Probe, ProbeStatus, TracerRound};

/// The state of all hops in a trace.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -121,6 +121,7 @@ pub struct Hop {
mean: f64,
m2: f64,
samples: Vec<Duration>,
extensions: Option<Extensions>,
}

impl Hop {
Expand Down Expand Up @@ -200,6 +201,10 @@ impl Hop {
pub fn samples(&self) -> &[Duration] {
&self.samples
}

pub fn extensions(&self) -> Option<&Extensions> {
self.extensions.as_ref()
}
}

impl Default for Hop {
Expand All @@ -216,6 +221,7 @@ impl Default for Hop {
mean: 0f64,
m2: 0f64,
samples: Vec::default(),
extensions: None,
}
}
}
Expand Down Expand Up @@ -319,6 +325,7 @@ impl TraceData {
}
let host = probe.host.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
*hop.addrs.entry(host).or_default() += 1;
hop.extensions = probe.extensions.clone();
}
ProbeStatus::Awaited => {
let index = usize::from(probe.ttl.0) - 1;
Expand Down
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ pub struct TrippyConfig {
pub max_inflight: u8,
pub initial_sequence: u16,
pub tos: u8,
pub icmp_extensions: bool,
pub read_timeout: Duration,
pub packet_size: u16,
pub payload_pattern: u8,
Expand Down Expand Up @@ -377,6 +378,11 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig {
cfg_file_strategy.tos,
constants::DEFAULT_STRATEGY_TOS,
);
let icmp_extensions = cfg_layer_bool_flag(
args.icmp_extensions,
cfg_file_strategy.icmp_extensions,
false,
);
let read_timeout = cfg_layer(
args.read_timeout,
cfg_file_strategy.read_timeout,
Expand Down Expand Up @@ -559,6 +565,7 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig {
packet_size,
payload_pattern,
tos,
icmp_extensions,
source_addr,
interface,
port_direction,
Expand Down
4 changes: 4 additions & 0 deletions src/config/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ pub struct Args {
#[arg(short = 'Q', long)]
pub tos: Option<u8>,

/// Parse ICMP extensions
#[arg(short = 'e', long)]
pub icmp_extensions: bool,

/// The socket read timeout [default: 10ms]
#[arg(long)]
pub read_timeout: Option<String>,
Expand Down
1 change: 1 addition & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub struct ConfigStrategy {
pub packet_size: Option<u16>,
pub payload_pattern: Option<u8>,
pub tos: Option<u8>,
pub icmp_extensions: Option<bool>,
pub read_timeout: Option<String>,
}

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ fn make_channel_config(
args.payload_pattern,
args.multipath_strategy,
args.tos,
args.icmp_extensions,
args.read_timeout,
args.min_round_duration,
)
Expand Down
2 changes: 1 addition & 1 deletion src/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ pub use config::{
pub use net::channel::TracerChannel;
pub use net::source::SourceAddr;
pub use net::SocketImpl;
pub use probe::{IcmpPacketType, Probe, ProbeStatus};
pub use probe::{Extension, Extensions, IcmpPacketType, Probe, ProbeStatus};
pub use tracer::{Tracer, TracerRound};
3 changes: 3 additions & 0 deletions src/tracing/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub struct TracerChannelConfig {
pub payload_pattern: PayloadPattern,
pub multipath_strategy: MultipathStrategy,
pub tos: TypeOfService,
pub icmp_extensions: bool,
pub read_timeout: Duration,
pub tcp_connect_timeout: Duration,
}
Expand All @@ -193,6 +194,7 @@ impl TracerChannelConfig {
payload_pattern: u8,
multipath_strategy: MultipathStrategy,
tos: u8,
icmp_extensions: bool,
read_timeout: Duration,
tcp_connect_timeout: Duration,
) -> Self {
Expand All @@ -206,6 +208,7 @@ impl TracerChannelConfig {
payload_pattern: PayloadPattern(payload_pattern),
multipath_strategy,
tos: TypeOfService(tos),
icmp_extensions,
read_timeout,
tcp_connect_timeout,
}
Expand Down
3 changes: 3 additions & 0 deletions src/tracing/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ mod ipv4;
/// IPv6 implementation.
mod ipv6;

/// ICMP extensions.
mod extension;

/// Platform specific network code.
mod platform;

Expand Down
20 changes: 15 additions & 5 deletions src/tracing/net/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct TracerChannel<S: Socket> {
payload_pattern: PayloadPattern,
multipath_strategy: MultipathStrategy,
tos: TypeOfService,
icmp_extensions: bool,
read_timeout: Duration,
tcp_connect_timeout: Duration,
send_socket: Option<S>,
Expand Down Expand Up @@ -68,6 +69,7 @@ impl<S: Socket> TracerChannel<S> {
payload_pattern: config.payload_pattern,
multipath_strategy: config.multipath_strategy,
tos: config.tos,
icmp_extensions: config.icmp_extensions,
read_timeout: config.read_timeout,
tcp_connect_timeout: config.tcp_connect_timeout,
send_socket,
Expand Down Expand Up @@ -95,7 +97,7 @@ impl<S: Socket> Network for TracerChannel<S> {
resp => Ok(resp),
},
}?;
if let Some(resp) = prob_response {
if let Some(resp) = &prob_response {
tracing::debug!(?resp);
}
Ok(prob_response)
Expand Down Expand Up @@ -170,10 +172,10 @@ impl<S: Socket> TracerChannel<S> {
fn dispatch_tcp_probe(&mut self, probe: Probe) -> TraceResult<()> {
let socket = match (self.src_addr, self.dest_addr) {
(IpAddr::V4(src_addr), IpAddr::V4(dest_addr)) => {
ipv4::dispatch_tcp_probe(probe, src_addr, dest_addr, self.tos)
ipv4::dispatch_tcp_probe(&probe, src_addr, dest_addr, self.tos)
}
(IpAddr::V6(src_addr), IpAddr::V6(dest_addr)) => {
ipv6::dispatch_tcp_probe(probe, src_addr, dest_addr)
ipv6::dispatch_tcp_probe(&probe, src_addr, dest_addr)
}
_ => unreachable!(),
}?;
Expand All @@ -187,8 +189,16 @@ impl<S: Socket> TracerChannel<S> {
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::V6(_) => ipv6::recv_icmp_probe(&mut self.recv_socket, self.protocol),
IpAddr::V4(_) => ipv4::recv_icmp_probe(
&mut self.recv_socket,
self.protocol,
self.icmp_extensions,
),
IpAddr::V6(_) => ipv6::recv_icmp_probe(
&mut self.recv_socket,
self.protocol,
self.icmp_extensions,
),
}
} else {
Ok(None)
Expand Down
66 changes: 66 additions & 0 deletions src/tracing/net/extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::tracing::error::TracerError;
use crate::tracing::packet::icmp_extension::extension_header::ExtensionHeaderPacket;
use crate::tracing::packet::icmp_extension::extension_object::{ClassNum, ExtensionObjectPacket};
use crate::tracing::packet::icmp_extension::extension_structure::ExtensionsPacket;
use crate::tracing::packet::icmp_extension::mpls_label_stack::MplsLabelStackPacket;
use crate::tracing::packet::icmp_extension::mpls_label_stack_member::MplsLabelStackMemberPacket;
use crate::tracing::probe::{Extension, Extensions, MplsLabelStack, MplsLabelStackMember};
use crate::tracing::util::Required;

/// The supported ICMP extension version number.
const ICMP_EXTENSION_VERSION: u8 = 2;

impl TryFrom<&[u8]> for Extensions {
type Error = TracerError;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::try_from(ExtensionsPacket::new_view(value).req()?)
}
}

impl TryFrom<ExtensionsPacket<'_>> for Extensions {
type Error = TracerError;

fn try_from(value: ExtensionsPacket<'_>) -> Result<Self, Self::Error> {
let header = ExtensionHeaderPacket::new_view(value.header()).req()?;
if header.get_version() != ICMP_EXTENSION_VERSION {
return Ok(Self::default());
}
let extensions = value
.objects()
.flat_map(|obj| ExtensionObjectPacket::new_view(obj).req())
.map(|obj| match obj.get_class_num() {
ClassNum::MultiProtocolLabelSwitchingLabelStack => {
MplsLabelStackPacket::new_view(obj.payload())
.req()
.map(|mpls| Extension::Mpls(MplsLabelStack::from(mpls)))
}
_ => Ok(Extension::Unknown),
})
.collect::<Result<_, _>>()?;
Ok(Self { extensions })
}
}

impl From<MplsLabelStackPacket<'_>> for MplsLabelStack {
fn from(value: MplsLabelStackPacket<'_>) -> Self {
Self {
members: value
.members()
.flat_map(|member| MplsLabelStackMemberPacket::new_view(member).req())
.map(MplsLabelStackMember::from)
.collect(),
}
}
}

impl From<MplsLabelStackMemberPacket<'_>> for MplsLabelStackMember {
fn from(value: MplsLabelStackMemberPacket<'_>) -> Self {
Self {
label: value.get_label(),
exp: value.get_exp(),
bos: value.get_bos(),
ttl: value.get_ttl(),
}
}
}
36 changes: 25 additions & 11 deletions src/tracing/net/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp, ProbeResponseSeqTcp,
ProbeResponseSeqUdp,
Extensions, ProbeResponse, ProbeResponseData, ProbeResponseSeq, ProbeResponseSeqIcmp,
ProbeResponseSeqTcp, ProbeResponseSeqUdp,
};
use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId, TypeOfService};
use crate::tracing::util::Required;
Expand Down Expand Up @@ -187,7 +187,7 @@ fn dispatch_udp_probe_non_raw<S: Socket>(

#[instrument(skip(probe))]
pub fn dispatch_tcp_probe<S: Socket>(
probe: Probe,
probe: &Probe,
src_addr: Ipv4Addr,
dest_addr: Ipv4Addr,
tos: TypeOfService,
Expand All @@ -206,12 +206,13 @@ pub fn dispatch_tcp_probe<S: Socket>(
pub fn recv_icmp_probe<S: Socket>(
recv_socket: &mut S,
protocol: TracerProtocol,
icmp_extensions: bool,
) -> 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[..bytes_read]).req()?;
Ok(extract_probe_resp(protocol, &ipv4)?)
Ok(extract_probe_resp(protocol, icmp_extensions, &ipv4)?)
}
Err(err) => match err.kind() {
ErrorKind::WouldBlock => Ok(None),
Expand Down Expand Up @@ -248,11 +249,10 @@ pub fn recv_tcp_socket<S: Socket>(
}
if platform::is_host_unreachable_error(code) {
let error_addr = tcp_socket.icmp_error_info()?;
return Ok(Some(ProbeResponse::TimeExceeded(ProbeResponseData::new(
SystemTime::now(),
error_addr,
resp_seq,
))));
return Ok(Some(ProbeResponse::TimeExceeded(
ProbeResponseData::new(SystemTime::now(), error_addr, resp_seq),
None,
)));
}
}
}
Expand Down Expand Up @@ -343,6 +343,7 @@ fn udp_payload_size(packet_size: usize) -> usize {
#[instrument]
fn extract_probe_resp(
protocol: TracerProtocol,
icmp_extensions: bool,
ipv4: &Ipv4Packet<'_>,
) -> TraceResult<Option<ProbeResponse>> {
let recv = SystemTime::now();
Expand All @@ -352,15 +353,28 @@ fn extract_probe_resp(
IcmpType::TimeExceeded => {
let packet = TimeExceededPacket::new_view(icmp_v4.packet()).req()?;
let nested_ipv4 = Ipv4Packet::new_view(packet.payload()).req()?;
let extension = if icmp_extensions {
packet.extension().map(Extensions::try_from).transpose()?
} else {
None
};
extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| {
ProbeResponse::TimeExceeded(ProbeResponseData::new(recv, src, resp_seq))
ProbeResponse::TimeExceeded(ProbeResponseData::new(recv, src, resp_seq), extension)
})
}
IcmpType::DestinationUnreachable => {
let packet = DestinationUnreachablePacket::new_view(icmp_v4.packet()).req()?;
let nested_ipv4 = Ipv4Packet::new_view(packet.payload()).req()?;
let extension = if icmp_extensions {
packet.extension().map(Extensions::try_from).transpose()?
} else {
None
};
extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| {
ProbeResponse::DestinationUnreachable(ProbeResponseData::new(recv, src, resp_seq))
ProbeResponse::DestinationUnreachable(
ProbeResponseData::new(recv, src, resp_seq),
extension,
)
})
}
IcmpType::EchoReply => match protocol {
Expand Down