Skip to content

Releases: lance0/xfr

v0.9.9

21 Apr 03:55

Choose a tag to compare

What's New

Added

  • Max jitter and packet size in UDP summary (#48 follow-up) — final UDP summary now reports Jitter Max (peak of the RFC 3550 running estimate) alongside the average, plus Packet Size (UDP payload bytes). Surfaced in plain text and JSON. Requested by @brettowe for NFS UDP packet-size tuning context.
  • -w short alias for --window (#60) — matches iperf3 muscle memory.

Changed

  • Bare-integer duration arguments mean seconds (#61) — -t 10, --max-duration 60, --rate-limit-window 30, and discover --timeout 5 now accept plain integers as seconds. Unit-suffixed forms (10s, 1min, 500ms) continue to work unchanged.
    • Side effect: --rate-limit-window now rejects zero (0, 0s, 0ms) — 0s was previously accepted and would later panic in the rate-limiter cleanup task because tokio::time::interval requires a non-zero duration. Other duration flags still accept 0 for their existing meanings (-t 0 is infinite).
  • Smoothed TUI jitter reading (#48) — the UDP stats panel shows jitter averaged over a 10-second rolling window rather than raw per-second samples. Server data pipeline is unchanged; display is smoothed. While running, the label reads Jitter (10s):; on completion it reverts to Jitter: with the authoritative final value from the server.

Fixed

  • Duplicate receive-error log on the server (#54) — tcp::receive_data/receive_data_half each warned at the read-error site, and the caller warned again on the returned Err. The inner warn! is removed so receive errors log exactly once. Reported by @matttbe.

  • Default to kernel TCP autotuning (#60) — xfr no longer forces SO_SNDBUF/SO_RCVBUF to 4 MB on either side by default. Both ends let the kernel autotune unless the user passes -w/--window. When set, the client's value propagates to the server over the control protocol so both sides apply the socket option symmetrically (matching iperf3). Reported by @matttbe.

    Caveats:

    • Loopback / intra-host benchmark numbers may decrease by roughly 10% — this is expected; the previous numbers were inflated by the oversized app-applied buffer.
    • On high-RTT paths, very short tests (e.g. -t 1s at high bitrate) may now show ramp-up-limited throughput in the final summary because kernel autotune takes a handful of RTTs to grow the window. Use a longer -t or pass an explicit -w to skip autotune. Note that -O/--omit only hides early intervals from output — the server's final summary is computed over the full test duration.
    • Explicit window sizes above c_int::MAX (≈2.1 GB on 64-bit) are now rejected with InvalidInput instead of silently wrapping before setsockopt.

Removed

  • Library API: pre-1.0 break — TcpConfig::high_speed() and TcpConfig::with_auto_detect() are gone; construct TcpConfig directly with the fields you want set. The HIGH_SPEED_BUFFER and HIGH_SPEED_WINDOW_THRESHOLD constants (which were private) are also removed. Downstream code that constructs ControlMessage::TestStart, protocol::UdpStats, or tui::app::App by name now needs to supply the new fields (window_size, jitter_max_ms, packet_size, jitter_history); all are additive with sensible defaults.

Install

  • Homebrew: brew upgrade xfr (via lance0/tap)
  • crates.io: cargo install xfr
  • Pre-built binaries: see assets below

Full Changelog: v0.9.8...v0.9.9

v0.9.8

17 Apr 19:57

Choose a tag to compare

Highlights

Fast, accurate TCP teardown (issue #54) — the shutdown() drain on the send path was waiting for bufferbloated send queues to ACK through rate-limited paths, sometimes far past the test duration. Replaced with SO_LINGER=0 (abortive close) on Linux, with tcpi_bytes_acked clamping so abrupt close doesn't overcount discarded tail bytes. macOS keeps graceful shutdown() since it lacks the bytes_acked counter.

Separate send/recv reporting in bidir tests (issue #56)--bidir now reports per-direction bytes and throughput so asymmetric links no longer collapse into a single misleading number. Visible in plain text, JSON (4 new optional fields), CSV (4 new columns), and the TUI throughput panel (↑ X / ↓ Y).

What's New

Added

  • Per-direction reporting in --bidir results (#56)

Fixed

  • TCP teardown no longer blocks on shutdown() drain (#54)
  • Sender-side byte counts clamped to tcpi_bytes_acked to remove ~5-10% overcount on bufferbloated/rate-limited links

Install

# Homebrew
brew install lance0/tap/xfr

# Cargo
cargo install xfr

# Download binary
gh release download v0.9.8 --repo lance0/xfr

Full Changelog: v0.9.7...v0.9.8

v0.9.7

16 Apr 15:26

Choose a tag to compare

Highlights

Early exit summary (issue #35) — Ctrl+C now displays a test summary with accumulated stats instead of silently exiting. Works in both plain text and TUI modes. Double Ctrl+C force-exits immediately.

DSCP server-side propagation--dscp flag is now sent to the server and applied to server-side TCP/UDP sockets for download and bidirectional tests. Previously only client-side sockets were marked.

What's New

Added

  • Early exit summary — Ctrl+C triggers graceful cancel, waits for server result, and displays full test summary
  • DSCP propagated to server via TestStart protocol message for download/bidir tests
  • Non-Unix --dscp warning shown before test starts

Fixed

  • Cancel flow waits for server Result message instead of immediately erroring
  • Server sends Result before slow post-processing (push gateway, metrics) to prevent false cancel timeouts
  • Rust 1.95 clippy compatibility (manual_checked_ops, collapsible_match_arms)

Changed

  • Bump softprops/action-gh-release from 2 to 3

Install

# Homebrew
brew install lance0/tap/xfr

# Cargo
cargo install xfr

# Download binary
gh release download v0.9.7 --repo lance0/xfr

Full Changelog: v0.9.6...v0.9.7

v0.9.6

18 Mar 12:53

Choose a tag to compare

Added

  • --dscp flag — DSCP/TOS marking on TCP and UDP client sockets for QoS policy testing. Accepts raw TOS byte (0-255) or DSCP names (EF, AF11-AF43, CS0-CS7, VA). Uses IP_TOS for IPv4, IPV6_TCLASS for IPv6. Warns for QUIC and non-Unix platforms.
  • omit_secs config support (#43) — [client] omit_secs = N in config.toml sets default --omit. CLI --omit (including --omit 0) takes precedence.
  • Per-interval jitter in TUI and plain text (#48) — TUI stream bars show jitter: X.XXms for UDP. Plain text interval lines show jitter: X.XXms lost: N for UDP, rtx: N rtt: Xms for TCP. Multi-stream jitter is averaged across streams.

Full Changelog: v0.9.5...v0.9.6

v0.9.5

17 Mar 14:41

Choose a tag to compare

Added

  • TCP --cport support (#44) — --cport now pins client-side TCP data-stream source ports for ECMP load balancing and strict egress firewall scenarios. Multi-stream TCP (-P N) uses sequential ports (cport, cport+1, ..., cport+N-1), matching existing UDP behavior.
  • Nix flake (#46) — nix run github:lance0/xfr or nix develop for a dev shell. Thanks to @deephack1982 for the contribution!

Changed

  • TCP --cport semantics — TCP control connection remains on an ephemeral source port while data streams use the requested port or range. TCP data binds now match the remote address family (dual-stack fix). Overlap between control and data ports is detected and rejected.

Full Changelog: v0.9.4...v0.9.5

v0.9.4

11 Mar 17:00

Choose a tag to compare

Added

  • --no-mdns flag (#41) — xfr serve --no-mdns disables mDNS service registration for environments where multicast is unwanted or another service already uses mDNS.
  • server.no_mdns config support — also configurable via [server] no_mdns = true in ~/.config/xfr/config.toml.

Changed

  • Delta retransmits in interval reports (#36) — plain text interval lines now show per-interval retransmit deltas instead of cumulative totals, making it easier to spot when retransmits actually occur. Hidden intervals from --omit, --quiet, or larger --interval settings no longer get folded into the next visible rtx: value. Final summary still shows cumulative totals.

Full Changelog: v0.9.3...v0.9.4

v0.9.3

10 Mar 14:54

Choose a tag to compare

Added

  • Server --bind flag (#38) — xfr serve --bind <IP> binds TCP, QUIC, and UDP data listeners to a specific address. Validates against -4/-6 flags and rejects unspecified addresses (::, 0.0.0.0).

Changed

  • Server sends random payloads (#34) — server-side TCP and UDP send paths now use random bytes by default in reverse and bidirectional modes, matching the client's default-on behavior.

Fixed

  • QUIC dual-stack on Windows (#39) — QUIC server endpoint now creates its UDP socket via socket2 with explicit IPV6_V6ONLY handling instead of relying on Quinn's Endpoint::server(). On Windows/macOS where IPV6_V6ONLY defaults to true, binding to [::] would only accept IPv6 connections.
  • Server random payload on single-port TCP reverse (#34) — the single-port TCP handler (DataHello path used by all modern clients) was missing random_payload = true, causing reverse-mode downloads to still send zeros.

Security

  • quinn-proto DoS fix — updated quinn-proto 0.11.13 → 0.11.14 (RUSTSEC-2026-0037, severity 8.7)

Full Changelog: v0.9.2...v0.9.3

v0.9.2

06 Mar 20:34

Choose a tag to compare

Full Changelog: v0.9.1...v0.9.2

v0.9.1

05 Mar 18:48

Choose a tag to compare

What's New (includes v0.9.0 + v0.9.1)

v0.9.0 was tagged but never released due to CI issues. This release includes all v0.9.0 features plus v0.9.1 fixes.

Added

  • MPTCP support (--mptcp) — Multi-Path TCP on Linux 5.6+ (issue #24). Uses IPPROTO_MPTCP at socket creation via socket2. All TCP features work transparently. Server automatically creates MPTCP listeners when available (no flag needed) — accepts both MPTCP and regular TCP clients with silent fallback.
  • Kernel TCP pacing via SO_MAX_PACING_RATE (issue #30) — On Linux, -b now uses the kernel's FQ scheduler with EDT for precise per-packet timing, eliminating burst behavior from userspace sleep/wake cycles. Falls back to userspace pacing on non-Linux, MPTCP sockets, or if the setsockopt fails. Note: -b sets a global bitrate shared across all parallel streams (unlike iperf3 where -b is per-stream).

Changed

  • Library APIcreate_tcp_listener(), connect_tcp(), and connect_tcp_with_bind() now take a mptcp: bool parameter. Pass false to preserve existing behavior.

Fixed

  • High stream-count teardown hardening (issue #32) — client now stops local data streams at local duration expiry instead of waiting for server Result, scales stream join timeout with stream count (max(2s, streams*50ms)), and TCP receivers drain briefly after cancel to reduce reset-on-close bursts at high -P.
  • Best-effort send shutdownsend_data() shutdown no longer propagates errors during normal teardown races.
  • Kernel pacing rate widthSO_MAX_PACING_RATE now uses native c_ulong instead of u32, removing an unintended ~34 Gbps ceiling on 64-bit Linux.
  • JoinHandle panic with many parallel streams (issue #24) — removed second join_all after aborting timed-out stream tasks.
  • Final summary showing 0 retransmits/RTT/cwnd (issue #26) — each stream task captures a final sender-side TCP_INFO snapshot before socket close.
  • Broken pipe / connection reset at teardown (issue #25) — client joins stream task handles with timeout before returning.
  • MPTCP label in server log — server displays "MPTCP" instead of "TCP" when client uses --mptcp.

Install

# Homebrew
brew install lance0/tap/xfr

# Cargo
cargo install xfr

# Binary (Linux x86_64)
curl -sSL https://github.com/lance0/xfr/releases/download/v0.9.1/xfr-x86_64-unknown-linux-musl.tar.gz | tar xz

Acknowledgments

Thanks to @matttbe (Linux kernel MPTCP co-maintainer) for extensive testing, bug reports (#24, #25, #30, #32), suggesting kernel TCP pacing via SO_MAX_PACING_RATE, and catching the FIN vs RST teardown behavior.

Full Changelog: v0.8.0...v0.9.1

v0.8.0

12 Feb 20:42

Choose a tag to compare

Full Changelog: v0.7.1...v0.8.0