From 848ceebaef7f1b77a2f609d5a8de8af3ba573af3 Mon Sep 17 00:00:00 2001 From: FujiApple Date: Sat, 22 Jul 2023 14:10:44 +0800 Subject: [PATCH] feat(net): add support for --unprivileged mode on MacOS (#101) --- src/config.rs | 67 ++++++++++++++++++--- src/config/cmd.rs | 4 ++ src/config/constants.rs | 3 + src/config/file.rs | 1 + src/frontend/render/header.rs | 15 ++++- src/main.rs | 7 ++- src/platform.rs | 1 - src/tracing.rs | 4 +- src/tracing/config.rs | 21 +++++++ src/tracing/net/channel.rs | 33 ++++++----- src/tracing/net/ipv4.rs | 77 ++++++++++++++++++------ src/tracing/net/ipv6.rs | 60 +++++++++++++++++-- src/tracing/net/platform/unix.rs | 91 ++++++++++++++++++++--------- src/tracing/net/platform/windows.rs | 86 +++++++++++++++++---------- src/tracing/net/socket.rs | 12 ++-- trippy-config-sample.toml | 7 +++ 16 files changed, 373 insertions(+), 116 deletions(-) diff --git a/src/config.rs b/src/config.rs index d7fbb8f67..8e8cd7195 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,7 +12,9 @@ use std::str::FromStr; use std::time::Duration; use strum::VariantNames; use theme::TuiThemeItem; -use trippy::tracing::{MultipathStrategy, PortDirection, TracerAddrFamily, TracerProtocol}; +use trippy::tracing::{ + MultipathStrategy, PortDirection, PrivilegeMode, TracerAddrFamily, TracerProtocol, +}; mod binding; mod cmd; @@ -212,6 +214,7 @@ pub struct TrippyConfig { pub tui_theme: TuiTheme, pub tui_bindings: TuiBindings, pub mode: Mode, + pub privilege_mode: PrivilegeMode, pub report_cycles: usize, pub geoip_mmdb_file: Option, pub max_rounds: Option, @@ -230,8 +233,8 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { args, &Platform { pid, - has_privileges: is_privileged, - .. + has_privileges, + needs_privileges, }, ) = data; if args.print_tui_theme_items { @@ -268,6 +271,18 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { let cfg_file_dns = cfg_file.dns.unwrap_or_default(); let cfg_file_report = cfg_file.report.unwrap_or_default(); let mode = cfg_layer(args.mode, cfg_file_trace.mode, constants::DEFAULT_MODE); + let unprivileged = cfg_layer_bool_flag( + args.unprivileged, + cfg_file_trace.unprivileged, + constants::DEFAULT_UNPRIVILEGED, + ); + + let privilege_mode = if unprivileged { + PrivilegeMode::Unprivileged + } else { + PrivilegeMode::Privileged + }; + let verbose = args.verbose; let log_format = cfg_layer( args.log_format, @@ -479,8 +494,9 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { Some(n) if n > 0 => Some(n), _ => None, }; - validate_privilege(is_privileged)?; + validate_privilege(privilege_mode, has_privileges, needs_privileges)?; validate_logging(mode, verbose)?; + validate_strategy(multipath_strategy, unprivileged)?; validate_multi(mode, protocol, &args.targets)?; validate_ttl(first_ttl, max_ttl)?; validate_max_inflight(max_inflight)?; @@ -535,6 +551,7 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { tui_theme, tui_bindings, mode, + privilege_mode, report_cycles, geoip_mmdb_file, max_rounds, @@ -572,11 +589,30 @@ fn cfg_layer_bool_flag(fst: bool, snd: Option, default: bool) -> bool { } } -fn validate_privilege(is_privileged: bool) -> anyhow::Result<()> { - if is_privileged { - Ok(()) - } else { - Err(anyhow!("privileges are required to use raw sockets, see https://github.com/fujiapple852/trippy#privileges")) +fn validate_privilege( + privilege_mode: PrivilegeMode, + has_privileges: bool, + needs_privileges: bool, +) -> anyhow::Result<()> { + const PRIVILEGE_URL: &str = "https://github.com/fujiapple852/trippy#privileges"; + match (privilege_mode, has_privileges, needs_privileges) { + (PrivilegeMode::Privileged, true, _) | (PrivilegeMode::Unprivileged, _, false) => Ok(()), + (PrivilegeMode::Privileged, false, true) => Err(anyhow!(format!( + "privileges are required\n\nsee {} for details", + PRIVILEGE_URL + ))), + (PrivilegeMode::Privileged, false, false) => Err(anyhow!(format!( + "privileges are required (hint: try adding -u to run in unprivileged mode)\n\nsee {} for details", + PRIVILEGE_URL + ))), + (PrivilegeMode::Unprivileged, false, true) => Err(anyhow!(format!( + "unprivileged mode not supported on this platform\n\nsee {} for details", + PRIVILEGE_URL + ))), + (PrivilegeMode::Unprivileged, true, true) => Err(anyhow!(format!( + "unprivileged mode not supported on this platform (hint: process is privileged so disable unprivileged mode)\n\nsee {} for details", + PRIVILEGE_URL + ))), } } @@ -588,6 +624,19 @@ fn validate_logging(mode: Mode, verbose: bool) -> anyhow::Result<()> { } } +/// Validate the tracing strategy against the privilege mode. +fn validate_strategy(strategy: MultipathStrategy, unprivileged: bool) -> anyhow::Result<()> { + match (strategy, unprivileged) { + (MultipathStrategy::Dublin, true) => Err(anyhow!( + "Dublin tracing strategy cannot be used in unprivileged mode" + )), + (MultipathStrategy::Paris, true) => Err(anyhow!( + "Paris tracing strategy cannot be used in unprivileged mode" + )), + _ => Ok(()), + } +} + /// We only allow multiple targets to be specified for the Tui and for `Icmp` tracing. fn validate_multi(mode: Mode, protocol: TracerProtocol, targets: &[String]) -> anyhow::Result<()> { match (mode, protocol) { diff --git a/src/config/cmd.rs b/src/config/cmd.rs index c6211c6e2..b1ffe5268 100644 --- a/src/config/cmd.rs +++ b/src/config/cmd.rs @@ -25,6 +25,10 @@ pub struct Args { #[arg(value_enum, short = 'm', long)] pub mode: Option, + /// Trace without requiring elevated privileges on supported platforms [default: false] + #[arg(short = 'u', long)] + pub unprivileged: bool, + /// Tracing protocol [default: icmp] #[arg(value_enum, short = 'p', long)] pub protocol: Option, diff --git a/src/config/constants.rs b/src/config/constants.rs index 8d1f20d4f..6d291652a 100644 --- a/src/config/constants.rs +++ b/src/config/constants.rs @@ -13,6 +13,9 @@ pub const MAX_HOPS: usize = u8::MAX as usize; /// The default value for `mode`. pub const DEFAULT_MODE: Mode = Mode::Tui; +/// The default value for `unprivileged`. +pub const DEFAULT_UNPRIVILEGED: bool = false; + /// The default value for `log-format`. pub const DEFAULT_LOG_FORMAT: LogFormat = LogFormat::Pretty; diff --git a/src/config/file.rs b/src/config/file.rs index eeebd595d..a8aab874d 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -85,6 +85,7 @@ pub struct ConfigFile { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct ConfigTrippy { pub mode: Option, + pub unprivileged: Option, pub log_format: Option, pub log_filter: Option, pub log_span_events: Option, diff --git a/src/frontend/render/header.rs b/src/frontend/render/header.rs index 1d7d8656e..54e34db4b 100644 --- a/src/frontend/render/header.rs +++ b/src/frontend/render/header.rs @@ -40,13 +40,22 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) { .block(header_block.clone()) .alignment(Alignment::Right); let protocol = match app.tracer_config().protocol { - TracerProtocol::Icmp => format!("icmp({})", app.tracer_config().addr_family), + TracerProtocol::Icmp => format!( + "icmp({}, {})", + app.tracer_config().addr_family, + app.tracer_config().privilege_mode + ), TracerProtocol::Udp => format!( - "udp({}, {})", + "udp({}, {}, {})", app.tracer_config().addr_family, app.tracer_config().multipath_strategy, + app.tracer_config().privilege_mode + ), + TracerProtocol::Tcp => format!( + "tcp({}, {})", + app.tracer_config().addr_family, + app.tracer_config().privilege_mode ), - TracerProtocol::Tcp => format!("tcp({})", app.tracer_config().addr_family), }; let details = if app.show_hop_details { String::from("on") diff --git a/src/main.rs b/src/main.rs index 0bd3a9f2c..ca6337a75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,11 +31,11 @@ use tracing_chrome::{ChromeLayerBuilder, FlushGuard}; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; -use trippy::tracing::SourceAddr; use trippy::tracing::{ MultipathStrategy, PortDirection, TracerAddrFamily, TracerChannelConfig, TracerConfig, TracerProtocol, }; +use trippy::tracing::{PrivilegeMode, SourceAddr}; mod backend; mod config; @@ -229,6 +229,7 @@ fn make_channel_config( target_addr: IpAddr, ) -> TracerChannelConfig { TracerChannelConfig::new( + args.privilege_mode, args.protocol, args.addr_family, source_addr, @@ -255,6 +256,7 @@ fn make_trace_info( source_addr, target, target_addr, + args.privilege_mode, args.multipath_strategy, args.port_direction, args.protocol, @@ -297,6 +299,7 @@ pub struct TraceInfo { pub source_addr: IpAddr, pub target_hostname: String, pub target_addr: IpAddr, + pub privilege_mode: PrivilegeMode, pub multipath_strategy: MultipathStrategy, pub port_direction: PortDirection, pub protocol: TracerProtocol, @@ -323,6 +326,7 @@ impl TraceInfo { source_addr: IpAddr, target_hostname: String, target_addr: IpAddr, + privilege_mode: PrivilegeMode, multipath_strategy: MultipathStrategy, port_direction: PortDirection, protocol: TracerProtocol, @@ -345,6 +349,7 @@ impl TraceInfo { source_addr, target_hostname, target_addr, + privilege_mode, multipath_strategy, port_direction, protocol, diff --git a/src/platform.rs b/src/platform.rs index 8acb7dc36..38d109cdd 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -9,7 +9,6 @@ pub struct Platform { /// /// Specifically, each platform requires privileges unless it supports the `IPPROTO_ICMP` socket type which _also_ /// allows the `IP_HDRINCL` socket option to be set. - #[allow(dead_code)] pub needs_privileges: bool, } diff --git a/src/tracing.rs b/src/tracing.rs index e17411a79..99ba13d5d 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -11,8 +11,8 @@ mod util; pub mod packet; pub use config::{ - MultipathStrategy, PortDirection, TracerAddrFamily, TracerChannelConfig, TracerConfig, - TracerProtocol, + MultipathStrategy, PortDirection, PrivilegeMode, TracerAddrFamily, TracerChannelConfig, + TracerConfig, TracerProtocol, }; pub use net::channel::TracerChannel; pub use net::source::SourceAddr; diff --git a/src/tracing/config.rs b/src/tracing/config.rs index c68c67bd7..0566d1d87 100644 --- a/src/tracing/config.rs +++ b/src/tracing/config.rs @@ -8,6 +8,24 @@ use std::fmt::{Display, Formatter}; use std::net::IpAddr; use std::time::Duration; +/// The privilege mode. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PrivilegeMode { + /// Privileged mode. + Privileged, + /// Unprivileged mode. + Unprivileged, +} + +impl Display for PrivilegeMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Privileged => write!(f, "privileged"), + Self::Unprivileged => write!(f, "unprivileged"), + } + } +} + /// The address family. #[derive(Debug, Copy, Clone)] pub enum TracerAddrFamily { @@ -149,6 +167,7 @@ impl PortDirection { /// Tracer network channel configuration. #[derive(Debug, Clone)] pub struct TracerChannelConfig { + pub privilege_mode: PrivilegeMode, pub protocol: TracerProtocol, pub addr_family: TracerAddrFamily, pub source_addr: IpAddr, @@ -165,6 +184,7 @@ impl TracerChannelConfig { #[allow(clippy::too_many_arguments)] #[must_use] pub fn new( + privilege_mode: PrivilegeMode, protocol: TracerProtocol, addr_family: TracerAddrFamily, source_addr: IpAddr, @@ -177,6 +197,7 @@ impl TracerChannelConfig { tcp_connect_timeout: Duration, ) -> Self { Self { + privilege_mode, protocol, addr_family, source_addr, diff --git a/src/tracing/net/channel.rs b/src/tracing/net/channel.rs index d7024108a..def3388c7 100644 --- a/src/tracing/net/channel.rs +++ b/src/tracing/net/channel.rs @@ -3,7 +3,9 @@ use crate::tracing::net::socket::Socket; use crate::tracing::net::{ipv4, ipv6, platform, Network}; use crate::tracing::probe::ProbeResponse; use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TypeOfService}; -use crate::tracing::{MultipathStrategy, Probe, TracerChannelConfig, TracerProtocol}; +use crate::tracing::{ + MultipathStrategy, PrivilegeMode, Probe, TracerChannelConfig, TracerProtocol, +}; use arrayvec::ArrayVec; use itertools::Itertools; use std::net::IpAddr; @@ -18,6 +20,7 @@ const MAX_TCP_PROBES: usize = 256; /// A channel for sending and receiving `Probe` packets. pub struct TracerChannel { + privilege_mode: PrivilegeMode, protocol: TracerProtocol, src_addr: IpAddr, ipv4_length_order: platform::PlatformIpv4FieldByteOrder, @@ -45,16 +48,18 @@ impl TracerChannel { config.packet_size.0, ))); } + let raw = config.privilege_mode == PrivilegeMode::Privileged; platform::startup()?; let ipv4_length_order = platform::PlatformIpv4FieldByteOrder::for_address(config.source_addr)?; let send_socket = match config.protocol { - TracerProtocol::Icmp => Some(make_icmp_send_socket(config.source_addr)?), - TracerProtocol::Udp => Some(make_udp_send_socket(config.source_addr)?), + TracerProtocol::Icmp => Some(make_icmp_send_socket(config.source_addr, raw)?), + TracerProtocol::Udp => Some(make_udp_send_socket(config.source_addr, raw)?), TracerProtocol::Tcp => None, }; - let recv_socket = make_recv_socket(config.source_addr)?; + let recv_socket = make_recv_socket(config.source_addr, raw)?; Ok(Self { + privilege_mode: config.privilege_mode, protocol: config.protocol, src_addr: config.source_addr, ipv4_length_order, @@ -137,6 +142,7 @@ impl TracerChannel { probe, src_addr, dest_addr, + self.privilege_mode, self.packet_size, self.payload_pattern, self.multipath_strategy, @@ -149,6 +155,7 @@ impl TracerChannel { probe, src_addr, dest_addr, + self.privilege_mode, self.packet_size, self.payload_pattern, ) @@ -235,27 +242,27 @@ impl TcpProbe { /// Make a socket for sending raw `ICMP` packets. #[instrument] -fn make_icmp_send_socket(addr: IpAddr) -> TraceResult { +fn make_icmp_send_socket(addr: IpAddr, raw: bool) -> TraceResult { Ok(match addr { - IpAddr::V4(_) => S::new_icmp_send_socket_ipv4(), - IpAddr::V6(_) => S::new_icmp_send_socket_ipv6(), + IpAddr::V4(_) => S::new_icmp_send_socket_ipv4(raw), + IpAddr::V6(_) => S::new_icmp_send_socket_ipv6(raw), }?) } /// Make a socket for sending `UDP` packets. #[instrument] -fn make_udp_send_socket(addr: IpAddr) -> TraceResult { +fn make_udp_send_socket(addr: IpAddr, raw: bool) -> TraceResult { Ok(match addr { - IpAddr::V4(_) => S::new_udp_send_socket_ipv4(), - IpAddr::V6(_) => S::new_udp_send_socket_ipv6(), + IpAddr::V4(_) => S::new_udp_send_socket_ipv4(raw), + IpAddr::V6(_) => S::new_udp_send_socket_ipv6(raw), }?) } /// Make a socket for receiving raw `ICMP` packets. #[instrument] -fn make_recv_socket(addr: IpAddr) -> TraceResult { +fn make_recv_socket(addr: IpAddr, raw: bool) -> TraceResult { Ok(match addr { - IpAddr::V4(ipv4addr) => S::new_recv_socket_ipv4(ipv4addr), - IpAddr::V6(ipv6addr) => S::new_recv_socket_ipv6(ipv6addr), + IpAddr::V4(ipv4addr) => S::new_recv_socket_ipv4(ipv4addr, raw), + IpAddr::V6(ipv6addr) => S::new_recv_socket_ipv6(ipv6addr, raw), }?) } diff --git a/src/tracing/net/ipv4.rs b/src/tracing/net/ipv4.rs index 4cb362325..34e8e94e5 100644 --- a/src/tracing/net/ipv4.rs +++ b/src/tracing/net/ipv4.rs @@ -19,7 +19,7 @@ use crate::tracing::probe::{ }; use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId, TypeOfService}; use crate::tracing::util::Required; -use crate::tracing::{MultipathStrategy, Probe, TracerProtocol}; +use crate::tracing::{MultipathStrategy, PrivilegeMode, Probe, TracerProtocol}; use std::io::ErrorKind; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::SystemTime; @@ -88,25 +88,56 @@ pub fn dispatch_udp_probe( probe: Probe, src_addr: Ipv4Addr, dest_addr: Ipv4Addr, + privilege_mode: PrivilegeMode, packet_size: PacketSize, payload_pattern: PayloadPattern, multipath_strategy: MultipathStrategy, ipv4_byte_order: platform::PlatformIpv4FieldByteOrder, ) -> TraceResult<()> { - let mut ipv4_buf = [0_u8; MAX_PACKET_SIZE]; - let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF]; let packet_size = usize::from(packet_size.0); if packet_size > MAX_PACKET_SIZE { return Err(TracerError::InvalidPacketSize(packet_size)); } - let mut payload_buffer = [0_u8; MAX_UDP_PAYLOAD_BUF]; + 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, + multipath_strategy, + ipv4_byte_order, + ), + PrivilegeMode::Unprivileged => { + dispatch_udp_probe_non_raw::(probe, src_addr, dest_addr, payload) + } + } +} + +/// Dispatch a UDP probe using a raw socket with `IP_HDRINCL` set. +/// +/// As `IP_HDRINCL` is set we must supply the IP and UDP headers which allows us to set custom +/// values for certain fields such as the checksum as required by the Paris tracing strategy. +#[allow(clippy::too_many_arguments)] +#[instrument(skip(raw_send_socket, probe))] +fn dispatch_udp_probe_raw( + raw_send_socket: &mut S, + probe: Probe, + src_addr: Ipv4Addr, + dest_addr: Ipv4Addr, + payload: &[u8], + multipath_strategy: MultipathStrategy, + ipv4_byte_order: platform::PlatformIpv4FieldByteOrder, +) -> TraceResult<()> { + let mut ipv4_buf = [0_u8; MAX_PACKET_SIZE]; + let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF]; + let payload_paris = probe.sequence.0.to_be_bytes(); let payload = if multipath_strategy == MultipathStrategy::Paris { - payload_buffer.as_mut_slice()[0..2].copy_from_slice(&probe.sequence.0.to_be_bytes()); - &payload_buffer[..2] + payload_paris.as_slice() } else { - let payload_size = udp_payload_size(packet_size); - payload_buffer.as_mut_slice()[0..payload_size].fill(payload_pattern.0); - &payload_buffer[..payload_size] + payload }; let mut udp = make_udp_packet( &mut udp_buf, @@ -117,7 +148,10 @@ pub fn dispatch_udp_probe( payload, )?; if multipath_strategy == MultipathStrategy::Paris { - swap_checksum_and_payload(&mut udp); + let checksum = udp.get_checksum().to_be_bytes(); + let payload = u16::from_be_bytes(core::array::from_fn(|i| udp.payload()[i])); + udp.set_checksum(payload); + udp.set_payload(&checksum); } let ipv4 = make_ipv4_packet( &mut ipv4_buf, @@ -134,14 +168,21 @@ pub fn dispatch_udp_probe( Ok(()) } -/// Swap the checksum and payload values. -/// -/// Assumes that the payload is 2 bytes in length. -fn swap_checksum_and_payload(udp: &mut UdpPacket<'_>) { - let checksum = udp.get_checksum().to_be_bytes(); - let payload = u16::from_be_bytes(core::array::from_fn(|i| udp.payload()[i])); - udp.set_checksum(payload); - udp.set_payload(&checksum); +/// Dispatch a UDP probe using a new UDP datagram socket. +#[instrument(skip(probe))] +fn dispatch_udp_probe_non_raw( + probe: Probe, + src_addr: Ipv4Addr, + dest_addr: Ipv4Addr, + payload: &[u8], +) -> TraceResult<()> { + let local_addr = SocketAddr::new(IpAddr::V4(src_addr), probe.src_port.0); + let remote_addr = SocketAddr::new(IpAddr::V4(dest_addr), probe.dest_port.0); + let mut socket = S::new_udp_dgram_socket_ipv4()?; + process_result(local_addr, socket.bind(local_addr))?; + socket.set_ttl(u32::from(probe.ttl.0))?; + socket.send_to(payload, remote_addr)?; + Ok(()) } #[instrument(skip(probe))] diff --git a/src/tracing/net/ipv6.rs b/src/tracing/net/ipv6.rs index c8eec4313..9630a21ae 100644 --- a/src/tracing/net/ipv6.rs +++ b/src/tracing/net/ipv6.rs @@ -19,7 +19,7 @@ use crate::tracing::probe::{ }; use crate::tracing::types::{PacketSize, PayloadPattern, Sequence, TraceId}; use crate::tracing::util::Required; -use crate::tracing::{Probe, TracerProtocol}; +use crate::tracing::{PrivilegeMode, Probe, TracerProtocol}; use std::io::ErrorKind; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::time::SystemTime; @@ -67,27 +67,58 @@ pub fn dispatch_icmp_probe( } #[allow(clippy::too_many_arguments)] -#[instrument(skip(udp_send_socket, probe))] +#[instrument(skip(raw_send_socket, probe))] pub fn dispatch_udp_probe( - udp_send_socket: &mut S, + raw_send_socket: &mut S, probe: Probe, src_addr: Ipv6Addr, dest_addr: Ipv6Addr, + privilege_mode: PrivilegeMode, packet_size: PacketSize, payload_pattern: PayloadPattern, ) -> TraceResult<()> { - let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF]; let packet_size = usize::from(packet_size.0); if packet_size > MAX_PACKET_SIZE { return Err(TracerError::InvalidPacketSize(packet_size)); } + let payload_size = udp_payload_size(packet_size); + match privilege_mode { + PrivilegeMode::Privileged => dispatch_udp_probe_raw( + raw_send_socket, + probe, + src_addr, + dest_addr, + payload_size, + payload_pattern, + ), + PrivilegeMode::Unprivileged => dispatch_udp_probe_non_raw::( + probe, + src_addr, + dest_addr, + payload_size, + payload_pattern, + ), + } +} + +#[allow(clippy::too_many_arguments)] +#[instrument(skip(udp_send_socket, probe))] +fn dispatch_udp_probe_raw( + udp_send_socket: &mut S, + probe: Probe, + src_addr: Ipv6Addr, + dest_addr: Ipv6Addr, + payload_size: usize, + payload_pattern: PayloadPattern, +) -> TraceResult<()> { + let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF]; let udp = make_udp_packet( &mut udp_buf, src_addr, dest_addr, probe.src_port.0, probe.dest_port.0, - udp_payload_size(packet_size), + payload_size, payload_pattern, )?; udp_send_socket.set_unicast_hops_v6(probe.ttl.0)?; @@ -99,6 +130,25 @@ pub fn dispatch_udp_probe( Ok(()) } +#[allow(clippy::too_many_arguments)] +#[instrument(skip(probe))] +fn dispatch_udp_probe_non_raw( + probe: Probe, + src_addr: Ipv6Addr, + dest_addr: Ipv6Addr, + payload_size: usize, + payload_pattern: PayloadPattern, +) -> TraceResult<()> { + let payload = &[payload_pattern.0; MAX_UDP_PAYLOAD_BUF][..payload_size]; + let local_addr = SocketAddr::new(IpAddr::V6(src_addr), probe.src_port.0); + let remote_addr = SocketAddr::new(IpAddr::V6(dest_addr), probe.dest_port.0); + let mut socket = S::new_udp_send_socket_ipv6(false)?; + process_result(local_addr, socket.bind(local_addr))?; + socket.set_unicast_hops_v6(probe.ttl.0)?; + socket.send_to(payload, remote_addr)?; + Ok(()) +} + #[instrument(skip(probe))] pub fn dispatch_tcp_probe( probe: Probe, diff --git a/src/tracing/net/platform/unix.rs b/src/tracing/net/platform/unix.rs index a6101d40a..3a895544c 100644 --- a/src/tracing/net/platform/unix.rs +++ b/src/tracing/net/platform/unix.rs @@ -208,43 +208,80 @@ impl SocketImpl { impl Socket for SocketImpl { #[instrument] - fn new_icmp_send_socket_ipv4() -> IoResult { - let socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?; - socket.set_nonblocking(true)?; - socket.set_header_included(true)?; - Ok(socket) + fn new_icmp_send_socket_ipv4(raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?; + socket.set_nonblocking(true)?; + socket.set_header_included(true)?; + Ok(socket) + } else { + let socket = Self::new(Domain::IPV4, Type::DGRAM, Protocol::ICMPV4)?; + socket.set_nonblocking(true)?; + socket.set_header_included(true)?; + Ok(socket) + } } #[instrument] - fn new_icmp_send_socket_ipv6() -> IoResult { - let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?; - socket.set_nonblocking(true)?; - Ok(socket) + fn new_icmp_send_socket_ipv6(raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?; + socket.set_nonblocking(true)?; + Ok(socket) + } else { + let socket = Self::new_dgram_ipv6(Protocol::ICMPV6)?; + socket.set_nonblocking(true)?; + Ok(socket) + } } #[instrument] - fn new_udp_send_socket_ipv4() -> IoResult { - let socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?; - socket.set_nonblocking(true)?; - socket.set_header_included(true)?; - Ok(socket) + fn new_udp_send_socket_ipv4(raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv4(Protocol::from(nix::libc::IPPROTO_RAW))?; + socket.set_nonblocking(true)?; + socket.set_header_included(true)?; + Ok(socket) + } else { + let socket = Self::new_dgram_ipv4(Protocol::UDP)?; + socket.set_nonblocking(true)?; + Ok(socket) + } } #[instrument] - fn new_udp_send_socket_ipv6() -> IoResult { - let socket = Self::new_raw_ipv6(Protocol::UDP)?; - socket.set_nonblocking(true)?; - Ok(socket) + fn new_udp_send_socket_ipv6(raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv6(Protocol::UDP)?; + socket.set_nonblocking(true)?; + Ok(socket) + } else { + let socket = Self::new_dgram_ipv6(Protocol::UDP)?; + socket.set_nonblocking(true)?; + Ok(socket) + } } #[instrument] - fn new_recv_socket_ipv4(addr: Ipv4Addr) -> IoResult { - let socket = Self::new_raw_ipv4(Protocol::ICMPV4)?; - socket.set_nonblocking(true)?; - socket.set_header_included(true)?; - Ok(socket) + fn new_recv_socket_ipv4(addr: Ipv4Addr, raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv4(Protocol::ICMPV4)?; + socket.set_nonblocking(true)?; + socket.set_header_included(true)?; + Ok(socket) + } else { + let socket = Self::new(Domain::IPV4, Type::DGRAM, Protocol::ICMPV4)?; + socket.set_nonblocking(true)?; + Ok(socket) + } } #[instrument] - fn new_recv_socket_ipv6(addr: Ipv6Addr) -> IoResult { - let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?; - socket.set_nonblocking(true)?; - Ok(socket) + fn new_recv_socket_ipv6(addr: Ipv6Addr, raw: bool) -> IoResult { + if raw { + let socket = Self::new_raw_ipv6(Protocol::ICMPV6)?; + socket.set_nonblocking(true)?; + Ok(socket) + } else { + let socket = Self::new_dgram_ipv6(Protocol::ICMPV6)?; + socket.set_nonblocking(true)?; + Ok(socket) + } } #[instrument] fn new_stream_socket_ipv4() -> IoResult { diff --git a/src/tracing/net/platform/windows.rs b/src/tracing/net/platform/windows.rs index 1c59063d9..27cbcd9b2 100644 --- a/src/tracing/net/platform/windows.rs +++ b/src/tracing/net/platform/windows.rs @@ -307,52 +307,76 @@ impl Drop for SocketImpl { #[allow(clippy::cast_possible_wrap)] impl Socket for SocketImpl { #[instrument] - fn new_icmp_send_socket_ipv4() -> IoResult { - let sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?; - sock.set_non_blocking(true)?; - sock.set_header_included(true)?; - Ok(sock) + fn new_icmp_send_socket_ipv4(raw: bool) -> IoResult { + if raw { + let sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?; + sock.set_non_blocking(true)?; + sock.set_header_included(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] - fn new_icmp_send_socket_ipv6() -> IoResult { - let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?; - sock.set_non_blocking(true)?; - Ok(sock) + fn new_icmp_send_socket_ipv6(raw: bool) -> IoResult { + if raw { + let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?; + sock.set_non_blocking(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] - fn new_udp_send_socket_ipv4() -> IoResult { - let sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?; - sock.set_non_blocking(true)?; - sock.set_header_included(true)?; - Ok(sock) + fn new_udp_send_socket_ipv4(raw: bool) -> IoResult { + if raw { + let sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::from(IPPROTO_RAW)))?; + sock.set_non_blocking(true)?; + sock.set_header_included(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] - fn new_udp_send_socket_ipv6() -> IoResult { - let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::UDP))?; - sock.set_non_blocking(true)?; - Ok(sock) + fn new_udp_send_socket_ipv6(raw: bool) -> IoResult { + if raw { + let sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::UDP))?; + sock.set_non_blocking(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] - fn new_recv_socket_ipv4(src_addr: Ipv4Addr) -> IoResult { - let mut sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?; - sock.bind(SocketAddr::new(IpAddr::V4(src_addr), 0))?; - sock.post_recv_from()?; - sock.set_non_blocking(true)?; - sock.set_header_included(true)?; - Ok(sock) + fn new_recv_socket_ipv4(src_addr: Ipv4Addr, raw: bool) -> IoResult { + if raw { + let mut sock = Self::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?; + sock.bind(SocketAddr::new(IpAddr::V4(src_addr), 0))?; + sock.post_recv_from()?; + sock.set_non_blocking(true)?; + sock.set_header_included(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] - fn new_recv_socket_ipv6(src_addr: Ipv6Addr) -> IoResult { - let mut sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?; - sock.bind(SocketAddr::new(IpAddr::V6(src_addr), 0))?; - sock.post_recv_from()?; - sock.set_non_blocking(true)?; - Ok(sock) + fn new_recv_socket_ipv6(src_addr: Ipv6Addr, raw: bool) -> IoResult { + if raw { + let mut sock = Self::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?; + sock.bind(SocketAddr::new(IpAddr::V6(src_addr), 0))?; + sock.post_recv_from()?; + sock.set_non_blocking(true)?; + Ok(sock) + } else { + unimplemented!("non-raw socket is not supported on Windows") + } } #[instrument] diff --git a/src/tracing/net/socket.rs b/src/tracing/net/socket.rs index ae4e8770a..08276d32e 100644 --- a/src/tracing/net/socket.rs +++ b/src/tracing/net/socket.rs @@ -7,17 +7,17 @@ where Self: Sized, { /// Create an IPv4 socket for sending ICMP probes. - fn new_icmp_send_socket_ipv4() -> Result; + fn new_icmp_send_socket_ipv4(raw: bool) -> Result; /// Create an IPv6 socket for sending ICMP probes. - fn new_icmp_send_socket_ipv6() -> Result; + fn new_icmp_send_socket_ipv6(raw: bool) -> Result; /// Create an IPv4 socket for sending UDP probes. - fn new_udp_send_socket_ipv4() -> Result; + fn new_udp_send_socket_ipv4(raw: bool) -> Result; /// Create an IPv6 socket for sending UDP probes. - fn new_udp_send_socket_ipv6() -> Result; + fn new_udp_send_socket_ipv6(raw: bool) -> Result; /// Create an IPv4 socket for receiving UDP probe responses. - fn new_recv_socket_ipv4(addr: Ipv4Addr) -> Result; + fn new_recv_socket_ipv4(addr: Ipv4Addr, raw: bool) -> Result; /// Create an IPv6 socket for receiving UDP probe responses. - fn new_recv_socket_ipv6(addr: Ipv6Addr) -> Result; + fn new_recv_socket_ipv6(addr: Ipv6Addr, raw: bool) -> Result; /// Create a IPv4/TCP socket for sending TCP probes. fn new_stream_socket_ipv4() -> Result; /// Create a IPv6/TCP socket for sending TCP probes. diff --git a/trippy-config-sample.toml b/trippy-config-sample.toml index bbcd30489..174f9cb75 100644 --- a/trippy-config-sample.toml +++ b/trippy-config-sample.toml @@ -32,6 +32,13 @@ # silent - Do not generate any output for N cycles mode = "tui" +# Trace without requiring elevated privileges [default: false] +# +# Enabling will cause IPPROTO_ICMP sockets to be used. +# +# Note: not supported on all platforms. +unprivileged = false + # How to format log data. # # Allowed values are: