From 5e97a4729c281afcb065d2096c559ce46a2b357a Mon Sep 17 00:00:00 2001 From: FujiApple Date: Sat, 11 Nov 2023 22:04:20 +0800 Subject: [PATCH] feat(net): added `dublin` tracing strategy support for IPv6/UDP (#272) --- src/config.rs | 19 ++++----------- src/main.rs | 1 + src/tracing/config.rs | 4 ++++ src/tracing/net/channel.rs | 5 +++- src/tracing/net/ipv4.rs | 6 ++++- src/tracing/net/ipv6.rs | 42 ++++++++++++++++++++++++++++++---- src/tracing/probe.rs | 3 +++ src/tracing/tracer.rs | 47 ++++++++++++++++++++++++++++++-------- src/tracing/types.rs | 2 ++ 9 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7bb85940b..3ae9457a8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -585,20 +585,11 @@ impl TrippyConfig { (true, _, _, _) => IpAddrFamily::Ipv4Only, (_, true, _, _) => IpAddrFamily::Ipv6Only, }; - - #[allow(clippy::match_same_arms)] - let multipath_strategy = match (multipath_strategy_cfg, addr_family) { - (MultipathStrategyConfig::Classic, _) => Ok(MultipathStrategy::Classic), - (MultipathStrategyConfig::Paris, _) => Ok(MultipathStrategy::Paris), - ( - MultipathStrategyConfig::Dublin, - IpAddrFamily::Ipv4Only | IpAddrFamily::Ipv4thenIpv6 | IpAddrFamily::Ipv6thenIpv4, - ) => Ok(MultipathStrategy::Dublin), - (MultipathStrategyConfig::Dublin, IpAddrFamily::Ipv6Only) => Err(anyhow!( - "Dublin multipath strategy not implemented for IPv6 yet!" - )), - }?; - + let multipath_strategy = match multipath_strategy_cfg { + MultipathStrategyConfig::Classic => MultipathStrategy::Classic, + MultipathStrategyConfig::Paris => MultipathStrategy::Paris, + MultipathStrategyConfig::Dublin => MultipathStrategy::Dublin, + }; let port_direction = match (protocol, source_port, target_port, multipath_strategy_cfg) { (Protocol::Icmp, _, _, _) => PortDirection::None, (Protocol::Udp, None, None, _) => PortDirection::new_fixed_src(pid.max(1024)), diff --git a/src/main.rs b/src/main.rs index 34cf358cf..16ca2bf65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -295,6 +295,7 @@ fn make_channel_config( target_addr, args.packet_size, args.payload_pattern, + args.initial_sequence, args.tos, args.icmp_extension_parse_mode, args.read_timeout, diff --git a/src/tracing/config.rs b/src/tracing/config.rs index 46bdc1c6d..30b145422 100644 --- a/src/tracing/config.rs +++ b/src/tracing/config.rs @@ -360,6 +360,7 @@ pub struct ChannelConfig { pub target_addr: IpAddr, pub packet_size: PacketSize, pub payload_pattern: PayloadPattern, + pub initial_sequence: Sequence, pub tos: TypeOfService, pub icmp_extension_mode: IcmpExtensionParseMode, pub read_timeout: Duration, @@ -376,6 +377,7 @@ impl ChannelConfig { target_addr: IpAddr, packet_size: u16, payload_pattern: u8, + initial_sequence: u16, tos: u8, icmp_extension_mode: IcmpExtensionParseMode, read_timeout: Duration, @@ -388,6 +390,7 @@ impl ChannelConfig { target_addr, packet_size: PacketSize(packet_size), payload_pattern: PayloadPattern(payload_pattern), + initial_sequence: Sequence(initial_sequence), tos: TypeOfService(tos), icmp_extension_mode, read_timeout, @@ -405,6 +408,7 @@ impl Default for ChannelConfig { target_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED), packet_size: PacketSize(defaults::DEFAULT_STRATEGY_PACKET_SIZE), payload_pattern: PayloadPattern(defaults::DEFAULT_STRATEGY_PAYLOAD_PATTERN), + initial_sequence: Sequence(defaults::DEFAULT_STRATEGY_INITIAL_SEQUENCE), tos: TypeOfService(defaults::DEFAULT_STRATEGY_TOS), icmp_extension_mode: defaults::DEFAULT_ICMP_EXTENSION_PARSE_MODE, read_timeout: defaults::DEFAULT_STRATEGY_READ_TIMEOUT, diff --git a/src/tracing/net/channel.rs b/src/tracing/net/channel.rs index 7eeb6b064..a08e49af2 100644 --- a/src/tracing/net/channel.rs +++ b/src/tracing/net/channel.rs @@ -4,7 +4,7 @@ use crate::tracing::net::socket::Socket; use crate::tracing::net::{ipv4, ipv6, platform, Network}; use crate::tracing::probe::{Probe, ProbeResponse}; use crate::tracing::types::{PacketSize, PayloadPattern, TypeOfService}; -use crate::tracing::{ChannelConfig, Port, PrivilegeMode, Protocol}; +use crate::tracing::{ChannelConfig, Port, PrivilegeMode, Protocol, Sequence}; use arrayvec::ArrayVec; use std::net::IpAddr; use std::time::{Duration, SystemTime}; @@ -25,6 +25,7 @@ pub struct TracerChannel { dest_addr: IpAddr, packet_size: PacketSize, payload_pattern: PayloadPattern, + initial_sequence: Sequence, tos: TypeOfService, icmp_extension_mode: IcmpExtensionParseMode, read_timeout: Duration, @@ -63,6 +64,7 @@ impl TracerChannel { dest_addr: config.target_addr, packet_size: config.packet_size, payload_pattern: config.payload_pattern, + initial_sequence: config.initial_sequence, tos: config.tos, icmp_extension_mode: config.icmp_extension_mode, read_timeout: config.read_timeout, @@ -154,6 +156,7 @@ impl TracerChannel { self.privilege_mode, self.packet_size, self.payload_pattern, + self.initial_sequence, ) } _ => unreachable!(), diff --git a/src/tracing/net/ipv4.rs b/src/tracing/net/ipv4.rs index 58a836019..0d2cfa58d 100644 --- a/src/tracing/net/ipv4.rs +++ b/src/tracing/net/ipv4.rs @@ -431,13 +431,13 @@ fn extract_probe_resp_seq( } (Protocol::Udp, IpProtocol::Udp) => { let (src_port, dest_port, checksum, identifier) = extract_udp_packet(ipv4)?; - Some(ProbeResponseSeq::Udp(ProbeResponseSeqUdp::new( identifier, IpAddr::V4(ipv4.get_destination()), src_port, dest_port, checksum, + 0, ))) } (Protocol::Tcp, IpProtocol::Tcp) => { @@ -1137,6 +1137,7 @@ mod tests { src_port, dest_port, checksum, + payload_len, }), .. }, @@ -1154,6 +1155,7 @@ mod tests { assert_eq!(31829, src_port); assert_eq!(33030, dest_port); assert_eq!(58571, checksum); + assert_eq!(0, payload_len); assert_eq!(None, extensions); Ok(()) } @@ -1189,6 +1191,7 @@ mod tests { src_port, dest_port, checksum, + payload_len, }), .. }, @@ -1206,6 +1209,7 @@ mod tests { assert_eq!(32779, src_port); assert_eq!(33010, dest_port); assert_eq!(10913, checksum); + assert_eq!(0, payload_len); assert_eq!(None, extensions); Ok(()) } diff --git a/src/tracing/net/ipv6.rs b/src/tracing/net/ipv6.rs index c9dd23dbd..1f30f8105 100644 --- a/src/tracing/net/ipv6.rs +++ b/src/tracing/net/ipv6.rs @@ -73,6 +73,7 @@ pub fn dispatch_icmp_probe( Ok(()) } +#[allow(clippy::too_many_arguments)] #[instrument(skip(raw_send_socket, probe))] pub fn dispatch_udp_probe( raw_send_socket: &mut S, @@ -82,6 +83,7 @@ pub fn dispatch_udp_probe( privilege_mode: PrivilegeMode, packet_size: PacketSize, payload_pattern: PayloadPattern, + initial_sequence: Sequence, ) -> TraceResult<()> { let packet_size = usize::from(packet_size.0); if !(MIN_PACKET_SIZE_UDP..=MAX_PACKET_SIZE).contains(&packet_size) { @@ -90,9 +92,14 @@ pub fn dispatch_udp_probe( let payload_size = udp_payload_size(packet_size); let payload = &[payload_pattern.0; MAX_UDP_PAYLOAD_BUF][0..payload_size]; match privilege_mode { - PrivilegeMode::Privileged => { - dispatch_udp_probe_raw(raw_send_socket, probe, src_addr, dest_addr, payload) - } + PrivilegeMode::Privileged => dispatch_udp_probe_raw( + raw_send_socket, + probe, + src_addr, + dest_addr, + payload, + initial_sequence, + ), PrivilegeMode::Unprivileged => { dispatch_udp_probe_non_raw::(probe, src_addr, dest_addr, payload) } @@ -106,11 +113,16 @@ fn dispatch_udp_probe_raw( src_addr: Ipv6Addr, dest_addr: Ipv6Addr, payload: &[u8], + initial_sequence: Sequence, ) -> TraceResult<()> { let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF]; + let dublin_payload = [222_u8; MAX_UDP_PAYLOAD_BUF]; let payload_paris = probe.sequence.0.to_be_bytes(); let payload = if probe.flags.contains(Flags::PARIS_CHECKSUM) { payload_paris.as_slice() + } else if probe.flags.contains(Flags::DUBLIN_CHECKSUM) { + let payload_len = (probe.sequence.0 - initial_sequence.0) + 1; + &dublin_payload[..usize::from(payload_len)] } else { payload }; @@ -371,13 +383,14 @@ fn extract_probe_resp_seq( ))) } (Protocol::Udp, IpProtocol::Udp) => { - let (src_port, dest_port, checksum) = extract_udp_packet(ipv6)?; + let (src_port, dest_port, checksum, payload_len) = extract_udp_packet(ipv6)?; Some(ProbeResponseSeq::Udp(ProbeResponseSeqUdp::new( 0, IpAddr::V6(ipv6.get_destination_address()), src_port, dest_port, checksum, + payload_len, ))) } (Protocol::Tcp, IpProtocol::Tcp) => { @@ -400,12 +413,13 @@ fn extract_echo_request(ipv6: &Ipv6Packet<'_>) -> TraceResult<(u16, u16)> { )) } -fn extract_udp_packet(ipv6: &Ipv6Packet<'_>) -> TraceResult<(u16, u16, u16)> { +fn extract_udp_packet(ipv6: &Ipv6Packet<'_>) -> TraceResult<(u16, u16, u16, u16)> { let udp_packet = UdpPacket::new_view(ipv6.payload())?; Ok(( udp_packet.get_source(), udp_packet.get_destination(), udp_packet.get_checksum(), + udp_packet.get_length() - UdpPacket::minimum_packet_size() as u16, )) } @@ -574,6 +588,7 @@ mod tests { let privilege_mode = PrivilegeMode::Privileged; let packet_size = PacketSize(48); let payload_pattern = PayloadPattern(0x00); + let initial_sequence = Sequence(33000); let expected_send_to_buf = hex_literal::hex!("00 7b 01 c8 00 08 7a ed"); let expected_send_to_addr = SocketAddr::new(IpAddr::V6(dest_addr), 0); @@ -600,6 +615,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, )?; Ok(()) } @@ -612,6 +628,7 @@ mod tests { let privilege_mode = PrivilegeMode::Privileged; let packet_size = PacketSize(56); let payload_pattern = PayloadPattern(0xaa); + let initial_sequence = Sequence(33000); let expected_send_to_buf = hex_literal::hex!( " 00 7b 01 c8 00 10 d0 32 aa aa aa aa aa aa aa aa @@ -642,6 +659,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, )?; Ok(()) } @@ -659,6 +677,7 @@ mod tests { // fixed two byte payload is used to hold the sequence let packet_size = PacketSize(300); let payload_pattern = PayloadPattern(0xaa); + let initial_sequence = Sequence(33000); let expected_send_to_buf = hex_literal::hex!( " 00 7b 01 c8 00 0a 80 e8 fa 00 @@ -689,6 +708,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, )?; Ok(()) } @@ -702,6 +722,7 @@ mod tests { let privilege_mode = PrivilegeMode::Unprivileged; let packet_size = PacketSize(48); let payload_pattern = PayloadPattern(0x00); + let initial_sequence = Sequence(33000); let expected_send_to_buf = hex_literal::hex!(""); let expected_send_to_addr = SocketAddr::new(IpAddr::V6(dest_addr), 456); let expected_bind_addr = SocketAddr::new(IpAddr::V6(src_addr), 123); @@ -744,6 +765,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, )?; Ok(()) } @@ -757,6 +779,7 @@ mod tests { let privilege_mode = PrivilegeMode::Unprivileged; let packet_size = PacketSize(56); let payload_pattern = PayloadPattern(0x1f); + let initial_sequence = Sequence(33000); let expected_send_to_buf = hex_literal::hex!("1f 1f 1f 1f 1f 1f 1f 1f"); let expected_send_to_addr = SocketAddr::new(IpAddr::V6(dest_addr), 456); let expected_bind_addr = SocketAddr::new(IpAddr::V6(src_addr), 123); @@ -799,6 +822,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, )?; Ok(()) } @@ -811,6 +835,7 @@ mod tests { let privilege_mode = PrivilegeMode::Privileged; let packet_size = PacketSize(47); let payload_pattern = PayloadPattern(0x00); + let initial_sequence = Sequence(33000); let mut mocket = MockSocket::new(); let err = dispatch_udp_probe( &mut mocket, @@ -820,6 +845,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, ) .unwrap_err(); assert!(matches!(err, TracerError::InvalidPacketSize(_))); @@ -834,6 +860,7 @@ mod tests { let privilege_mode = PrivilegeMode::Privileged; let packet_size = PacketSize(1025); let payload_pattern = PayloadPattern(0x00); + let initial_sequence = Sequence(33000); let mut mocket = MockSocket::new(); let err = dispatch_udp_probe( &mut mocket, @@ -843,6 +870,7 @@ mod tests { privilege_mode, packet_size, payload_pattern, + initial_sequence, ) .unwrap_err(); assert!(matches!(err, TracerError::InvalidPacketSize(_))); @@ -1068,6 +1096,7 @@ mod tests { src_port, dest_port, checksum, + payload_len, }), .. }, @@ -1085,6 +1114,7 @@ mod tests { assert_eq!(22694, src_port); assert_eq!(33029, dest_port); assert_eq!(53489, checksum); + assert_eq!(36, payload_len); assert_eq!(None, extensions); Ok(()) } @@ -1124,6 +1154,7 @@ mod tests { src_port, dest_port, checksum, + payload_len, }), .. }, @@ -1141,6 +1172,7 @@ mod tests { assert_eq!(26477, src_port); assert_eq!(33118, dest_port); assert_eq!(37906, checksum); + assert_eq!(36, payload_len); assert_eq!(None, extensions); Ok(()) } diff --git a/src/tracing/probe.rs b/src/tracing/probe.rs index f9d659f7b..fea285b05 100644 --- a/src/tracing/probe.rs +++ b/src/tracing/probe.rs @@ -231,6 +231,7 @@ pub struct ProbeResponseSeqUdp { pub src_port: u16, pub dest_port: u16, pub checksum: u16, + pub payload_len: u16, } impl ProbeResponseSeqUdp { @@ -240,6 +241,7 @@ impl ProbeResponseSeqUdp { src_port: u16, dest_port: u16, checksum: u16, + payload_len: u16, ) -> Self { Self { identifier, @@ -247,6 +249,7 @@ impl ProbeResponseSeqUdp { src_port, dest_port, checksum, + payload_len, } } } diff --git a/src/tracing/tracer.rs b/src/tracing/tracer.rs index 658413bfc..b40d9170e 100644 --- a/src/tracing/tracer.rs +++ b/src/tracing/tracer.rs @@ -300,13 +300,21 @@ impl)> Tracer { src_port, dest_port, checksum, + payload_len, .. }) => { - let sequence = match (self.config.multipath_strategy, self.config.port_direction) { - (MultipathStrategy::Classic, PortDirection::FixedDest(_)) => src_port, - (MultipathStrategy::Classic, _) => dest_port, - (MultipathStrategy::Paris, _) => checksum, - (MultipathStrategy::Dublin, _) => identifier, + let sequence = match ( + self.config.multipath_strategy, + self.config.port_direction, + self.config.target_addr, + ) { + (MultipathStrategy::Classic, PortDirection::FixedDest(_), _) => src_port, + (MultipathStrategy::Classic, _, _) => dest_port, + (MultipathStrategy::Paris, _, _) => checksum, + (MultipathStrategy::Dublin, _, IpAddr::V4(_)) => identifier, + (MultipathStrategy::Dublin, _, IpAddr::V6(_)) => { + self.config.initial_sequence.0 + payload_len - 1 + } }; (TraceId(0), Sequence(sequence), resp.recv, resp.addr) } @@ -663,19 +671,19 @@ mod state { Port(src_port.0), Port(round_port), TraceId(self.sequence.0), - Flags::empty(), + Flags::DUBLIN_CHECKSUM, ), PortDirection::FixedDest(dest_port) => ( Port(round_port), Port(dest_port.0), TraceId(self.sequence.0), - Flags::empty(), + Flags::DUBLIN_CHECKSUM, ), PortDirection::FixedBoth(src_port, dest_port) => ( Port(src_port.0), Port(dest_port.0), TraceId(self.sequence.0), - Flags::empty(), + Flags::DUBLIN_CHECKSUM, ), PortDirection::None => unimplemented!(), } @@ -850,7 +858,7 @@ mod state { /// wrapping during a round, which is more problematic. #[instrument(skip(self))] pub fn advance_round(&mut self, first_ttl: TimeToLive) { - if self.sequence >= MAX_SEQUENCE { + if self.sequence >= self.max_sequence() { self.sequence = self.config.initial_sequence; } self.target_found = false; @@ -861,6 +869,27 @@ mod state { self.round += Round(1); self.ttl = first_ttl; } + + /// The maximum sequence number allowed. + /// + /// The Dublin multipath strategy for IPv6/udp encodes the sequence + /// number in the payload length and consequently the maximum sequence + /// number must be no larger than the maximum IPv6/udp payload size. + /// + /// It is important that the range of possible sequence numbers is + /// _at least_ `BUFFER_SIZE` to ensure delayed responses from a prior + /// round are not incorrectly associated with later rounds (see + /// `in_round` function). + fn max_sequence(&self) -> Sequence { + // TODO set as 762 = 254 * 3 (3 full rounds) + const MAX_SEQUENCE_DUBLIN: Sequence = Sequence(762); + match (self.config.multipath_strategy, self.config.target_addr) { + (MultipathStrategy::Dublin, IpAddr::V6(_)) => { + self.config.initial_sequence + MAX_SEQUENCE_DUBLIN + } + _ => MAX_SEQUENCE, + } + } } #[cfg(test)] diff --git a/src/tracing/types.rs b/src/tracing/types.rs index 39f38a4eb..4eb63b87a 100644 --- a/src/tracing/types.rs +++ b/src/tracing/types.rs @@ -48,6 +48,8 @@ bitflags! { pub struct Flags: u32 { /// Swap the checksum and payload (UDP only). const PARIS_CHECKSUM = 1; + /// TODO + const DUBLIN_CHECKSUM = 2; } }