diff --git a/src/config.rs b/src/config.rs index 8e8cd7195..3fe53ac4c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -215,6 +215,7 @@ pub struct TrippyConfig { pub tui_bindings: TuiBindings, pub mode: Mode, pub privilege_mode: PrivilegeMode, + pub dns_resolve_all: bool, pub report_cycles: usize, pub geoip_mmdb_file: Option, pub max_rounds: Option, @@ -276,13 +277,16 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { cfg_file_trace.unprivileged, constants::DEFAULT_UNPRIVILEGED, ); - let privilege_mode = if unprivileged { PrivilegeMode::Unprivileged } else { PrivilegeMode::Privileged }; - + let dns_resolve_all = cfg_layer_bool_flag( + args.dns_resolve_all, + cfg_file_dns.dns_resolve_all, + constants::DEFAULT_DNS_RESOLVE_ALL, + ); let verbose = args.verbose; let log_format = cfg_layer( args.log_format, @@ -497,7 +501,7 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { validate_privilege(privilege_mode, has_privileges, needs_privileges)?; validate_logging(mode, verbose)?; validate_strategy(multipath_strategy, unprivileged)?; - validate_multi(mode, protocol, &args.targets)?; + validate_multi(mode, protocol, &args.targets, dns_resolve_all)?; validate_ttl(first_ttl, max_ttl)?; validate_max_inflight(max_inflight)?; validate_read_timeout(read_timeout)?; @@ -552,6 +556,7 @@ impl TryFrom<(Args, &Platform)> for TrippyConfig { tui_bindings, mode, privilege_mode, + dns_resolve_all, report_cycles, geoip_mmdb_file, max_rounds, @@ -638,7 +643,13 @@ fn validate_strategy(strategy: MultipathStrategy, unprivileged: bool) -> anyhow: } /// 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<()> { +fn validate_multi( + mode: Mode, + protocol: TracerProtocol, + targets: &[String], + _all_resolved_ips: bool, +) -> anyhow::Result<()> { + // TODO validate dns_resolve_all match (mode, protocol) { (Mode::Stream | Mode::Pretty | Mode::Markdown | Mode::Csv | Mode::Json, _) if targets.len() > 1 => diff --git a/src/config/cmd.rs b/src/config/cmd.rs index b1ffe5268..12c705659 100644 --- a/src/config/cmd.rs +++ b/src/config/cmd.rs @@ -118,6 +118,10 @@ pub struct Args { #[arg(value_enum, short = 'r', long)] pub dns_resolve_method: Option, + /// Trace to all IPs resolved from DNS lookup [default: false] + #[arg(short = 'y', long)] + pub dns_resolve_all: bool, + /// The maximum time to wait to perform DNS queries [default: 5s] #[arg(long)] pub dns_timeout: Option, diff --git a/src/config/constants.rs b/src/config/constants.rs index 6d291652a..28811600a 100644 --- a/src/config/constants.rs +++ b/src/config/constants.rs @@ -16,6 +16,9 @@ pub const DEFAULT_MODE: Mode = Mode::Tui; /// The default value for `unprivileged`. pub const DEFAULT_UNPRIVILEGED: bool = false; +/// The default value for `all_resolved_ips`. +pub const DEFAULT_DNS_RESOLVE_ALL: 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 a8aab874d..220f8ec06 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -118,6 +118,7 @@ pub struct ConfigStrategy { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct ConfigDns { pub dns_resolve_method: Option, + pub dns_resolve_all: Option, pub dns_lookup_as_info: Option, pub dns_timeout: Option, } diff --git a/src/main.rs b/src/main.rs index ca6337a75..da14f9613 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,12 +53,8 @@ fn main() -> anyhow::Result<()> { let _guard = configure_logging(&cfg); let resolver = start_dns_resolver(&cfg)?; let geoip_lookup = create_geoip_lookup(&cfg)?; - let traces: Vec<_> = cfg - .targets - .iter() - .enumerate() - .map(|(i, target_host)| start_tracer(&cfg, target_host, platform.pid + i as u16, &resolver)) - .collect::>>()?; + let addrs = resolve_targets(&cfg.targets, cfg.dns_resolve_all, &resolver)?; + let traces = start_tracers(&cfg, &addrs, platform.pid)?; Platform::drop_privileges()?; run_frontend(&cfg, resolver, geoip_lookup, traces)?; Ok(()) @@ -128,30 +124,71 @@ fn configure_logging(cfg: &TrippyConfig) -> Option { None } +/// Resolve targets. +fn resolve_targets( + targets: &[String], + dns_resolve_all: bool, + resolver: &DnsResolver, +) -> anyhow::Result> { + targets + .iter() + .flat_map(|target| match resolver.lookup(target) { + Ok(addrs) => { + if dns_resolve_all { + addrs + .into_iter() + .enumerate() + .map(|(i, addr)| { + Ok(TargetInfo { + hostname: format!("{} [{}]", target, i + 1), + addr, + }) + }) + .collect::>>() + .into_iter() + } else { + addrs + .into_iter() + .map(|addr| { + Ok(TargetInfo { + hostname: target.to_string(), + addr, + }) + }) + .take(1) + .collect::>>() + .into_iter() + } + } + Err(e) => { + vec![Err(anyhow!("failed to resolve target: {} ({})", target, e))].into_iter() + } + }) + .collect::>>() +} + +/// Start all tracers. +fn start_tracers( + cfg: &TrippyConfig, + addrs: &[TargetInfo], + pid: u16, +) -> anyhow::Result> { + addrs + .iter() + .enumerate() + .map(|(i, TargetInfo { hostname, addr })| { + start_tracer(cfg, hostname, *addr, pid + i as u16) + }) + .collect::>>() +} + /// Start a tracer to a given target. fn start_tracer( cfg: &TrippyConfig, target_host: &str, + target_addr: IpAddr, trace_identifier: u16, - resolver: &DnsResolver, ) -> Result { - let target_addr: IpAddr = resolver - .lookup(target_host) - .map_err(|e| anyhow!("failed to resolve target: {} ({})", target_host, e))? - .into_iter() - .find(|addr| { - matches!( - (cfg.addr_family, addr), - (TracerAddrFamily::Ipv4, IpAddr::V4(_)) | (TracerAddrFamily::Ipv6, IpAddr::V6(_)) - ) - }) - .ok_or_else(|| { - anyhow!( - "failed to find an {:?} address for target: {}", - cfg.addr_family, - target_host - ) - })?; let source_addr = match cfg.source_addr { None => SourceAddr::discover(target_addr, cfg.port_direction, cfg.interface.as_deref())?, Some(addr) => SourceAddr::validate(addr)?, @@ -292,6 +329,13 @@ fn make_tui_config(args: &TrippyConfig) -> TuiConfig { ) } +/// Information about a tracing target. +#[derive(Debug, Clone)] +pub struct TargetInfo { + pub hostname: String, + pub addr: IpAddr, +} + /// Information about a `Trace` needed for the Tui, stream and reports. #[derive(Debug, Clone)] pub struct TraceInfo { diff --git a/trippy-config-sample.toml b/trippy-config-sample.toml index 174f9cb75..9ed0e2aa0 100644 --- a/trippy-config-sample.toml +++ b/trippy-config-sample.toml @@ -176,6 +176,12 @@ read-timeout = "10ms" # cloudflare - Resolve using the Cloudflare `1.1.1.1` DNS service dns-resolve-method = "system" +# Trace to all IPs resolved from DNS lookup (ICMP only) [default: false] +# +# When set to true a trace will be started for all IPs resolved for all given targets. +# When set to false a trace will be started for one arbitrarily chosen IP per given target. +dns_resolve_all = false + # Whether to lookup AS information [default: false] # # If enabled, AS (autonomous system) information is retrieved during DNS