Skip to content

Commit cd78543

Browse files
committed
Bump version to v0.18.0
1 parent 42c0785 commit cd78543

File tree

16 files changed

+1021
-212
lines changed

16 files changed

+1021
-212
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.18.0] - 2026-02-13
9+
10+
### Added
11+
- **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)
12+
- **`E` indicator**: New ECMP indicator in main table replaces misleading `!` (route flap) when ECMP is the actual cause
13+
- **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
14+
- **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
15+
- **Hop detail view**: Per-packet ECMP section shows responder count and path count; per-flow ECMP section unchanged
16+
- **Last RTT semantics documented**: `Last` column tracks primary responder's most recent RTT (code docs + KNOWN_ISSUES)
17+
- **Main table layout guard tests**: Header/cell/width count parity verified across Auto/Compact/Wide x single-flow/multi-flow
18+
- **IPv6 RAW payload fallback tests**: Echo Reply and Time Exceeded parsing tests for IPv6
19+
20+
### Changed
21+
- **Flap detection suppressed during ECMP**: `!` indicator no longer fires when per-packet ECMP is detected at a hop
22+
- **Probe engines use per-session config**: Each target's engine sees the effective flow count, not the raw CLI value
23+
824
## [0.17.0] - 2026-02-12
925

1026
### Added

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ttl"
3-
version = "0.17.0"
3+
version = "0.18.0"
44
edition = "2024"
55
rust-version = "1.88"
66
description = "Modern traceroute/mtr-style TUI with hop stats and optional ASN/geo enrichment"

KNOWN_ISSUES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ This document tracks known limitations and edge cases that are documented but no
7878

7979
### `--flows` Has No Effect with ICMP Protocol
8080

81-
ICMP probes have no source/destination port to vary, so all probes are sent as flow 0 regardless of the `--flows` value. Multi-flow ECMP detection requires `--protocol udp` or `--protocol tcp`. A runtime warning will be added in a future release (#46).
81+
ICMP probes have no source/destination port to vary, so all probes are sent as flow 0 regardless of the `--flows` value. Multi-flow ECMP detection requires `--protocol udp` or `--protocol tcp`. ttl warns at runtime when `--flows > 1` resolves to effective ICMP probing.
8282

8383
### macOS Requires Root
8484

@@ -104,7 +104,7 @@ Linux delivers ICMPv6 Echo Reply only to the socket that sent the request, not t
104104

105105
### Last RTT Not Persisted in JSON
106106

107-
`last_rtt` is intentionally `#[serde(skip)]` — it represents the most recent probe response and is inherently a live-only metric. The `Last` column in TUI and `last_ms` in CSV will show "-"/empty for replayed sessions. All other RTT and jitter stats are persisted and display correctly.
107+
`last_rtt` is intentionally `#[serde(skip)]` — it represents a live-only sample. The `Last` column in TUI and `last_ms` in CSV are sourced from the current primary responder's `last_rtt`, and will show "-"/empty for replayed sessions. All other RTT and jitter stats are persisted and display correctly.
108108

109109
### TCP Bitrate Not Paced
110110

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ See [Installation](#installation) below for setup instructions.
3535
- **Fast continuous path monitoring** with detailed hop statistics
3636
- **Multiple simultaneous targets** - trace to several destinations at once
3737
- **Paris/Dublin traceroute** - multi-flow probing for ECMP path enumeration
38+
- **ECMP classification** - distinguishes per-flow vs per-packet load balancing
3839
- **Path MTU discovery** - binary search for maximum unfragmented size
3940
- **NAT detection** - identify when NAT devices rewrite source ports
4041
- **Route flap detection** - alert on path changes indicating routing instability
@@ -215,7 +216,7 @@ Unstable BGP or failover issues cause intermittent problems that are hard to cat
215216
sudo ttl -i 0.5 production-server.com
216217
```
217218

218-
TTL tracks when the responding IP at a hop changes. The `!` indicator flags route flaps, and hop details show change history.
219+
TTL tracks when the responding IP at a hop changes. The `!` indicator flags route flaps, and hop details show change history. ECMP load balancing shows `E` instead, so you can distinguish real instability from expected multi-path behavior.
219220

220221
### Detect Transparent Proxies
221222

ROADMAP.md

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**mtr is the de facto standard** for interactive traceroute but hasn't seen major feature development in years. **trippy** (Rust) is the main modern alternative but focuses on a different feature set.
66

77
**Key advantages ttl already has:**
8-
- ECMP path enumeration (Paris/Dublin traceroute with `--flows`)
8+
- ECMP path enumeration with per-flow/per-packet classification (`--flows`)
99
- NAT detection (source port rewrite analysis)
1010
- ICMP rate limit detection (distinguish rate limiting from real loss)
1111
- Route flap and asymmetric routing detection
@@ -60,7 +60,7 @@
6060
- [x] Update notifications (checks GitHub releases, install-method-aware)
6161
- [x] FreeBSD support (experimental, raw sockets)
6262

63-
## Completed (v0.15.x - v0.17.x)
63+
## Completed (v0.15.x - v0.18.x)
6464

6565
- [x] Animated replay (`--replay file --animate`) with speed control
6666
- [x] Probe event recording for replay accuracy
@@ -72,6 +72,11 @@
7272
- [x] Last RTT column in main table (mtr parity — `Loss% Snt Last Avg Min Max StdDev`)
7373
- [x] JAvg and JMax columns in Wide display mode
7474
- [x] Wider ASN column for full AS name visibility
75+
- [x] ECMP classification: per-flow vs per-packet detection with primary_ratio heuristic (#46)
76+
- [x] Paths column reflects actual responder count for per-packet ECMP (#46)
77+
- [x] `E` indicator for ECMP detected vs `!` for route flap (#46)
78+
- [x] Effective flow capability: `--flows` + ICMP warns and collapses to single-flow (#46)
79+
- [x] Receiver flow attribution hardening: unknown flows only match when unambiguous (#46)
7580

7681
---
7782

@@ -81,20 +86,20 @@
8186

8287
**Why this matters:** Per-packet load balancing (common on Arista, Juniper, Cisco) is undercounted by the current flow-primary model. Users see 8 responders in the detail view but "Paths: 1" in the main table. Related: #46
8388

84-
- [ ] Detect per-packet vs per-flow ECMP (primary_ratio heuristic per flow)
85-
- [ ] Paths column reflects actual responder count for per-packet ECMP
86-
- [ ] Separate indicators: `E` for ECMP detected vs `!` for route flap
87-
- [ ] Warn when `--flows > 1` with effective ICMP probing (`-p icmp`, or `-p auto` when auto-select resolves to ICMP)
88-
- [ ] Define `-p auto` warning semantics for multi-target/mixed-family runs (warn if any target resolves to effective ICMP, avoid duplicate spam)
89-
- [ ] Track effective flow capability at runtime (requested `--flows` vs effective protocol) and use it for flap detection + NAT/Paths column visibility
90-
- [ ] Add CLI/TUI hint that flow-based ECMP detection is meaningful with UDP/TCP probes
91-
- [ ] Keep Paths value + highlight + host indicator driven by one shared ECMP classification (avoid count/style drift)
92-
- [ ] Handle out-of-range returned src ports as unknown flow (not forced flow 0) to avoid false per-flow attribution behind NAT/CGNAT
93-
- [ ] Update indicator/UI budget for new `E` marker (host width autosize currently assumes `" !~^"`)
94-
- [ ] Update user-facing indicator docs/help (`E` vs `!`) in CLI help + docs pages
95-
- [ ] Add tests for per-packet ECMP classification, `-p auto` ICMP warning behavior, and out-of-range src-port flow attribution
96-
- [ ] #46 acceptance: per-packet ECMP no longer presents as misleading `Paths: 1` when many responders are observed
97-
- [ ] #46 acceptance: `E` (ECMP) and `!` (route flap) are no longer conflated in the same scenario
89+
- [x] Detect per-packet vs per-flow ECMP (primary_ratio heuristic per flow)
90+
- [x] Paths column reflects actual responder count for per-packet ECMP
91+
- [x] Separate indicators: `E` for ECMP detected vs `!` for route flap
92+
- [x] Warn when `--flows > 1` with effective ICMP probing (`-p icmp`, or `-p auto` when auto-select resolves to ICMP)
93+
- [x] Define `-p auto` warning semantics for multi-target/mixed-family runs (warn if any target resolves to effective ICMP, avoid duplicate spam)
94+
- [x] Track effective flow capability at runtime (requested `--flows` vs effective protocol) and use it for flap detection + NAT/Paths column visibility
95+
- [x] Add CLI/TUI hint that flow-based ECMP detection is meaningful with UDP/TCP probes
96+
- [x] Keep Paths value + highlight + host indicator driven by one shared ECMP classification (avoid count/style drift)
97+
- [x] Handle out-of-range returned src ports as unknown flow (not forced flow 0) to avoid false per-flow attribution behind NAT/CGNAT
98+
- [x] Update indicator/UI budget for new `E` marker (host width autosize currently assumes `" !~^"`)
99+
- [x] Update user-facing indicator docs/help (`E` vs `!`) in CLI help + docs pages
100+
- [x] Add tests for per-packet ECMP classification, `-p auto` ICMP warning behavior, and out-of-range src-port flow attribution
101+
- [x] #46 acceptance: per-packet ECMP no longer presents as misleading `Paths: 1` when many responders are observed
102+
- [x] #46 acceptance: `E` (ECMP) and `!` (route flap) are no longer conflated in the same scenario
98103
- [ ] Paris strategy for UDP (`--strategy paris` — fixed 5-tuple, checksum encodes sequence) *(follow-on after #46 core fix)*
99104
- [ ] Dublin strategy for UDP (`--strategy dublin` — IP ID field encodes sequence) *(follow-on after #46 core fix)*
100105

@@ -137,9 +142,9 @@
137142
### Quick Wins (low effort, high impact)
138143
- [ ] **Progress indicator in replay** — show position in timeline during animated replay
139144
- [ ] **Interactive replay** — step through events, jump to time
140-
- [ ] **Last metric semantics**decide and document whether `Last` is hop-most-recent (any responder) or primary-responder-most-recent; align TUI/CSV behavior and labels
141-
- [ ] **IPv6 RAW payload fallback tests** — unit tests for IPv6 Echo Reply and Time Exceeded parsing
142-
- [ ] **Main table layout tests** — verify header/cell/width count parity across Auto/Compact/Wide × single-flow/multi-flow modes
145+
- [x] **Last metric semantics**documented as primary-responder-most-recent; TUI/CSV aligned
146+
- [x] **IPv6 RAW payload fallback tests** — unit tests for IPv6 Echo Reply and Time Exceeded parsing
147+
- [x] **Main table layout tests** — verify header/cell/width count parity across Auto/Compact/Wide × single-flow/multi-flow modes
143148

144149
### Medium Effort (moderate effort, high impact)
145150
- [ ] **PCAP export** — write probe/response packets to .pcap for Wireshark analysis
@@ -210,7 +215,7 @@
210215
| trippy | Rust | Yes (UDP) | No | No | Yes | Active |
211216
| traceroute | C | No | Yes | No | No | Maintenance |
212217
| tracepath | C | No | Yes | No | No | Maintenance |
213-
| **ttl** | Rust | Yes | Yes | Yes | Yes | Active |
218+
| **ttl** | Rust | Yes (per-flow + per-packet) | Yes | Yes | Yes | Active |
214219

215220
---
216221

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ Only the latest release is supported with security updates.
2222

2323
| Version | Supported |
2424
|---------|-----------|
25-
| 0.17.x | Yes |
26-
| < 0.17 | No |
25+
| 0.18.x | Yes |
26+
| < 0.18 | No |

docs/COMPARISON.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ How ttl compares to other traceroute and network diagnostic tools.
2323
| IX detection | :white_check_mark: | :x: | :x: | :x: | :x: |
2424
| **ECMP** ||||||
2525
| Multi-path detection | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
26+
| Per-flow/per-packet classification | :white_check_mark: | :x: | :x: | :x: | :x: |
2627
| Paris traceroute | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: |
2728
| **TUI** ||||||
2829
| Interactive | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: |

docs/FEATURES.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ ECMP routers hash on the 5-tuple: (src_ip, dst_ip, src_port, dst_port, protocol)
6060
- The TUI shows a "Paths" column when `--flows > 1`
6161
- Paths are highlighted when multiple responders are detected
6262

63+
### ECMP Classification
64+
65+
ttl distinguishes two types of ECMP:
66+
67+
- **Per-flow ECMP**: Each flow consistently maps to one next-hop. Different flows take different paths. The Paths column shows unique flow primaries.
68+
- **Per-packet ECMP**: Responses rotate between responders regardless of flow. The Paths column shows the actual number of observed responders.
69+
70+
Classification uses a primary concentration heuristic: if no single responder dominates a flow's responses (below 70% threshold), it's per-packet. The `E` indicator appears in the main table when ECMP is detected at a hop.
71+
72+
**Note:** `--flows` requires UDP or TCP probing (`-p udp` or `-p tcp`). ICMP probes have no port to vary, so flows are always 1 in ICMP mode. ttl warns at startup if `--flows > 1` resolves to effective ICMP probing.
73+
6374
### Paris vs Dublin
6475

6576
- **Paris traceroute**: Varies source port to enumerate paths
@@ -113,9 +124,10 @@ Trace all IP addresses that a hostname resolves to. Useful for:
113124

114125
ttl detects route instability when the primary responder IP changes at a hop:
115126

116-
- Main table shows "!" indicator after hostname when route changes detected
127+
- Main table shows `!` indicator after hostname when route changes detected
128+
- `!` is suppressed when ECMP is detected at the hop (`E` shown instead) — per-packet load balancing is expected multi-path behavior, not instability
117129
- Hop detail view (Enter key) shows route change history with timestamps
118-
- Uses hysteresis (margin of 2 responses) to avoid false positives from per-packet load balancing
130+
- Uses hysteresis (margin of 2 responses) to avoid false positives
119131
- Requires 5+ responses before recording changes (avoids startup noise)
120132
- History capped at 50 changes per hop
121133
- Only active in single-flow mode (disabled when `--flows > 1` since ECMP expects path variation)

src/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ DETECTION INDICATORS:
3232
[RL?] - Router rate-limiting ICMP (loss may be artificial)
3333
[ASYM] - Asymmetric routing detected (return path differs)
3434
[TTL!] - TTL manipulation detected (middlebox modifying TTL)
35-
! - Route flap at this hop (path instability)
35+
E - ECMP detected at this hop (single-flow marker; see Paths in multi-flow)
36+
! - Route flap at this hop (path instability, when ECMP not indicated)
3637
~ - Asymmetric routing suspected at this hop
3738
^ - TTL manipulation suspected at this hop
3839

0 commit comments

Comments
 (0)