Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.10.0] - 2026-07-18

Completes IPv6 across every supported platform: with Windows added, `ftr -6`
now works on macOS, Linux, Windows, and the BSDs — unprivileged everywhere
except the BSDs (which need root for all traceroute, matching their v4
posture).

### Added
- Windows IPv6 traceroute via the IP Helper `Icmp6SendEcho2` API — the
last platform without v6. No elevation required (same as v4); `-6` now
Expand All @@ -23,6 +30,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
examples that only surface under
`cargo clippy --target x86_64-pc-windows-msvc --all-targets` (CI's
clippy job runs on Ubuntu and never sees them) are all resolved
- Removed a dead `BsdAsyncIcmpSocket::new()` constructor surfaced by the
first release-profile builds of ftr on live FreeBSD 14.3 and OpenBSD 7.7
(the `cfg(target_os = "openbsd")` code compiled for the first time; both
BSD IPv6 builds are clean)

## [0.9.0] - 2026-07-17

Expand Down Expand Up @@ -623,7 +634,8 @@ let ftr = Ftr::with_caches(Some(asn_cache), None, None);
- Clean, informative output with RTT measurements
- Support for both hostnames and IP addresses

[Unreleased]: https://github.com/dweekly/ftr/compare/v0.9.0...HEAD
[Unreleased]: https://github.com/dweekly/ftr/compare/v0.10.0...HEAD
[0.10.0]: https://github.com/dweekly/ftr/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/dweekly/ftr/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/dweekly/ftr/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/dweekly/ftr/compare/v0.6.0...v0.7.0
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

**ftr** (Fast TRaceroute) is a high-performance parallel ICMP traceroute written in Rust. It sends probes concurrently for ~10x speedup over traditional traceroute, with automatic ASN lookup, reverse DNS, public IP detection via STUN, and network segment classification. Available as both a CLI tool and a Rust library. Cross-platform: Linux, macOS, Windows, FreeBSD, OpenBSD.

- **Version**: 0.9.0 (keep in sync with `Cargo.toml`)
- **Version**: 0.10.0 (keep in sync with `Cargo.toml`)
- **MSRV**: 1.85.0 (keep in sync with `rust-version` in `Cargo.toml`)
- **Rust Edition**: 2024
- **License**: MIT
Expand Down Expand Up @@ -76,7 +76,7 @@ git config core.hooksPath .githooks
| macOS | v4: Raw ICMP; v6: DGRAM ICMPv6 | v4: Yes; v6: No | v6 needs no root (Darwin unprivileged DGRAM ICMPv6) |
| Linux | Raw/DGRAM/UDP (v4+v6) | No (UDP via IP(V6)_RECVERR) | Unprivileged UDP traceroute both families |
| FreeBSD/OpenBSD | Raw ICMP (v4+v6) | Yes | Requires root for all operations |
| Windows | Win32 ICMP API (v4 only) | No | Uses IcmpSendEcho; IPv6 planned (Icmp6SendEcho2) |
| Windows | Win32 ICMP API (v4+v6) | No | IcmpSendEcho / Icmp6SendEcho2, not raw sockets |

## CI Pipeline

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ftr"
version = "0.9.0"
version = "0.10.0"
edition = "2024"
authors = ["David Weekly <david@weekly.org>"]
description = "A fast, parallel ICMP traceroute with ASN lookup, reverse DNS, and ISP detection"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A fast, parallel traceroute implementation with automatic ASN lookup. Available
- **ASN Enrichment** - Automatic AS number and organization lookup with caching
- **ISP Detection** - Identifies your ISP and classifies network segments
- **Cross-Platform** - Works on Linux, macOS, Windows, FreeBSD, and OpenBSD
- **IPv6** - Full IPv6 traceroute with v6 ASN/rDNS/STUN enrichment (`-6`); no root needed on macOS or Linux (Windows IPv6 planned)
- **IPv6** - Full IPv6 traceroute with v6 ASN/rDNS/STUN enrichment (`-6`) on all platforms; no root needed except on the BSDs
- **Multiple Protocols** - ICMP and UDP support with automatic fallback
- **JSON Output** - Structured output for programmatic use
- **Minimal Dependencies** - Efficient design with focus on performance
Expand Down
30 changes: 17 additions & 13 deletions docs/IMPROVEMENT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,24 @@ before/after `cargo tree` counts in the body.
network tests behind an env var and serialize them (SwiftFTR's
`NetworkTestGate` pattern) instead of letting them flake CI.

## IPv6: remaining platform + polish work

macOS, Linux, and BSD IPv6 shipped in 0.9.0 (PRs #38–#43). Remaining:

- Windows IPv6 via `Icmp6SendEcho2`, mirroring the v4 `IcmpSendEcho2`
implementation. Validate live on the `nwx-dell-11` Tailscale box (local
Parallels is unreliable); also pay down the ~16 dead-code lints visible
only under a Windows-target `clippy --all-targets` (CI's clippy job runs
on Ubuntu and never compiles windows.rs — consider a windows-target
clippy cross-check in CI).
- Open observations: router-originated ICMPv6 Time Exceeded on FreeBSD
remains unobserved first-hand (CI VM has no external v6); macOS root-mode
RAW-vs-DGRAM comparison (`sudo cargo run --release --example
## IPv6: polish + remaining validation

IPv6 traceroute ships on every supported platform as of 0.10.0 (macOS,
Linux, BSD in 0.9.0 via PRs #38–#43; Windows in 0.10.0 via #50), each
live-validated on real hardware/VMs with external v6. Remaining:

- Deferred live validation (needs privileged access we couldn't script):
router-originated ICMPv6 Time Exceeded observed first-hand on FreeBSD
(the FreeBSD VM has external v6, but raw sockets need root and the test
`ftr` user isn't in `wheel`; run from console or `pw group mod wheel -m
ftr`, then `ftr -6 2001:4860:4860::8888`). OpenBSD's VM has no external
v6, so its live multi-hop trace needs a v6-connected host. macOS
root-mode RAW-vs-DGRAM comparison (`sudo cargo run --release --example
spike_traceroute6`).
- CI gap that keeps biting: clippy runs only on Ubuntu, so `windows.rs` and
`bsd*.rs` dead-code/lints surface only when someone compiles those
targets (three separate cleanups so far). Add windows-target (and ideally
a BSD-target) `clippy --all-targets` cross-check to CI.
- Polish: NAT64/DNS64 handling review (`AI_V4MAPPED`-style synthesis for
v4 literals on v6-only networks); first-class zone-scoped (`fe80::%if`)
targets; hop-level zone display.
Expand Down
18 changes: 13 additions & 5 deletions tests/event_overhead_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,19 @@ async fn test_polling_vs_event_driven() {

println!("Event-driven approach: {:?}", event_elapsed);

// Event-driven should be close to 100ms, polling will have overhead
// Allow more tolerance for system load and CI environments
// The event fires after a 100ms sleep, so event_elapsed must be at least
// ~100ms (proves we actually waited on the channel, not a spurious wake).
// We deliberately do NOT assert a tight upper bound: this runs on shared
// CI runners where task scheduling can add tens to hundreds of ms of
// jitter under load, which made a 150ms ceiling flaky. The generous 2s
// ceiling still catches a real regression (a deadlocked channel or a hang)
// without failing on scheduling noise.
assert!(
event_elapsed < Duration::from_millis(150),
"Event-driven took too long: {:?}",
event_elapsed
event_elapsed >= Duration::from_millis(90),
"Event fired implausibly early ({event_elapsed:?}); expected to block ~100ms"
);
assert!(
event_elapsed < Duration::from_secs(2),
"Event-driven wakeup took far too long ({event_elapsed:?}); likely a hang"
);
}
Loading