Skip to content

Latest commit

 

History

History
984 lines (807 loc) · 60.9 KB

File metadata and controls

984 lines (807 loc) · 60.9 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Security

  • rustls 0.23.40 → 0.23.45 clears RUSTSEC-2026-0285 (TLS 1.3 handshake messages incorrectly accepted across encryption-level boundaries). The advisory reaches ttl transitively via reqwest (PeeringDB) and ureq (update check). Also replaces the yanked chacha20 0.10.0 with 0.10.2.

Dependencies

  • dirs 6 → 7 (direct; the only change is Windows preference_dir, which ttl does not use), hickory-resolver/-proto/-net 0.26.1 → 0.26.3, maxminddb 0.30.0 → 0.30.3, aws-lc-rs 1.16.3 → 1.18.1, aws-lc-sys 0.40.0 → 0.45.0, toml 1.1.4 → 1.1.6.

[0.22.0] - 2026-08-24

Changed

  • The TUI redraws only when something changed. The interactive view previously rebuilt its session snapshot and repainted the terminal on every 16 ms tick (~60 fps) whether or not a probe had landed — at the default 1 s probe interval roughly 55 of every 60 frames were pure churn. Each tick now compares a fingerprint of everything the view renders (hop-stat generation, overlays, theme, status line, replay clock, settings, target list, IX cache state) against the previous frame and skips both the snapshot clone and the draw when they match. Measured on a 9-hop trace at the default interval, 1.4% → 0.4% of one CPU core, and the saving grows with hop count. Output is unchanged and the tick rate is still 16 ms, so input, replay animation, and terminal resizes stay as responsive as before.
  • The published crate no longer bundles the demo assets. docs/*.gif and docs/*.tape are excluded from the package, roughly halving the crates.io tarball (1.14 MB → 0.56 MB compressed) for every cargo install ttl. The README still renders the demo from the repository.

Security

  • rkyv 0.8.16 → 0.8.18 clears RUSTSEC-2026-0233 (use-after-free during deserialization) and RUSTSEC-2026-0234 / RUSTSEC-2026-0235 (out-of-bounds reads on crafted archives). The advisory reaches ttl transitively via getifssmallvec-wrapper.
  • lru 0.18.0 → 0.18.2 clears RUSTSEC-2026-0253 (potential use-after-free from missing panic safety in LruCache::pop()). The advisory reaches ttl transitively via ratatuiratatui-core.

Dependencies

  • maxminddb 0.29 → 0.30 (direct), plus tokio 1.52.3 → 1.53.1, clap 4.6.1 → 4.6.6, clap_complete 4.6.7 → 4.6.9, futures 0.3.32 → 0.3.34, libc 0.2.186 → 0.2.189, serde 1.0.228 → 1.0.229, serde_json 1.0.150 → 1.0.151, socket2 0.6.4 → 0.6.5, and anyhow 1.0.103 → 1.0.104.

[0.21.0] - 2026-07-07

Added

  • Opt out of the startup update check (#110). The GitHub release check can now be disabled five ways: the --no-update-check flag, the DO_NOT_TRACK=1 (cross-tool standard) or TTL_NO_UPDATE_CHECK=1 environment variables, a no_update_check = true preference in ~/.config/ttl/config.toml, the Update Check toggle in the TUI Settings modal, or a build with --no-default-features, which compiles the check out entirely and drops the update-informer dependency. Addresses package-repository, air-gapped, and privacy use cases where an unconditional phone-home is unexpected. An examples/config.toml now documents the available preferences.

Security

  • crossbeam-epoch 0.9.18 → 0.9.20 clears RUSTSEC-2026-0204 (invalid pointer dereference in the fmt::Pointer impl for Atomic/Shared). The advisory reaches ttl transitively via mokahickory-resolver; the bump keeps cargo audit clean.

[0.20.2] - 2026-07-06

Fixed

  • Response TTL and return-path estimation on macOS/BSD IPv4 (#117). The hardened control-message parser required a 4-byte int, but the BSDs and macOS deliver the IPv4 IP_RECVTTL value as a single u_char — that 1-byte message was dropped, blanking the reply TTL and the return-hop / path-asymmetry estimate derived from it on those platforms. The value is now read width-aware (4-byte int on Linux and for the IPv6 hop-limit, single byte on BSD/macOS IPv4) while preserving the short-message over-read protection. Exercised on real macOS and FreeBSD kernels in CI.
  • Panic-proofing across the CLI, probe, and TUI paths. CLI validation rejects out-of-range ports, counts, intervals, and DSCP values and guards base_port + ttl / src_port + flow overflow with clean errors instead of panicking (#99); arithmetic on clock jumps and oversized packets uses saturating/checked math (#101); the TUI no longer panics on multi-byte (UTF-8) input or cursor editing (#100, #116).
  • Unsafe socket code hardened against undefined behavior (#102). Control-message parsing uses unaligned reads and validates cmsg lengths before dereferencing, removing potential misaligned reads and out-of-bounds access on malformed ancillary data.
  • PMTUD correlation, lock ordering, and enrichment dedup (#104). Corrects a Path MTU Discovery correlation edge case, eliminates an opposing lock-acquisition order in the timeout-cleanup path (deadlock risk), and de-duplicates in-flight enrichment lookups.
  • IPv6 correctness. Extension headers inside quoted ICMP error payloads are now parsed so probes correlate correctly (#114, LAN-144), and the interface scope ID is propagated through the receive/bind paths so link-local responders are no longer ambiguous (#113, LAN-143).
  • Bounded enrichment caches (#115, LAN-145). The ASN/geo/rDNS/IX response caches are capped at 10,000 entries (expiring by TTL, then evicting oldest) so long-running or high-fan-out traces cannot grow memory without bound.
  • Replay robustness (#116, LAN-150/181). Saved-session replay validates its input instead of asserting, and --replay/--diff are correctly exempt from the live-trace batch validations (e.g. --replay --json no longer demands -c). Corrupt session durations deserialize to an error rather than panicking.
  • File security and output hygiene (#103). Auto-generated export filenames are sanitized (path separators, control characters, and Windows reserved names) to prevent traversal; the preferences file is created and kept at 0600; enrichment cache files are written atomically (temp + rename); report/CSV output is sanitized.
  • Export filename length (#119). A maximum-length FQDN plus the fixed ttl-<target>-<timestamp>.json overhead could exceed the 255-byte per-component filesystem limit and fail JSON export with ENAMETOOLONG; the target component is now capped on a UTF-8 boundary.

Dependencies

  • maxminddb 0.28.1 → 0.29.0, clap_complete 4.6.5 → 4.6.7, anyhow 1.0.102 → 1.0.103.
  • CI/release GitHub Actions bumped to current majors: checkout v6 → v7, cache v5 → v6, and the docker actions setup-buildx v3 → v4, setup-qemu v3 → v4, login v3 → v4, metadata v5 → v6, build-push v6 → v7.

[0.20.1] - 2026-06-27

Fixed

  • Traces no longer collapse to a single hop on macOS and other fast hosts (#12). sendto is asynchronous on macOS/BSD: the kernel stamps each queued datagram with the socket's current IP TTL at drain time, so a rapid setsockopt(IP_TTL); send loop could emit every probe with the final TTL, leaving only the destination/gateway visible — intermittently, and more often on fast Apple Silicon (reproduced on the wire on an M2, macOS 26.5.1). The previous fixed 500µs delay only masked the race.
    • IPv4 (all platforms): every probe is now sent through a single raw IP_HDRINCL socket with the TTL written directly into a hand-built IP header (ICMP/UDP/TCP), removing the race by construction — no per-probe socket churn, no timing delay. The per-OS ip_len/ip_off byte order is handled (host order on macOS/NetBSD, network order on Linux/FreeBSD ≥11) and validated on real Linux, macOS, and FreeBSD kernels in CI.
    • IPv6: each probe is sent from a fresh socket on the BSD-derived platforms (macOS, FreeBSD, NetBSD) — there is no IPv4-style IP_HDRINCL for IPv6 — gated by the per_probe_send cfg. Linux reuses one shared socket (its sendto never exhibited the race).
    • The interim 500µs inter-probe delay has been removed. The rapid probe sweeps are now race-free (IPv4 via IP_HDRINCL, IPv6 via per-probe sockets on all BSD-derived platforms), so the timing stopgap is no longer needed. The only remaining shared-socket send is the single IPv6 PMTUD probe per round, which is an isolated send (not a back-to-back burst) and so is unaffected.
    • Correlation is unaffected — the receiver matches on the probe sequence / payload-embedded identifier. IPv4 Path MTU Discovery now also works on NetBSD, since the Don't Fragment bit is set in the hand-built header rather than via the unavailable IP_DONTFRAG socket option.

Dependencies

  • quinn-proto 0.11.14 → 0.11.15, clearing RUSTSEC-2026-0185. The advisory affects a transitive dependency (reqwest's unused HTTP/3 feature) that ttl never compiles into the binary, so it was not reachable at runtime; the bump keeps cargo audit clean.
  • socket2 0.6.3 → 0.6.4, reqwest 0.13.3 → 0.13.4, ratatui 0.30.0 → 0.30.2, chrono 0.4.44 → 0.4.45 (patch updates).

[0.20.0] - 2026-06-10

Added

  • Interactive target selection: run ttl with no arguments to open an empty session, then press o to add targets — also works mid-session in any live TUI run. Hostnames resolve in the background with a loading state; probe engines (and receivers, for a new IP family) spawn at runtime. Entering an already-traced host switches to it.
  • Daemon mode (--daemon): headless probing with no per-hop stdout, for container/monitoring deployments. Combine with --prometheus and/or --stream-json to consume the data.
  • Prometheus exporter (--prometheus <ADDR>, e.g. :9090): OpenMetrics endpoint with per-hop sent/response/timeout counters, RTT avg/min/max/stddev gauges, loss ratio, ECMP responder count, hop identity info series, and per-target reachability/path-length metrics. Includes GET /healthz for container orchestration. Hand-rolled over the existing tokio runtime — no new dependencies.
  • SIGTERM handling: docker stop now triggers the same graceful shutdown as Ctrl-C (unix).
  • Official Dockerfile: multi-stage Alpine/musl build producing a minimal image; .dockerignore included.
  • GHCR container publishing: releases now push multi-arch (amd64/arm64) images to ghcr.io/lance0/ttl, assembled from the prebuilt static musl release binaries. New aarch64-unknown-linux-musl release artifact (also useful for glibc-free arm64 distros).
  • Trace diffing (--diff before.json after.json): compare two saved sessions hop by hop. Reports path changes (primary responder differs), added/lost hops, ECMP responder-set changes, avg RTT deltas (highlighted when ≥5ms and ≥20%), loss changes, and destination reachability. --json emits the diff as machine-readable JSON.
  • Streaming JSON output (--stream-json): emit each probe event (reply/timeout/late_reply) as one line of JSON on stdout for piping to jq/grep/monitoring pipelines. Event lines match the saved-session events schema with a target field added; a per-target summary line is emitted at end of stream. Implies --no-tui; memory stays bounded on infinite runs.

Changed

  • IX prefix lookup now uses a binary radix trie instead of a linear scan: O(prefix_len) per responder IP (≤32/128 node hops) instead of O(n) over ~2,000 PeeringDB prefixes. No behavior change.

Dependencies

  • maxminddb 0.27.3 → 0.28.1, getifs 0.4.0 → 0.6.1, tokio 1.52.1 → 1.52.3, serde_json 1.0.149 → 1.0.150, clap_complete 4.6.3 → 4.6.5

[0.19.1] - 2026-05-02

Added

  • Pre-commit hooks (.pre-commit-config.yaml): cargo fmt and cargo clippy --all-targets -- -D warnings on every commit, cargo test --lib on every push. Setup documented in CONTRIBUTING.md for both prek (fast Rust port) and the original Python pre-commit.
  • CI: cargo clippy --all-targets -- -D warnings now runs on macOS and FreeBSD in addition to Linux. Catches platform-specific cfg-gating regressions before merge.
  • README: NetBSD pkgsrc installation instructions; replay controls listed in the Keybindings table.

Changed

  • hickory-resolver upgraded 0.25 → 0.26. Internal API migration in DNS/ASN lookup modules — transparent to users.
  • toml upgraded 0.9 → 1.x. No code changes required.
  • CI: softprops/action-gh-release upgraded v2 → v3 (Node.js 24 runtime).

Fixed

  • DNS resolver fallback (#71): Restored Google DNS fallback when the system resolver builder is constructable but its build() step fails. Caught in Copilot review of the hickory 0.26 migration.
  • macOS clippy warnings (#72): Unused imports and is_dgram variable on macOS-only paths, plus three needless_return lints in src/probe/socket.rs. Contributed by @SSakutaro.
  • FreeBSD/NetBSD dead-code warnings: DGRAM ICMP socket helpers now cfg-gated to match their call sites.
  • clippy 1.95 lints: collapsible_match in TUI key handlers and unnecessary_sort_by in IX prefix sorting.
  • FreeBSD CI: Install ca_root_nss before fetching crates to avoid SSL verification failures from the FreeBSD VM image.

Security

  • hickory-proto via hickory-resolver 0.26.1: fixes RUSTSEC-2026-0119 (O(n²) DNS name compression CPU exhaustion). RUSTSEC-2026-0118 (NSEC3 unbounded loop) also no longer applies — ttl does not validate DNSSEC.
  • rustls-webpki 0.103.13: cumulative fixes for RUSTSEC-2026-0049, 0098, 0099, 0104.
  • aws-lc-sys 0.39.0: fixes RUSTSEC-2026-0044/0045/0046/0047/0048 (CRL distribution scope, AES-CCM timing side-channel, X.509 wildcard bypass, PKCS7 validation bypass).
  • quinn-proto 0.11.14: fixes RUSTSEC-2026-0037 (Quinn endpoint DoS — not exploitable in ttl, which only acts as a TLS client).

Dependencies

  • libc 0.2.182 → 0.2.186, tokio 1.49 → 1.50, socket2 0.6.2 → 0.6.3, clap 4.5 → 4.6, clap_complete 4.5 → 4.6, proptest 1.10 → 1.11, chrono 0.4.43 → 0.4.44

[0.19.0] - 2026-02-26

Added

  • Interactive replay controls: Seek (Left/Right ±500ms, [/] ±5s), speed adjust (+/- ±0.5x, 0.5x–5.0x range), Home/End jump to start/end
  • Replay progress bar: Shows play state, event count, timeline position, and speed multiplier
  • Replay help section: Help overlay (?) now shows replay-specific keybindings when in replay mode

Fixed

  • Replay timing precision: Switched from f32 to f64 for elapsed time calculations to prevent drift on long replays
  • Replay seek safety: Saturating arithmetic prevents overflow on relative seek; Instant::checked_sub prevents underflow on corrupted replay files

[0.18.2] - 2026-02-23

Fixed

  • NetBSD UDP probes: Auto-detect source IP for UDP DGRAM sockets on NetBSD. Fixes "No route to host" (EHOSTUNREACH) when sending UDP probes without --source-ip (#47)
  • NetBSD IPv4 PMTUD retry spam: PMTUD now terminates immediately on first DF flag failure instead of retrying every probe round. Prints a single warning when IP_DONTFRAG is unavailable (#47)

[0.18.1] - 2026-02-22

Added

  • NetBSD platform support (experimental): Raw sockets, IPv6 PMTUD, BSD minimum inter-probe delay. IPv4 PMTUD unavailable (no IP_DONTFRAG). Interface binding (-i) not supported. (#47)

Fixed

  • Update checker reliability: Changed from blocking recv_timeout(1s) to non-blocking try_recv() polling in TUI event loop. Update notifications no longer silently drop when GitHub API response exceeds 1 second
  • Update checker first-run: Set update-informer interval to Duration::ZERO so first launch performs an immediate network check instead of writing a cache file and waiting for the next run

Dependencies

  • Updated clap (4.5.58→4.5.60), libc (0.2.181→0.2.182), futures (0.3.31→0.3.32), maxminddb (0.27.1→0.27.3), toml_parser (1.0.7→1.0.9), anyhow (1.0.101→1.0.102), plus transitive deps

[0.18.0] - 2026-02-13

Added

  • ECMP classification: Detects per-flow vs per-packet ECMP using primary concentration heuristics. Paths column now accurately reflects observed responder count for per-packet load balancing (#46)
  • E indicator: New ECMP indicator in main table replaces misleading ! (route flap) when ECMP is the actual cause
  • Effective flow capability: Runtime detection of protocol flow support. --flows > 1 with ICMP now warns and collapses to single-flow instead of silently doing nothing
  • Receiver flow attribution hardening: Out-of-range source ports (NAT/CGNAT) no longer force-attributed to flow 0. Unknown-flow responses only match pending probes when unambiguous
  • Hop detail view: Per-packet ECMP section shows responder count and path count; per-flow ECMP section unchanged
  • Last RTT semantics documented: Last column tracks primary responder's most recent RTT (code docs + KNOWN_ISSUES)
  • Main table layout guard tests: Header/cell/width count parity verified across Auto/Compact/Wide x single-flow/multi-flow
  • IPv6 RAW payload fallback tests: Echo Reply and Time Exceeded parsing tests for IPv6

Changed

  • Flap detection suppressed during ECMP: ! indicator no longer fires when per-packet ECMP is detected at a hop
  • Probe engines use per-session config: Each target's engine sees the effective flow count, not the raw CLI value

[0.17.0] - 2026-02-12

Added

  • Last RTT column: Main table now shows the most recent probe RTT between Sent and Avg, matching mtr's default column order (#17)
  • JAvg and JMax columns: Jitter average and jitter max columns appear in Wide display mode (press w) (#17)
  • CSV columns: Added last_ms, jitter_avg_ms, jitter_max_ms to CSV export

Changed

  • CSV schema (breaking): Header now has 14 columns (was 11). last_ms inserted before avg_ms; jitter_avg_ms and jitter_max_ms appended
  • ASN column width: Increased auto mode cap (30→40 chars) and wide mode width (24→36 chars) to show more of the AS name

Dependencies

  • Updated clap (4.5.54→4.5.58), clap_complete (4.5.65→4.5.66), anyhow (1.0.100→1.0.101), libc (0.2.180→0.2.181), proptest (1.9.0→1.10.0), plus transitive deps

[0.16.1] - 2026-02-11

Fixed

  • Hang on exit: Bounded IPv6 echo-reply drain loop to prevent starvation when socket is continuously readable (#41)
  • Ctrl+C during shutdown: Ctrl+C now force-exits if cleanup stalls — first press cancels, second press terminates immediately (#41)

Added

  • Shutdown tracing: Set TTL_SHUTDOWN_TRACE=1 to emit [shutdown] stage markers for diagnosing exit hangs

[0.16.0] - 2026-02-05

Changed

  • Immediate sent counting: Sent counters now increment when probes leave, not when responses arrive. Matches mtr behavior for real-time feedback (#17)

Fixed

  • FreeBSD ICMP sockets: FreeBSD doesn't support SOCK_DGRAM + IPPROTO_ICMP, now uses RAW sockets directly. Fixes Protocol not supported error on FreeBSD (#14)
  • Dual-stack --resolve-all: --resolve-all without -4/-6 now traces both IPv4 and IPv6 addresses by spawning dual receivers. Previously silently dropped one address family (#11)
  • IPv6 Echo Reply double-counting: Fixed sent counter being incremented twice for Linux IPv6 ICMP probes (once at send, once at Echo Reply)

Removed

  • Dead create_probe_interval() function

[0.15.3] - 2026-01-29

Changed

  • ASN column format: Now shows AS##### name instead of just the name, so ASN number is always visible even when truncated (#17)

Fixed

  • Sent count consistency: All pending probes are now counted as timeouts when trace completes, ensuring consistent sent counts across all hops (#37)

[0.15.2] - 2026-01-28

Added

  • --jumbo flag: Enable 9216-byte max for PMTUD in jumbo frame environments. Without this flag, PMTUD uses standard 1500-byte ethernet max (#28)

Fixed

  • PMTUD accuracy: Standard ethernet (1500 MTU) now reports exact MTU instead of ~1495 (#28)

[0.15.1] - 2026-01-28

Fixed

  • TUI lock contention: Snapshot session data before rendering so no locks are held during draw, fixing UI freezes during rapid target switching (#19)
  • Stat column desync: Sent counter now updates atomically with Avg/Min/Max/Loss instead of racing ahead (#17)

[0.15.0] - 2026-01-27

Added

  • Jumbo frame support: --size now accepts up to 9216 bytes (was 1500) for jumbo frame environments. PMTUD binary search also starts at 9216, discovering jumbo MTUs automatically (#28)
  • Animated replay mode: --replay <file> --animate replays saved sessions showing probe-by-probe discovery instead of final state (#9)
  • Replay speed control: --speed flag controls replay speed (default 10x, use 1.0 for real-time)
  • Probe event recording: Sessions now record per-probe events with full correlation info (TTL, seq, flow_id, responder, RTT)
  • Replay controls: Press Space to pause/resume animated replay
  • Late reply tracking: Responses arriving after timeout are recorded as late_reply events for replay accuracy

Fixed

  • FreeBSD build failure: Fix cargo install ttl on FreeBSD by making getifs dependency conditional. Gateway detection unavailable on FreeBSD (uses macOS-specific APIs).
  • Replay timing accuracy: Events now replay at their recorded timestamps instead of fixed intervals
  • Replay pause/resume: Resuming no longer causes time jump or skipped events
  • Event timestamp monotonicity: Use monotonic clock (Instant) for event offsets to prevent clock jump issues

Changed

  • TUI refresh rate: Increased from 10fps to 60fps for more responsive per-probe updates (#17)
  • CI: Added FreeBSD 14.2 build testing via vmactions/freebsd-vm

[0.14.2] - 2026-01-26

Added

  • In-app update notification: When an update is available, a yellow status bar and banner show the new version. Press 'u' to dismiss.
  • Update command in help: Help overlay now shows the install-method-aware update command (e.g., brew upgrade ttl or cargo install ttl)

Fixed

  • Update notification TLS: Fix update checker silently failing due to missing TLS support in ureq backend

[0.14.1] - 2026-01-26

Added

  • FreeBSD support (experimental): Basic traceroute now works on FreeBSD 13/14. Requires sudo. Interface binding (-i) is not supported due to missing kernel APIs.

Changed

  • Resolver behavior: Target resolution now follows OS resolver order (respects /etc/gai.conf on Linux). Use -4 or -6 to force a specific IP family. (PR #24 by @n-thumann)
  • Target list number keys: Pressing 1-9 now selects and closes the dialog in one action

Fixed

  • Target list lockup: Fix lock contention when selecting target from list overlay (#19)
  • Export lock contention: Clone session data before file I/O to avoid blocking receiver thread
  • Hop navigation lock contention: Extract hop count in scoped block before updating UI state
  • Hop detail modal truncation: Modal now dynamically sizes to content, preventing help text cutoff

Performance

  • Status bar allocation: Use Cow<str> to avoid string allocation on every frame

[0.14.0] - 2026-01-25

Added

  • Update notifications: Checks GitHub releases for new versions (daily, cached). Shows install-method-aware update command on exit when a new version is available. Only displays on TTY.

Changed

  • Gateway detection via kernel APIs: Replaced subprocess-based route detection with direct kernel API calls (netlink on Linux, sysctl on macOS). Instant startup even on DFZ routers with millions of routes (#16)

Fixed

  • DFZ router startup hang: Gateway detection no longer shells out to ip route which could hang on systems with large routing tables (#16)

[0.13.4] - 2026-01-24

Added

  • Hop detail navigation: Use Up/Down/k/j to navigate between hops while detail view is open, 1-9 to jump directly (#18)
  • Version in help: Help overlay now shows version number in title (#22)

Fixed

  • PMTUD accuracy: Trust ICMP-reported MTU directly per RFC 1191, fixing 6-byte underreporting (#23)

[0.13.3] - 2026-01-23

Fixed

  • UI lockup with multiple targets: Target list overlay no longer blocks receiver thread (#19)
  • Startup hang on routers with large routing tables: Route detection commands now timeout after 2 seconds (#16)
  • PMTUD probes inflating hop statistics: PMTUD probes no longer count toward hop sent/received stats (#21)

[0.13.2] - 2026-01-22

Fixed

  • macOS: Increase inter-probe delay from 200µs to 500µs to eliminate remaining TTL batching issues (#12)
  • Fix clippy warning with Rust 1.93+

[0.13.0] - 2026-01-21

Added

  • Multi-IP resolution (--resolve-all): Trace all resolved IP addresses for hostnames
    • Round-robin DNS (multiple A records) now traceable with single command
    • Dual-stack hosts (A + AAAA) supported - compares IPv4 vs IPv6 paths
    • Automatic deduplication by IP with hostname aliasing
    • Prefers IPv4 by default, falls back to IPv6 if no IPv4 addresses
    • Skip warnings show how many addresses were filtered (e.g., "3 IPv6 skipped")
    • Title bar shows hostname -> IP format when tracing resolved hostnames
  • Target list overlay (l key): Overview of all resolved targets in multi-target mode
    • Shows IP address, hostname, hop count, and loss percentage for each target
    • Navigate with Up/Down or j/k, select with Enter, jump with 1-9
    • Only available when multiple targets are being traced
  • Settings modal (s key): Configure theme, display mode, and PeeringDB API key
    • Live preview of theme changes
    • Display mode selector (auto/compact/wide) for column widths
    • PeeringDB API key input with text editing support
    • Cache status display (prefix count, age, expiry indicator)
    • Press r in PeeringDB section to refresh cache
    • Settings persist to config file on exit
  • PeeringDB API key persistence: API key saved to ~/.config/ttl/config.toml
    • Environment variable PEERINGDB_API_KEY still takes precedence over saved key
  • Wide mode CLI flag (--wide): Start with wide (generous column widths) mode
  • Autosize columns (w key): Intelligent column width control
    • Auto mode (default): Columns auto-fit to longest hostname/ASN content
    • Compact mode: Minimal column widths (host: 20, ASN: 12)
    • Wide mode: Generous column widths (host: 45, ASN: 24)
    • Press w to cycle: auto → compact → wide → auto
    • Long hostnames like po-300-xar01.alexandria.va.bad.comcast.net display fully in auto mode
    • Maximum caps prevent layout blowout (host: 60 chars, ASN: 30 chars)
    • Display mode persisted to ~/.config/ttl/config.toml

Changed

  • Status bar shows l list hint when multiple targets are available
  • Status bar shows w display hint for cycling display modes
  • Help overlay updated with s (Settings), l (Target list), and w (Display mode) keybindings
  • Settings modal now shows "Display Mode" selector instead of "Wide Mode" toggle
  • Intel Mac binaries restored: Pre-built x86_64-apple-darwin binaries available again
    • TTL batching bug (#12) was likely the cause of previous Intel Mac issues
    • Uses new macos-15-intel GitHub Actions runner

Fixed

  • macOS: Probes sent with wrong TTL in initial burst (#12)
    • Rapid setsockopt(IP_TTL) calls were batched by macOS kernel
    • First N probes sent with same TTL, causing only 1 hop to display
    • Added 200µs minimum delay between probes on macOS to ensure TTL changes take effect
    • Workaround: --rate flag also fixes this by adding delay between probes

[0.12.8] - 2026-01-19

Fixed

  • Fix cargo fmt in integration tests (no functional changes from 0.12.7)

[0.12.7] - 2026-01-19 [YANKED]

Fixed

  • Non-responding hops frozen after destination found: Hops showing * * * now continue to be probed after destination is discovered, matching mtr/trippy behavior
    • Previously, Sent counters froze on non-responding hops after completion
    • Now probes all TTLs up to destination every round
    • Allows detecting hops that recover from rate limiting

Added

  • Ctrl+C to quit TUI: Now handles Ctrl+C (and ETX) in addition to q
  • [max_ttl=30] warning: Title bar shows warning when destination not found and using default max_ttl of 30; hints to try --max-ttl 64 for long paths

Changed

  • More probes sent after destination found (all TTLs probed each round)
    • Bounded by dest_ttl, not max_ttl
    • Matches mtr/trippy probing behavior

[0.12.6] - 2026-01-18

Fixed

  • ICMPv6 checksum computation: Fix IPv6 traceroute not detecting destinations
    • ICMPv6 packets had checksum 0, relying on kernel to fill it in (it didn't)
    • Destinations dropped packets with invalid checksums; intermediate hops worked
    • Added manual ICMPv6 checksum computation with RFC 8200 pseudo-header
    • Algorithm derived from trippy (BSD-licensed) with known-value test verification
    • Socket now bound to source IP for IPv6 to ensure checksum consistency

Improved

  • IPv6 address display: Increased width for full IPv6 addresses
    • TUI host column: 28 → 42 chars for IPv6 (prevents truncation)
    • Text report host column: 40 → 46 chars

[0.12.5] - 2026-01-18

Fixed

  • IPv6 ICMP traceroute: Fix 100% packet loss on Linux for destination hop
    • Linux delivers ICMPv6 Echo Reply only to the socket that sent the request
    • Added send socket polling for Echo Reply in IPv6 ICMP mode (Linux-only)
    • Intermediate hops (Time Exceeded) were unaffected; only destination detection was broken
    • ICMPv6 Echo Request now uses correct type 128 (was incorrectly using type 8)

Improved

  • Hop detail dialog: Add Enter and q keys to close dialog (PR #6 by @themoog)
    • Enter now toggles the dialog (open and close)
    • q provides familiar quit-key for TUI users
    • Improves accessibility for users with non-functional Escape keys

Changed

  • Cargo.lock: Now tracked in version control for reproducible builds
    • Best practice for binary applications per Cargo documentation
    • Enables deterministic builds for package managers (nixpkgs, etc.)

[0.12.4] - 2026-01-17

Fixed

  • Linux binary compatibility: Switch x86_64 builds to musl libc
    • Pre-built binaries now work on Debian 11/12 and other older distros
    • Previously required glibc 2.39 (Ubuntu 24.04+), now fully static

[0.12.3] - 2026-01-16

Fixed

  • Hop detail view stats: Fixed "Sent: 0" display bug in hop detail panel
    • Hop detail now correctly shows hop-level sent/received/loss stats
    • Previously showed per-responder sent (always 0) instead of hop-level sent
    • Note: Per-responder sent can't be tracked (we don't know which responder will reply before sending)

[0.12.2] - 2026-01-16

Improved

  • Quick Start documentation: Made Linux setcap command more prominent
    • Shows how to run without sudo on Linux after one-time capability setup
    • Clarifies macOS always requires sudo

[0.12.1] - 2026-01-16

Security

  • Terminal injection protection: Sanitize DNS hostnames, ASN names, and IX info before display
    • Filters control characters from external data sources (PTR records, Team Cymru, PeeringDB)
    • Prevents malicious terminal escape sequences from affecting the TUI

Fixed

  • --count semantics: -c N now sends N probe rounds (one probe per TTL), not N × max_ttl probes
    • Each round sends probes to all active TTLs in a single interval
    • Behavior now matches user expectations: -c 10 = 10 rounds of probing
    • Updated help text to clarify "probe rounds" semantics
  • Port overflow validation: --src-port + --flows combination now correctly validated
    • Fixed off-by-one: ports 65520 + 16 flows (max port 65535) now accepted
    • Clear error message shows the computed maximum port number
  • Sequence wrap prevention: Reject --timeout > 256 × --interval
    • ProbeId uses u8 sequence (0-255), wraps every 256 intervals
    • Validation prevents mis-correlation when old probes outlive sequence wrap
  • Dead code removal: Removed unused recv_icmp_for_udp function

Changed

  • Dependencies updated:
    • hickory-resolver 0.24 → 0.25
    • socket2 0.5 → 0.6
    • reqwest 0.12 → 0.13
    • dirs 5.0 → 6.0
    • toml 0.8 → 0.9
    • ipnetwork 0.20 → 0.21
    • Removed unused thiserror dependency

Technical

  • Added sanitize_display() helper in lookup module for control character filtering
  • Added 5 CLI validation tests for port overflow and timeout/interval checks
  • All three probe modes (ICMP, UDP, TCP) now track rounds_completed for consistent -c behavior

[0.12.0] - 2026-01-16

Added

  • Shell completions: Generate completions for bash, zsh, fish, and powershell via --completions <shell>
  • WSL2 documentation: Added Windows via WSL2 installation guide to README

Improved

  • Document PEERINGDB_API_KEY environment variable for higher API rate limits
  • Extract RECENT_WINDOW_SIZE constant for cleaner code
  • Better documentation for rate limit detection edge cases

[0.11.5] - 2026-01-15

Fixed

  • Linux permission error: Fail fast with clear instructions (setcap or sudo) instead of silently falling back to broken unprivileged mode

[0.11.4] - 2026-01-15

Fixed

  • Multi-target response misattribution: Fix bug where responses could be attributed to wrong target when tracing multiple destinations concurrently
    • Extract original destination IP from quoted ICMP error packets for direct lookup
    • Use responder IP for Echo Reply disambiguation (responder IS the target)
    • Eliminates ambiguous linear target iteration

Changed

  • MSG_CTRUNC detection: Return None TTL when control message is truncated to prevent unreliable asymmetry detection
  • IPv6 permission check on Linux: Warn if IPv6 sockets unavailable (mirrors macOS behavior)
  • macOS CI: Add macOS test job to catch platform-specific issues before release

Improved

  • Remove panic-able unwrap() from MPLS label parsing (use direct array conversion)

[0.11.3] - 2026-01-15

Changed

  • macOS Sequoia (15) support: Document as "build from source" only
    • Pre-built binaries are built on Tahoe (26) and may have display issues on Sequoia
    • Users on macOS 15 should use cargo install ttl to compile from source
    • Updated README Platform Support table to clarify compatibility

[0.11.2] - 2026-01-15

Changed

  • Switch macOS build to macos-latest runner (Tahoe 26)
    • Did not resolve Sequoia compatibility (see 0.11.3)

[0.11.1] - 2026-01-15

Fixed

  • macOS traceroute 100% packet loss: Fix ICMP traceroute showing all hops as * * *
    • DGRAM ICMP sockets cannot receive ICMP Time Exceeded messages from intermediate routers
    • Now uses RAW socket for receiving (can receive all ICMP types) while keeping DGRAM for sending (supports IP_TTL)
    • Added payload-based correlation fallback for RAW receive paths (fixes 100% loss when macOS kernel modifies ICMP identifier)
    • Requires sudo on macOS since RAW sockets need root privileges
    • Clear error message when run without elevated privileges
  • Linux unprivileged ICMP: Restore support for unprivileged ICMP sockets (broken in v0.11.0)
    • Linux users with ping_group_range enabled can run without sudo
    • Falls back to DGRAM sockets when RAW sockets are unavailable
  • IPv6 DGRAM availability check: Warn on macOS if IPv6 DGRAM sockets are unavailable

[0.11.0] - 2026-01-14

Fixed

  • macOS traceroute: Fix ICMP traceroute showing only 1 hop on macOS
    • Use SOCK_DGRAM instead of SOCK_RAW for ICMP sockets on macOS
    • macOS raw sockets don't support IP_TTL setsockopt, preventing TTL manipulation
    • DGRAM sockets allow setting TTL per-packet for proper traceroute functionality
    • Added DGRAM-aware packet parsing (no IP header in received packets)
    • Embedded ProbeId in ICMP payload for correlation fallback (macOS may override identifier)

[0.10.3] - 2026-01-14

Changed

  • Platform support: Drop Intel Mac (x86_64-apple-darwin) binaries - Apple Silicon only
    • Intel Macs can still build from source via cargo install ttl

[0.10.2] - 2026-01-14

Fixed

  • Cross-compilation: Switch from native-tls to rustls-tls to avoid OpenSSL dependency for aarch64 builds
  • macOS build: Fix msg_controllen type mismatch (u32 vs usize)
  • Deprecation warning: Use bind_device_by_index_v4 instead of deprecated bind_device_by_index

[0.10.1] - 2026-01-14

Added

  • CLI examples in help: --help now shows usage examples and detection indicator legend
  • Smoke test script: tests/smoke.sh for cross-platform verification

Changed

  • README improvements: Homebrew install, simplified permissions, Known Limitations section, better troubleshooting

[0.10.0] - 2026-01-13

Highlights: Path MTU discovery, ICMP rate limit detection, route flap detection, asymmetric routing detection, TTL manipulation detection, and CI/CD automation. Major release for network diagnostic capabilities.

Added

  • Path MTU discovery (--pmtud): Binary search to find maximum unfragmented packet size
    • Uses DF (Don't Fragment) flag to detect MTU limits
    • Binary search algorithm: starts at 1500, converges to within 8 bytes
    • Shows progress in TUI title bar: [MTU: min-max] during search, [MTU: X] when complete
    • Extracts MTU from ICMP Fragmentation Needed (IPv4 Type 3 Code 4) and ICMPv6 Packet Too Big (Type 2)
    • Handles EMSGSIZE errors for local interface MTU limits
    • Requires 2 consecutive successes or failures before moving binary search bounds (handles network flakiness)
    • IPv4 minimum: 68 bytes (RFC 791), IPv6 minimum: 1280 bytes (RFC 8200)
    • Conflicts with --size (mutually exclusive)
  • Packet size control (--size): Set probe packet size for MTU testing
    • Range: 36-1500 bytes for IPv4, 56-1500 bytes for IPv6
    • Total packet size includes IP header (20/40 bytes) + protocol header + payload
    • Packets sent with DF (Don't Fragment) flag for proper MTU discovery
    • Works with all probe protocols (ICMP, UDP, TCP)
  • DSCP/ToS marking (--dscp): Set IP header DSCP field (0-63) for QoS policy testing
    • DSCP 46 = Expedited Forwarding (EF) for VoIP traffic
    • DSCP 34 = AF41 for video traffic
    • Useful for testing QoS policies and seeing where traffic gets remarked
    • Works with all probe protocols (ICMP, UDP, TCP)
    • Supports both IPv4 (TOS) and IPv6 (Traffic Class)
  • GitHub Actions CI: Automated build, test, clippy, and format checks on PRs
    • Runs on ubuntu-latest for all pushes to master and PRs
    • Strict clippy (-D warnings) catches issues before merge
  • Binary releases: Automated builds on version tags via GitHub Actions
    • Linux x86_64 and aarch64 (cross-compiled)
    • macOS x86_64 (Intel) and aarch64 (Apple Silicon)
    • Pre-built binaries attached to GitHub releases
    • SHA256 checksums included for verification
    • cargo-audit security check before release
  • Rate limiting (--rate): Limit probes per second to avoid triggering router rate limits
    • Useful for slow links or avoiding overwhelming targets
    • --rate 0 = unlimited (default), --rate 10 = 10 probes/sec max
    • Global limit applies across all flows
  • Source IP selection (--source-ip): Force probes to use a specific source IP address
    • Useful for multi-homed hosts with multiple IPs
    • Works with all probe protocols (ICMP, UDP, TCP)
    • Validates source IP family matches target family
  • ICMP rate limit detection: Identify when routers are rate-limiting ICMP responses
    • Detects misleading packet loss caused by router rate limiting (not actual packet drops)
    • Three detection heuristics:
      1. Isolated hop loss: Loss at hop N but 0% loss downstream = rate limiting
      2. Uniform flow loss: All flows losing equally in Paris/Dublin mode = hop-level limiting
      3. Stable loss ratio: Consistent loss percentage over time = rate limiting (vs fluctuating congestion)
    • Loss% column shows "RL" suffix (e.g., "50%RL") when rate limiting suspected
    • Title bar shows [RL?] indicator when any hop has rate limiting detected
    • Hop detail view shows detection reason, confidence level, and mitigation tip
    • Tip suggests slower probing with -i 1.0 or -i 2.0 to avoid triggering limits
    • Detection automatically clears when loss drops below threshold
  • First-hop gateway detection: Display source IP and default gateway in TUI
    • Shows routing info in title bar: eth0 (192.168.1.100 → 192.168.1.1)
    • Auto-detects default gateway from system routing table
    • Works with or without --interface flag
    • Parses ip route show on Linux, route -n get default on macOS
    • Gateway info also populated when using --interface option
  • Route flap detection: Detect when primary responder IP changes at a hop
    • Indicates routing instability in single-flow mode
    • Main table shows "!" after hostname when flaps detected
    • Hop detail view shows route change history (last 5 changes)
    • Uses sticky tie-breaker with margin (requires new IP to exceed old by 2+ responses)
    • Minimum 5 responses before recording flaps (avoids startup noise)
    • Disabled in multi-flow mode (--flows > 1) where path changes are expected
    • History capped at 50 changes per hop
  • Asymmetric routing detection: Detect when return path differs from forward path
    • Extracts response TTL from ICMP packets using recvmsg() with IP_RECVTTL/IPV6_RECVHOPLIMIT
    • Estimates return hops using common initial TTL defaults (64, 128, 255)
    • Compares forward TTL vs estimated return hops to detect asymmetry
    • Flags asymmetry when difference >= 3 hops in >50% of samples (minimum 5 samples)
    • Title bar shows [ASYM] indicator when any hop has asymmetric routing detected
    • Main table shows "~" after hostname when asymmetry suspected at that hop
    • Hop detail view shows routing symmetry section: forward hops, return hops, confidence
    • High variance in return hops suggests return-path ECMP
    • Disabled in multi-flow mode (like route flap detection)
  • TTL manipulation detection: Detect middleboxes that modify IP TTL values
    • Analyzes quoted TTL in ICMP Time Exceeded (code 0) responses only
    • Code 0 = TTL exceeded in transit; code 1 = fragment reassembly exceeded (ignored)
    • Per RFC 1812, quoted TTL should be 0 or 1 (post-decrement or pre-decrement)
    • Detects: transparent proxies (quoted TTL == sent TTL), abnormal quoted TTL > 1
    • Hop 1 guard: avoids false positive when sent_ttl=1 and quoted_ttl=1 (normal pre-decrement)
    • Title bar shows [TTL!] indicator when manipulation detected
    • Main table shows "^" after hostname at affected hops
    • Hop detail view shows: sent TTL, last quoted TTL, normal/anomalous sample counts
    • Works in both single-flow and multi-flow modes (unlike asymmetry/flap detection)
    • Hysteresis clearing resets anomaly counters to prevent re-triggering

Fixed

  • PeeringDB pagination: Added limit=0 to API requests to fetch all IX records
    • Without this, only the first page of results was cached, missing many IX detections
  • PeeringDB User-Agent: Added proper User-Agent header to avoid 403 Forbidden responses
  • PeeringDB API key support: Set PEERINGDB_API_KEY env var for higher rate limits
    • Anonymous API access is rate-limited (1/hour for large queries)
    • API key authentication provides 40 requests/minute
  • IX lookup race condition: Use OnceCell::get_or_try_init for thread-safe lazy loading
    • Previously, concurrent lookups could trigger multiple parallel API fetches
    • get_or_try_init only fills cell on success, allowing retries after backoff on failure
  • IX lookup failure backoff: Skip retries for 5 minutes after load failure
    • Prevents log spam and repeated API hits on unstable networks
  • Longest prefix match: Sort prefixes by length descending for correct matching
    • Previously returned first match; now returns most specific (longest) prefix
  • Rate limit reset: reset_stats now clears rate limit detection state
    • Previously RL warnings could persist after reset or replay
  • Stable loss ratio calculation: Fixed segment length calculation for non-divisible window sizes
    • Previously third segment used wrong divisor, skewing detection
  • Rate limit clearing hysteresis: Require 2 consecutive negative checks before clearing
    • Also clears when downstream loss rises above 10% (isolated loss no longer applies)
    • Force clears after 5 negatives regardless (signal gone if heuristics stop matching)
    • Prevents UI flicker while ensuring stale RL doesn't linger
  • Stable-loss uses recent window: Detection now uses recent_results loss, not lifetime
    • Fixes sticky RL during recovery when lifetime loss is still high but recent is 0%
  • PMTUD probe ID collision: Added is_pmtud flag to pending map key
    • Completely eliminates collision between normal and PMTUD probes with same ProbeId
  • PMTUD consecutive counter logic: Direction changes now reset opposite counter
    • Ensures 2 truly consecutive results before advancing binary search bounds
  • PMTUD response size verification: Only process responses matching current probe size
    • Ignores late responses from previous probe sizes that could corrupt state
  • IPv6 Packet Too Big handling: Added dedicated PacketTooBig enum variant
    • ICMPv6 Type 2 now correctly triggers PMTUD MTU clamping
  • Multi-target JSON output: Multiple targets now wrapped in JSON array
    • Previously output invalid JSON (concatenated objects without delimiters)
  • TUI pause state sync: Switching targets now syncs pause indicator with target's state
    • Previously pause indicator could be stale after Tab/n target switch

Changed

  • Dependencies updated: ratatui 0.28→0.30, crossterm 0.28→0.29, maxminddb 0.24→0.27
    • Fixes RUSTSEC-2025-0132 (maxminddb unsafe memmap), RUSTSEC-2024-0436 (paste unmaintained)
  • Security audit CI: Added .github/workflows/audit.yml for daily RustSec advisory checks

Technical

  • PMTUD: PmtudState struct with binary search state (min/max bounds, success/failure counters)
  • PMTUD: PmtudPhase enum (WaitingForDestination, Searching, Complete)
  • PMTUD: set_dont_fragment() in socket.rs for Linux (IP_MTU_DISCOVER) and macOS (IP_DONTFRAG)
  • PMTUD: MTU extraction from ICMP errors in correlate.rs (Type 3 Code 4 for IPv4, Type 2 for ICMPv6)
  • PMTUD: packet_size field in PendingProbe for correlation
  • PMTUD: Engine sends PMTUD probes at destination TTL after normal traceroute finds destination
  • New src/state/ratelimit.rs module for detection logic
  • RateLimitInfo struct with suspected flag, confidence (0-1), reason, and loss data
  • Background async worker runs analysis every 2 seconds (lightweight)
  • Detection integrates with all modes: interactive TUI, batch, and streaming
  • JSON export includes rate limit data via serde
  • IX lookup uses tokio::sync::OnceCell for thread-safe lazy initialization
  • Refactored Receiver::new() and spawn_receiver() to use ReceiverConfig struct (9 args → 4 args)
  • Renamed internal fixed_port field to port_fixed for Rust naming consistency
  • Gateway detection: detect_gateway_ipv4() and detect_gateway_ipv6() in interface.rs
  • Gateway detection: detect_default_gateway() for auto-detected interface routing
  • InterfaceInfo extended with gateway_ipv4 and gateway_ipv6 fields
  • Session extended with source_ip and gateway fields for TUI display

[0.9.0] - 2026-01-13

Added

  • IX detection via PeeringDB: Identify Internet Exchange points in the path
    • Fetches IX peering LAN prefixes from PeeringDB API
    • Matches hop IPs against IX prefixes (IPv4 and IPv6)
    • Shows IX name, city, and country in hop detail view
    • Data cached locally for 24 hours to respect API rate limits
    • Cache stored in ~/.cache/ttl/peeringdb/ix_cache.json
    • Disable with --no-ix flag

Technical

  • New src/lookup/ix.rs module for PeeringDB integration
  • IxInfo struct added to ResponderStats for IX data
  • IxLookup handles API fetching, caching, and prefix matching
  • Background run_ix_worker updates session state like ASN/GeoIP workers
  • Added reqwest dependency for HTTP requests

[0.7.0] - 2026-01-13

Added

  • Interface binding: Force probes through a specific network interface
    • New --interface <NAME> flag binds all sockets to the specified interface
    • Useful for multi-homed hosts, VPN split tunneling, or deterministic egress path selection
    • Works with all probe protocols (ICMP, UDP, TCP)
    • Interface name shown in TUI title bar ("via eth0") and report output
    • Linux uses SO_BINDTODEVICE, macOS uses IP_BOUND_IF
  • Asymmetric routing support: New --recv-any flag
    • Requires --interface to be set
    • Disables receiver socket binding to interface
    • Allows receiving replies on any interface (for asymmetric routing, VPN scenarios)
    • Send sockets remain bound to the specified interface

Fixed

  • IPv6 interface detection: Fixed bug where global IPv6 addresses were incorrectly rejected
    • The link-local check used bitwise NOT (!v6.segments()[0]) instead of comparison (!=)
    • Global IPv6 addresses like 2001:db8::1 now correctly detected on dual-stack interfaces
  • Link-local only rejection: Non-loopback interfaces with only link-local IPv6 now return clear error
    • Link-local addresses require scope IDs and can't reach Internet targets
    • Error message explains the issue and suggests assigning a global address
  • Auto-protocol UDP binding: Auto-protocol mode now tests UDP with interface binding
    • Previously could select UDP even if interface binding would fail later
    • Now fails fast with clear error instead of confusing runtime failure

Technical

  • New src/probe/interface.rs module for cross-platform interface validation and binding
  • is_link_local_ipv6() helper function shared between production code and tests
  • InterfaceInfo struct holds validated interface name, index, IPv4/IPv6 addresses
  • Interface passed through ProbeEngine, Receiver, and all socket creation functions
  • recv_any field in Config controls receiver binding behavior
  • Uses pnet::datalink::interfaces() for enumeration, socket2 for binding

[0.6.1] - 2026-01-13

Fixed

  • Enrichment in batch/streaming modes: DNS, ASN, and GeoIP lookups now work in --json, --report, --csv, and --no-tui modes
    • Previously enrichment workers only spawned in interactive TUI mode
    • Batch mode waits for enrichment to settle before export
    • Streaming mode shows hostnames progressively as DNS resolves
  • Terminal state restoration: TUI now properly restores terminal on early errors or panics
    • Added scopeguard::defer! guard to ensure cleanup runs on all exit paths
    • Prevents terminal being left in raw/alternate screen mode on crash

Technical

  • Added scopeguard = "1" dependency for cleanup guards
  • run_batch_mode() and run_streaming_mode() now spawn enrichment workers
  • Streaming output includes hostname column when resolved

[0.6.0] - 2026-01-12

Added

  • Multiple simultaneous targets: Trace to multiple destinations at once
    • Pass multiple targets: ttl 8.8.8.8 1.1.1.1 google.com
    • Tab/n to switch to next target, Shift-Tab/N for previous
    • Target indicator in title bar shows [1/3] for current target
    • Per-target pause/reset (p/r affect only current target)
    • Each target runs its own probe engine with independent state
  • SessionMap architecture: Shared sessions map for multi-target support
    • SessionMap = Arc<RwLock<HashMap<IpAddr, Arc<RwLock<Session>>>>>
    • Single receiver demultiplexes responses to correct session
    • Lookup workers (DNS, ASN, GeoIP) iterate all sessions

Technical

  • PendingKey now includes target IP: (ProbeId, flow_id, IpAddr)
  • Receiver iterates target list to find matching probe
  • run_tui() accepts SessionMap and targets list
  • MainView::with_target_info() for target indicator display
  • Mixed IPv4/IPv6 targets not supported (single receiver limitation)

[0.5.1] - 2026-01-12

Added

  • NAT detection: Detect when NAT devices rewrite source ports
    • Compare sent source port vs returned port in ICMP error payloads
    • NAT indicator column ("!") in TUI when multi-flow mode enabled
    • [NAT] warning in title bar when NAT detected anywhere
    • Per-hop NAT details in hop detail view (match/rewrite counts, samples)
    • Warning when NAT may affect ECMP accuracy
    • NatInfo struct tracks port matches and rewrites per hop

Technical

  • PendingProbe now stores original_src_port for NAT detection
  • Hop::record_nat_check() compares original vs returned source ports
  • Session::has_nat() checks if NAT detected at any hop
  • NAT info included in JSON export via serde

[0.5.0] - 2026-01-12

Added

  • Paris/Dublin traceroute (ECMP detection): Multi-flow probing to discover parallel network paths
    • New --flows N flag: Send probes on N different flows (1-16, default 1)
    • New --src-port BASE flag: Base source port for flow identification (default 50000)
    • Each flow uses a different source port (UDP/TCP) for path differentiation
    • Routers using ECMP load balancing will route different flows to different paths
  • Per-flow path tracking: Track which responders are seen on each flow
    • FlowPathStats struct tracks sent/received/responder per flow
    • Hop::has_ecmp() detects when multiple paths exist
    • Hop::ecmp_paths() returns list of (flow_id, responder) pairs
    • Hop::path_count() returns number of unique paths discovered
  • ECMP display in TUI:
    • New "Paths" column in main table when --flows > 1
    • Column shows number of unique responders across flows
    • Highlighted in warning color when ECMP detected (>1 path)
    • Hop detail view shows per-flow path breakdown with hostnames
  • Source port extraction: ICMP error parsing extracts original source port for flow correlation

Fixed

  • Loss percentage "pulsing": Fixed visual glitch where loss would pulse on each hop
    • Loss now calculated from completed probes only: timeouts / (received + timeouts)
    • In-flight probes no longer count as temporary losses
    • Added timeouts counter to Hop struct for accurate tracking

Technical

  • Multi-flow UDP probing: Creates separate bound sockets per flow
  • Multi-flow TCP probing: Varies source port in raw SYN packets
  • Flow ID tracked in PendingProbe for response correlation
  • ParsedResponse.src_port field for flow identification from ICMP errors
  • PendingMap keyed by (ProbeId, flow_id) to prevent multi-flow entry collisions
  • Flow derivation validates port range to avoid mis-attribution from NAT rewrites
  • Backward compatible: --flows 1 (default) = identical to previous behavior

Known Limitations

  • NAT devices may rewrite source ports, causing multi-flow correlation to fail (responses will appear as losses)

[0.2.0] - 2025-01-12

Added

  • ASN column in main table: Network provider/ISP now visible at a glance
    • Shows AS name (e.g., "GOOGLE", "COMCAST") for each hop
    • Complements existing ASN details in hop detail view
  • TCP SYN probing mode: Send TCP SYN packets instead of ICMP Echo
    • Enable with -p tcp or --protocol tcp
    • Default port 80, customizable with --port flag
    • Probe ID encoded in TCP sequence number for correlation
    • Proper TCP checksum calculation with pseudo-header
  • Protocol auto-detection: Automatically select best available protocol
    • New default mode (-p auto): tries ICMP → UDP → TCP in order
    • Falls back when socket creation fails (e.g., no raw socket permission)
    • Seamless degradation for unprivileged users
  • Fixed port option: Disable per-TTL port variation for UDP/TCP
    • New --fixed-port flag keeps destination port constant
    • Useful for probing specific services (e.g., DNS on port 53)
  • High-rate optimizations: Improved performance at fast probe intervals
    • Batch drain limit (100 packets) prevents receiver starvation
    • Batched state updates reduce lock contention
    • Single lock acquisition per batch instead of per-packet
  • Receiver error tracking: Stop after 50 consecutive socket errors
    • Prevents infinite error loops when socket fails persistently
    • Logs error count progress (e.g., "Receive error (5/50): ...")
    • Graceful shutdown with descriptive error message
  • ASN lookup: Automatic ASN enrichment via Team Cymru DNS (enabled by default)
    • Displays ASN number, name, and BGP prefix in hop detail view
    • Supports both IPv4 and IPv6 addresses
    • Caching for 1 hour to reduce DNS queries
    • Disable with --no-asn flag
  • GeoIP lookup: Optional geolocation via MaxMind GeoLite2 database
    • Displays city, region, country, and coordinates in hop detail view
    • Auto-discovers database in common paths (~/.local/share/ttl/, /usr/share/GeoIP/)
    • Specify custom path with --geoip-db flag
    • Disable with --no-geo flag
  • UDP probing mode: Send UDP probes instead of ICMP Echo
    • Enable with -p udp or --protocol udp
    • Uses classic traceroute port range (33434+)
    • Port can be customized with --port flag
    • Probe ID encoded in UDP payload for correlation
  • Receiver panic handler: Captures panic details instead of generic error message
    • Uses catch_unwind for clean error reporting
    • Improves debugging when receiver thread fails
  • Enhanced jitter statistics: avg_jitter, max_jitter, and last_rtt now tracked and displayed
  • RTT percentiles: p50, p95, p99 calculated from sample history (last 256 samples)
  • MPLS label parsing: RFC 4884/4950 ICMP extensions parsed for MPLS label stacks
  • Enhanced hop detail view: Now displays percentiles, enhanced jitter stats, last RTT, and MPLS labels
  • Parallel DNS resolution: Up to 10 concurrent reverse DNS lookups for faster hostname resolution

Fixed

  • Startup false drops: Fixed race condition where fast ICMP responses arrived before probe was registered
    • Shared pending map with insert-before-send eliminates registration race
    • Socket drain before timeout cleanup prevents dropping queued responses
  • Improved accuracy for low-latency first hops
  • ASN TXT parsing: Fixed handling of quoted/split TXT records from Team Cymru DNS

Documentation

  • Jitter semantics: Clarified that jitter measures RTT variance, not inter-packet timing
    • Added detailed code comments explaining RFC 3550-inspired EWMA calculation
    • New "Statistics Explained" section in README with jitter/metrics documentation

Technical

  • TCP probe module (src/probe/tcp.rs) with SYN packet building and checksum calculation
  • TCP checksum uses actual source IP via UDP connect routing lookup (not 0.0.0.0)
  • TCP correlation support in ICMP error payload parsing
  • Batched receiver state updates for reduced lock contention
  • Added futures crate for parallel async operations
  • Sample history stored in circular buffer (256 entries) for percentile calculations
  • MplsLabel struct with RFC 4950 format parsing
  • MPLS extension parsing uses RFC 4884 length field (not fixed 128-byte offset)
  • Clarified jitter UI labels to distinguish smoothed vs raw sample stats
  • ASN lookup uses Team Cymru DNS (origin.asn.cymru.com, AS name lookup)
  • GeoIP lookup uses MaxMind GeoLite2-City database format
  • UDP probe correlation extracts ProbeId from UDP payload in ICMP errors
  • Receiver error tracking with consecutive failure counting

Changed

  • Library API boundary cleanup: Internal modules now use pub(crate) visibility
    • Public API: config, export, state modules
    • Internal (crate-only): cli, lookup, probe, trace, tui modules
    • Binary still has full access to all modules

[0.1.2] - 2025-01-12

Added

  • Theme persistence: saves selected theme to ~/.config/ttl/config.toml
  • Theme automatically restored on next launch
  • CLI --theme flag still overrides saved preference

[0.1.1] - 2025-01-12

Added

  • Theme support with 11 built-in themes via --theme flag
  • Themes: default, kawaii, cyber, dracula, monochrome, matrix, nord, gruvbox, catppuccin, tokyo_night, solarized
  • Runtime theme cycling with t key in TUI
  • Theme-aware UI rendering (borders, status colors, highlights)

[0.1.0] - 2025-01-12

Added

  • Initial release
  • ICMP Echo probing with TTL sweep (1-30 by default)
  • IPv4 and IPv6 support with extension header handling
  • Real-time TUI built with ratatui
  • Hop statistics: loss%, min/avg/max RTT, standard deviation, jitter
  • ECMP detection showing multiple responders per TTL
  • Reverse DNS resolution for hop IPs
  • Export formats: JSON, CSV, text report
  • Session replay from saved JSON files
  • Interactive TUI with j/k navigation, hop detail view
  • Loss-aware sparkline visualization
  • Pause/resume probing (p key)
  • Stats reset (r key)
  • Destination detection (automatically stops at actual hop count)
  • Platform support documentation (Linux, macOS)

Technical

  • Welford's online algorithm for numerically stable mean/variance
  • RFC 3550-style smoothed jitter calculation (measures RTT variance)
  • Probe correlation via ICMP sequence field encoding
  • IPv6 extension header parsing (Hop-by-Hop, Routing, Destination Options)
  • ICMP checksum validation for IPv4 Echo Reply
  • Graceful handling of receive buffer size limits

Security

  • Max TTL validation (capped at 64 to prevent resource exhaustion)
  • Replay file size limit (10MB max to prevent DoS)

Documentation

  • Troubleshooting section in README (permissions, high loss, IPv6, DNS)

Tests

  • 92 unit tests covering ICMP parsing, stats calculation, session state
  • 20 integration tests for probe→state pipeline
  • 9 property-based tests (proptest) for packet parsing robustness
  • Tests for IPv6 extension headers, ECMP scenarios, edge cases