Skip to content

Commit

Permalink
feat(net): add support for ping sockets (#101) - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Oct 22, 2023
1 parent a80b126 commit ffc559e
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 86 deletions.
37 changes: 23 additions & 14 deletions src/caps.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// Linux

use trippy::tracing::PrivilegeMode;

#[cfg(target_os = "linux")]
/// Check if `CAP_NET_RAW` is in the permitted set and if so raise it to the effective set.
pub fn ensure_caps() -> anyhow::Result<()> {
if caps::has_cap(None, caps::CapSet::Permitted, caps::Capability::CAP_NET_RAW)? {
caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_NET_RAW)?;
} else {
eprintln!("capability CAP_NET_RAW is required, see https://github.com/fujiapple852/trippy#privileges");
std::process::exit(-1);
pub fn ensure_caps(privilege_mode: PrivilegeMode) -> anyhow::Result<()> {
if privilege_mode == PrivilegeMode::Privileged {
if caps::has_cap(None, caps::CapSet::Permitted, caps::Capability::CAP_NET_RAW)? {
caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_NET_RAW)?;
} else {
eprintln!("capability CAP_NET_RAW is required, see https://github.com/fujiapple852/trippy#privileges");
std::process::exit(-1);
}
}
Ok(())
}
Expand All @@ -23,10 +27,10 @@ pub fn drop_caps() -> anyhow::Result<()> {

#[cfg(all(unix, not(target_os = "linux")))]
#[allow(clippy::unnecessary_wraps)]
/// Ensure the effective user is `root`.
pub fn ensure_caps() -> anyhow::Result<()> {
if !nix::unistd::Uid::effective().is_root() {
eprintln!("root user required to use raw sockets, see https://github.com/fujiapple852/trippy#privileges");
/// Ensure the effective user is privileged, unless we are in unprivileged mode.
pub fn ensure_caps(privilege_mode: PrivilegeMode) -> anyhow::Result<()> {
if privilege_mode == PrivilegeMode::Privileged && !nix::unistd::Uid::effective().is_root() {
eprintln!("privileged user required to use raw sockets (hint: try adding --unprivileged (-u) to run in unprivileged mode), see https://github.com/fujiapple852/trippy#privileges");
std::process::exit(-1);
}
Ok(())
Expand All @@ -45,8 +49,8 @@ pub fn drop_caps() -> anyhow::Result<()> {

#[cfg(windows)]
#[allow(clippy::unnecessary_wraps)]
/// Ensure the effective user is `root`.
pub fn ensure_caps() -> anyhow::Result<()> {
/// Ensure the effective user is an `Administrator`.
pub fn ensure_caps(privilege_mode: PrivilegeMode) -> anyhow::Result<()> {
macro_rules! syscall {
($p: path, $fn: ident ( $($arg: expr),* $(,)* ) ) => {{
#[allow(unsafe_code)]
Expand Down Expand Up @@ -109,8 +113,13 @@ pub fn ensure_caps() -> anyhow::Result<()> {
}
}

if !Privileged::current_process()?.is_elevated()? {
eprintln!("administrator capability is required, see https://github.com/fujiapple852/trippy#privileges");
if privilege_mode == PrivilegeMode::Privileged {
if !Privileged::current_process()?.is_elevated()? {
eprintln!("administrator capability is required, see https://github.com/fujiapple852/trippy#privileges");
std::process::exit(-1);
}
} else {
eprintln!("unprivileged mode is not supported on Windows");
std::process::exit(-1);
}
Ok(())
Expand Down
32 changes: 31 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -211,6 +213,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<String>,
pub max_rounds: Option<usize>,
Expand Down Expand Up @@ -260,6 +263,18 @@ impl TryFrom<(Args, u16)> 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,
Expand Down Expand Up @@ -472,6 +487,7 @@ impl TryFrom<(Args, u16)> for TrippyConfig {
_ => None,
};
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)?;
Expand Down Expand Up @@ -526,6 +542,7 @@ impl TryFrom<(Args, u16)> for TrippyConfig {
tui_theme,
tui_bindings,
mode,
privilege_mode,
report_cycles,
geoip_mmdb_file,
max_rounds,
Expand Down Expand Up @@ -571,6 +588,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) {
Expand Down
4 changes: 4 additions & 0 deletions src/config/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub struct Args {
#[arg(value_enum, short = 'm', long)]
pub mode: Option<Mode>,

/// Trace without requiring elevated privileges (Unix ICMP and UDP only)
#[arg(short = 'u', long)]
pub unprivileged: bool,

/// Tracing protocol [default: icmp]
#[arg(value_enum, short = 'p', long)]
pub protocol: Option<Protocol>,
Expand Down
3 changes: 3 additions & 0 deletions src/config/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub struct ConfigFile {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ConfigTrippy {
pub mode: Option<Mode>,
pub unprivileged: Option<bool>,
pub log_format: Option<LogFormat>,
pub log_filter: Option<String>,
pub log_span_events: Option<LogSpanEvents>,
Expand Down
15 changes: 12 additions & 3 deletions src/frontend/render/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,22 @@ pub fn render<B: Backend>(f: &mut Frame<'_, B>, 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")
Expand Down
9 changes: 7 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,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 caps;
Expand All @@ -50,7 +50,7 @@ fn main() -> anyhow::Result<()> {
let _guard = configure_logging(&cfg);
let resolver = start_dns_resolver(&cfg)?;
let geoip_lookup = create_geoip_lookup(&cfg)?;
ensure_caps()?;
ensure_caps(cfg.privilege_mode)?;
let traces: Vec<_> = cfg
.targets
.iter()
Expand Down Expand Up @@ -227,6 +227,7 @@ fn make_channel_config(
target_addr: IpAddr,
) -> TracerChannelConfig {
TracerChannelConfig::new(
args.privilege_mode,
args.protocol,
args.addr_family,
source_addr,
Expand All @@ -253,6 +254,7 @@ fn make_trace_info(
source_addr,
target,
target_addr,
args.privilege_mode,
args.multipath_strategy,
args.port_direction,
args.protocol,
Expand Down Expand Up @@ -295,6 +297,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,
Expand All @@ -321,6 +324,7 @@ impl TraceInfo {
source_addr: IpAddr,
target_hostname: String,
target_addr: IpAddr,
privilege_mode: PrivilegeMode,
multipath_strategy: MultipathStrategy,
port_direction: PortDirection,
protocol: TracerProtocol,
Expand All @@ -343,6 +347,7 @@ impl TraceInfo {
source_addr,
target_hostname,
target_addr,
privilege_mode,
multipath_strategy,
port_direction,
protocol,
Expand Down
4 changes: 2 additions & 2 deletions src/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions src/tracing/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -177,6 +197,7 @@ impl TracerChannelConfig {
tcp_connect_timeout: Duration,
) -> Self {
Self {
privilege_mode,
protocol,
addr_family,
source_addr,
Expand Down
26 changes: 16 additions & 10 deletions src/tracing/net/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,6 +20,7 @@ const MAX_TCP_PROBES: usize = 256;

/// A channel for sending and receiving `Probe` packets.
pub struct TracerChannel<S: Socket> {
privilege_mode: PrivilegeMode,
protocol: TracerProtocol,
src_addr: IpAddr,
ipv4_length_order: platform::PlatformIpv4FieldByteOrder,
Expand Down Expand Up @@ -45,16 +48,18 @@ impl<S: Socket> TracerChannel<S> {
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,
Expand Down Expand Up @@ -137,6 +142,7 @@ impl<S: Socket> TracerChannel<S> {
probe,
src_addr,
dest_addr,
self.privilege_mode,
self.packet_size,
self.payload_pattern,
self.multipath_strategy,
Expand Down Expand Up @@ -235,27 +241,27 @@ impl<S: Socket> TcpProbe<S> {

/// Make a socket for sending raw `ICMP` packets.
#[instrument]
fn make_icmp_send_socket<S: Socket>(addr: IpAddr) -> TraceResult<S> {
fn make_icmp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> TraceResult<S> {
Ok(match addr {
IpAddr::V4(_) => S::new_icmp_send_socket_ipv4(),
IpAddr::V4(_) => S::new_icmp_send_socket_ipv4(raw),
IpAddr::V6(_) => S::new_icmp_send_socket_ipv6(),
}?)
}

/// Make a socket for sending `UDP` packets.
#[instrument]
fn make_udp_send_socket<S: Socket>(addr: IpAddr) -> TraceResult<S> {
fn make_udp_send_socket<S: Socket>(addr: IpAddr, raw: bool) -> TraceResult<S> {
Ok(match addr {
IpAddr::V4(_) => S::new_udp_send_socket_ipv4(),
IpAddr::V4(_) => S::new_udp_send_socket_ipv4(raw),
IpAddr::V6(_) => S::new_udp_send_socket_ipv6(),
}?)
}

/// Make a socket for receiving raw `ICMP` packets.
#[instrument]
fn make_recv_socket<S: Socket>(addr: IpAddr) -> TraceResult<S> {
fn make_recv_socket<S: Socket>(addr: IpAddr, raw: bool) -> TraceResult<S> {
Ok(match addr {
IpAddr::V4(ipv4addr) => S::new_recv_socket_ipv4(ipv4addr),
IpAddr::V4(ipv4addr) => S::new_recv_socket_ipv4(ipv4addr, raw),
IpAddr::V6(ipv6addr) => S::new_recv_socket_ipv6(ipv6addr),
}?)
}
Loading

0 comments on commit ffc559e

Please sign in to comment.