lance0
xfr
Rust

A modern iperf3 alternative with a live TUI, multi-client server, and QUIC support. Built in Rust.

Last updated Jul 9, 2026
513
Stars
21
Forks
3
Issues
+1
Stars/day
Attention Score
63
Language breakdown
Rust 97.0%
Shell 2.8%
Nix 0.2%
โ–ธ Files click to expand
README

xfr

xfr logo

A modern iperf3 alternative with a live TUI, multi-client server, MPTCP, and QUIC support. Built in Rust.

Crates.io CI License Ko-fi

Quick Start

# Server
xfr serve

Client (in another terminal or machine)

xfr 192.168.1.1 # Basic TCP test xfr 192.168.1.1 -b 100M # TCP at 100 Mbps xfr 192.168.1.1 -P 4 # 4 parallel streams xfr 192.168.1.1 -u -b 1G # UDP at 1 Gbps

See Installation below for setup instructions.

TUI Preview

xfr live TUI: real-time throughput graph, per-second stats, and settings

Features

  • Live TUI with real-time throughput graphs and per-stream stats
  • Server dashboard - xfr serve --tui for monitoring active tests
  • Multi-client server - handle multiple simultaneous tests
  • TCP, UDP, QUIC, and MPTCP with configurable bitrate pacing and parallel streams
  • Firewall-friendly - single-port TCP and UDP (all traffic on 5201), QUIC multiplexing, and --cport for pinning data source ports
  • Bidirectional testing - measure upload and download simultaneously
  • Multiple output formats - plain text, JSON, JSON streaming, CSV
  • Result comparison - xfr diff to detect performance regressions
  • LAN discovery - find xfr servers with mDNS (xfr discover)
  • Prometheus metrics - export stats for monitoring dashboards
  • Config file - save defaults in ~/.config/xfr/config.toml
  • Environment variables - XFRPORT, XFRDURATION overrides

vs iperf3

| Feature | iperf3 | xfr | |---------|--------|-----| | Live TUI | No | Yes (client & server) | | Multi-client server | No | Yes | | MPTCP | No | Yes (auto on server, --mptcp on client, Linux 5.6+) | | Firewall-friendly | --cport (TCP/UDP) | Single-port TCP & UDP + --cport | | Output formats | Text/JSON | Text/JSON/CSV | | Prometheus metrics | No | Yes (optional feature) | | Compare runs | No | xfr diff | | LAN discovery | No | xfr discover | | Config file | No | Yes |

Real-World Use Cases

VPN Tunnel Testing

Measure actual throughput through your VPN:
# On VPN server
xfr serve

From client, through VPN

xfr 10.8.0.1 -t 30s

UDP Congestion Detection

Test UDP at your expected rate to detect packet loss:
xfr <host> -u -b 500M -t 60s    # Watch for loss percentage in TUI

Path MTU Discovery

Find the largest UDP payload that survives the path โ€” per direction, with the don't-fragment flag set so middleboxes can't hide the limit by fragmenting (useful for NFS-over-UDP and VPN/tunnel tuning):
xfr <host> --probe-mtu

Before/After Comparison

Quantify the impact of network changes:
xfr <host> --json -o before.json

... make changes ...

xfr <host> --json -o after.json xfr diff before.json after.json --threshold 5

Multi-Stream for Bonded Connections

Test aggregate bandwidth across bonded/LACP interfaces:
xfr <host> -P 8 -t 30s          # 8 streams to utilize all links

Prometheus Monitoring

Continuous performance monitoring:
xfr serve --prometheus 9090 --push-gateway http://pushgateway:9091

Scrape metrics or view in Grafana

Installation

From crates.io (Recommended)

Requires Rust 1.88+:

# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

Install xfr

cargo install xfr

Homebrew (macOS/Linux)

brew install lance0/tap/xfr

Docker (GHCR)

Multi-arch image (amd64/arm64), handy for running a server:

docker run --rm -p 5201:5201 -p 5201:5201/udp ghcr.io/lance0/xfr:latest serve

Pre-built Binaries

Download from GitHub Releases:

| Platform | Target | |----------|--------| | Linux x8664 | xfr-x8664-unknown-linux-musl.tar.gz | | Linux ARM64 | xfr-aarch64-unknown-linux-gnu.tar.gz (or -musl for fully static) | | macOS Apple Silicon | xfr-aarch64-apple-darwin.tar.gz | | macOS Intel | Use cargo install xfr | | Android (Termux) | xfr-aarch64-linux-android.tar.gz | | Windows | Use WSL2 (native support is experimental) |

# Example: Linux x86_64
curl -LO https://github.com/lance0/xfr/releases/latest/download/xfr-x86_64-unknown-linux-musl.tar.gz
tar xzf xfr-*.tar.gz && sudo mv xfr /usr/local/bin/

eget

eget lance0/xfr

Arch Linux (AUR)

yay -S xfr-bin

From Source

git clone https://github.com/lance0/xfr
cd xfr && cargo build --release
sudo cp target/release/xfr /usr/local/bin/

Quick Install Script

Note: Review scripts before piping to sh. See the install script source.
curl -fsSL https://raw.githubusercontent.com/lance0/xfr/master/install.sh | sh

Termux (Android)

Download the aarch64-linux-android binary from releases, or build from source:

pkg install rust
cargo install xfr

Nix

nix run github:lance0/xfr          # Run without installing
nix profile install github:lance0/xfr  # Install to profile

Or add to your flake inputs:

inputs.xfr.url = "github:lance0/xfr";

A dev shell is also available via nix develop.

NetBSD

Available via pkgsrc:

pkgin install xfr

Optional Features

| Feature | Default | Description | |---------|---------|-------------| | discovery | Yes | mDNS LAN discovery (xfr discover) | | prometheus | No | Prometheus metrics endpoint and Push Gateway support |

cargo install xfr --features prometheus    # Prometheus support
cargo install xfr --all-features           # All features

Shell Completions

# Bash
xfr --completions bash > ~/.local/share/bash-completion/completions/xfr

Zsh (add ~/.zfunc to fpath in .zshrc first)

xfr --completions zsh > ~/.zfunc/_xfr

Fish

xfr --completions fish > ~/.config/fish/completions/xfr.fish

PowerShell (add to $PROFILE)

xfr --completions powershell >> $PROFILE

Elvish

xfr --completions elvish > ~/.elvish/lib/xfr.elv

Usage

Server

xfr serve                    # Listen on port 5201
xfr serve -p 9000            # Custom port
xfr serve --tui              # Live dashboard showing active tests
xfr serve --one-off          # Exit after one test
xfr serve --max-duration 60s # Limit test duration
xfr serve --push-gateway http://pushgateway:9091  # Push metrics on test complete
xfr serve --psk mysecret     # Require PSK authentication
xfr serve --rate-limit 2     # Max 2 concurrent tests per IP
xfr serve --allow 192.168.0.0/16 --deny 0.0.0.0/0  # IP ACL

Client

xfr 192.168.1.1              # TCP test, 10s, single stream
xfr 192.168.1.1 -t 30s       # 30 second test
xfr 192.168.1.1 -P 4         # 4 parallel streams
xfr 192.168.1.1 -R           # Reverse (download test)
xfr 192.168.1.1 --bidir      # Bidirectional
xfr 192.168.1.1 -6           # Force IPv6 only
xfr ::1 -6                   # IPv6 localhost

UDP Mode

xfr 192.168.1.1 -u           # UDP mode
xfr 192.168.1.1 -u -b 1G     # UDP at 1 Gbps
xfr 192.168.1.1 -u -b 100M   # UDP at 100 Mbps

QUIC Mode

xfr 192.168.1.1 --quic       # QUIC transport (encrypted)
xfr 192.168.1.1 --quic -P 4  # QUIC with 4 parallel streams
xfr 192.168.1.1 --quic -R    # QUIC download test

QUIC provides built-in TLS 1.3 encryption with stream multiplexing over a single connection.

Security Note: QUIC encrypts traffic but does not verify server identity by default. For authenticated connections, use --psk on both client and server to prevent MITM attacks.

MPTCP Mode

xfr 192.168.1.1 --mptcp       # MPTCP (Multi-Path TCP, Linux 5.6+)
xfr 192.168.1.1 --mptcp -P 4  # MPTCP with 4 parallel streams
xfr 192.168.1.1 --mptcp -R    # MPTCP download test

MPTCP enables a single connection to use multiple network paths simultaneously (e.g., WiFi + Ethernet). The server automatically creates MPTCP listeners โ€” no flag needed on the server side. All TCP features (nodelay, congestion control, window size, bidir, multi-stream) work transparently with MPTCP.

Output Formats

xfr <host> --json              # JSON summary
xfr <host> --json-stream       # JSON per interval (for scripting)
xfr <host> --csv               # CSV output
xfr <host> -q                  # Quiet mode (summary only)
xfr <host> -o results.json     # Save to file
xfr <host> --no-tui            # Plain text, no TUI
xfr <host> --timestamp-format iso8601  # ISO 8601 timestamps

Note: Log messages go to stderr, allowing clean JSON/CSV piping: xfr <host> --json 2>/dev/null

Interval Control

xfr <host> -i 2                # Report every 2 seconds
xfr <host> --omit 3            # Skip first 3s of intervals (TCP ramp-up)

Compare Results

xfr diff baseline.json current.json
xfr diff baseline.json current.json --threshold 5

Discovery

xfr discover                 # Find xfr servers on LAN
xfr discover --timeout 10s   # Extended search

Keybindings (Client TUI)

| Key | Action | |-----|--------| | q | Quit (cancels test) | | p | Pause/Resume test traffic | | s | Settings modal | | t | Cycle color theme | | d | Toggle per-stream view | | ? / F1 | Help | | j | Print JSON result | | u | Dismiss update notification |

Keybindings (Server TUI)

| Key | Action | |-----|--------| | q | Quit server | | ? / F1 | Help | | Esc | Close help |

Themes

xfr includes 11 built-in color themes. Select with --theme or press t during a test:

xfr <host> --theme dracula     # Dark purple theme
xfr <host> --theme matrix      # Green on black hacker style
xfr <host> --theme catppuccin  # Soothing pastels
xfr <host> --theme nord        # Arctic blue tones

Available themes: default, kawaii, cyber, dracula, monochrome, matrix, nord, gruvbox, catppuccin, tokyo_night, solarized

Your theme preference is auto-saved to ~/.config/xfr/prefs.toml.

Configuration

xfr reads defaults from:

  • Linux/macOS: ~/.config/xfr/config.toml
  • Windows: %APPDATA%\xfr\config.toml
[client] duration_secs = 10 parallel_streams = 1 tcp_nodelay = false windowsize = "1M"           # TCP/UDP socket buffer size (SOSNDBUF/SO_RCVBUF), e.g. "512K", "2M" 

bitrate = "100M" # target bitrate; accepts the same units as --bitrate

congestion = "bbr" # TCP congestion-control algorithm

dscp = "EF" # DSCP name or raw TOS byte (0-255)

interval_secs = 1.0 # report interval; default is 1.0 seconds json_output = false no_tui = false theme = "default" # or dracula, catppuccin, nord, matrix, etc. timestamp_format = "relative" # or "iso8601", "unix" address_family = "dual" # "ipv4", "ipv6", or "dual" omit_secs = 0 # omit first N seconds (TCP ramp-up)

bind = "192.168.1.100" # local address, optionally with :port

cport = 5202 # client source port for firewall traversal

psk = "my-secret-key" log_file = "~/.config/xfr/xfr.log" log_level = "info"

[server] port = 5201 one_off = false no_mdns = false address_family = "dual" # "ipv4", "ipv6", or "dual" psk = "my-secret-key" rate_limit = 5 ratelimitwindow = 60 allow = ["192.168.0.0/16", "10.0.0.0/8"] deny = [] acl_file = "/path/to/acl.txt" push_gateway = "http://pushgateway:9091" log_file = "~/.config/xfr/xfr-server.log" log_level = "info"

The value-taking transport and output settings under [client] are defaults: an explicit CLI value takes precedence. This includes omitsecs and intervalsecs, so --omit 0 and --interval 1.0 override the config file. bitrate accepts the same 100M/1G syntax as --bitrate; dscp accepts the same DSCP names and numeric values as --dscp.

Environment variables override config file:

export XFR_PORT=9000
export XFR_DURATION=30s

Prometheus Metrics

Enable with --features prometheus:

xfr serve --prometheus 9090

Metrics available at http://localhost:9090/metrics:

  • xfrbytestotal - Total bytes transferred
  • xfrthroughputmbps - Current throughput
  • xfractivetests - Number of active tests
  • xfrretransmitstotal - TCP retransmissions
See examples/grafana-dashboard.json for a sample Grafana dashboard.

CLI Reference

| Flag | Short | Default | Description | |------|-------|---------|-------------| | --port | -p | 5201 | Server/client port | | --time | -t | 10s | Test duration (use 0 for infinite) | | --udp | -u | false | UDP mode | | --quic | -Q | false | QUIC mode (encrypted, multiplexed streams) | | --bitrate | -b | unlimited | Target bitrate for TCP and UDP (e.g., 1G, 100M). 0 = unlimited. Global across all streams | | --parallel | -P | 1 | Parallel streams | | --reverse | -R | false | Reverse direction (download) | | --bidir | | false | Bidirectional test | | --ipv4 | -4 | false | Force IPv4 only | | --ipv6 | -6 | false | Force IPv6 only | | --bind | | none | Local address to bind (e.g., 192.168.1.100) | | --cport | | none | Client source port for firewall traversal (UDP/QUIC/TCP data streams) | | --dscp | | none | DSCP/TOS marking for TCP/UDP QoS testing (0-255 or name: EF, AF11, CS1, etc.) | | --mptcp | | false | MPTCP mode (client-only, Linux 5.6+; server auto-enables) | | --random | | true | Use random payload data for client-sent TCP/UDP traffic (default) | | --zeros | | false | Use zero-filled payload data (client-sent traffic only) | | --zerocopy | -Z | true (TCP) | Zero-copy TCP sends via sendfile(2), like iperf3 -Z (Linux; lowers sender CPU overhead). On by default; explicit -Z warns when zero-copy can't take effect | | --no-zerocopy | | false | Disable zero-copy TCP sends (use regular buffered writes) | | --probe-mtu | | false | Probe the path MTU per direction instead of running a throughput test (UDP + DF bit; needs server โ‰ฅ this version) | | --connect-timeout | | none | Fail if the control connection takes longer than this (e.g. 5s); without it, dead servers are bounded only by OS defaults | | --json | | false | JSON output | | --json-stream | | false | JSON per interval | | --csv | | false | CSV output | | --quiet | -q | false | Summary only | | --interval | -i | 1.0 | Report interval (seconds) | | --omit | | 0 | Omit first N seconds | | --output | -o | stdout | Output file | | --no-tui | | false | Disable TUI | | --no-update-check | | false | Disable the background update check (also honors DONOTTRACK / XFRNOUPDATE_CHECK) | | --theme | | default | Color theme (dracula, nord, matrix, etc.) | | --tcp-nodelay | | false | Disable Nagle algorithm | | --window | -w | OS default | Socket buffer size for TCP and UDP (SOSNDBUF/SORCVBUF on both ends); when unset, TCP autotunes and UDP uses the kernel default | | --congestion | | OS default | TCP congestion control algorithm (e.g. cubic, bbr, reno) | | --timestamp-format | | relative | Timestamp format (relative, iso8601, unix) | | --log-file | | none | Log file path (e.g., ~/.config/xfr/xfr.log) | | --log-level | | info | Log level (error, warn, info, debug, trace) | | --push-gateway | | none | Prometheus Push Gateway URL (server) | | --prometheus | | none | Prometheus metrics port (server, requires feature) | | --psk | | none | Pre-shared key for authentication | | --psk-file | | none | Read PSK from file | | --rate-limit | | none | Max concurrent tests per IP (server) | | --rate-limit-window | | 60s | Rate limit time window (server) | | --completions | | none | Generate shell completions (bash, zsh, fish, powershell, elvish) | | --allow | | none | Allow IP/subnet, repeatable (server) | | --deny | | none | Deny IP/subnet, repeatable (server) | | --acl-file | | none | ACL rules file (server) | | --max-duration | | none | Maximum test duration, server-side limit (server) | | --tui | | false | Enable live dashboard (server) | | --one-off | | false | Exit after one test (server, works with TCP and QUIC) | | --no-mdns | | false | Disable mDNS service registration (server) |

TCP and UDP tests use random payloads by default to avoid inflated results on WAN-optimized or compressing paths. --random and --zeros control client-sent traffic. Server-sent TCP/UDP traffic also defaults to random, but payload mode is not negotiated over the wire.

--dscp applies to TCP and UDP sockets on both ends when relevant, including server-sent download and bidirectional traffic. QUIC ignores it because the underlying socket is managed by Quinn, and non-Unix platforms currently warn instead of applying socket marking.

Security Considerations

Transport Encryption

| Mode | Encryption | Certificate Verification | |------|------------|-------------------------| | TCP | None | N/A | | UDP | None | N/A | | QUIC | TLS 1.3 | Disabled by default |

QUIC mode (-Q/--quic) provides TLS 1.3 encryption but does not verify server certificates, making it vulnerable to MITM attacks without additional authentication. Always use --psk with QUIC on untrusted networks. Alternatively, use a VPN or SSH tunnel.

Authentication

PSK authentication (--psk) verifies client identity but does not encrypt TCP/UDP traffic. For encrypted + authenticated connections, use QUIC with PSK:

# Server
xfr serve --psk "secretkey"

Client (encrypted + authenticated)

xfr <host> -Q --psk "secretkey"
Security note: --psk and the XFR_PSK environment variable expose the
key through process metadata such as ps, shell history, and
/proc/<pid>/environ. For production deployments, store the key in a file
readable only by the owner and use --psk-file instead.

Network Considerations

  • Single-port TCP: TCP uses single-port mode by default -- control and data connections share port 5201. Data connections are validated against the control connection's IP address, preventing unauthorized access.
  • UDP on untrusted networks: UDP mode may be susceptible to reflection attacks from spoofed source addresses. Use TCP or QUIC on public networks.
  • Rate limiting: Use --rate-limit on public servers to prevent abuse.
  • ACLs: Use --allow/--deny to restrict client access.

DoS Protections

  • Slow-loris resistance: New connections must send their first message within 5 seconds, preventing slow-loris attacks from blocking the accept loop.
  • DataHello flood protection: DataHello messages for unknown test IDs are rejected immediately without allocating resources.
  • Bounded reads: All control messages are limited to 8KB, preventing memory exhaustion from oversized messages.
  • Capability negotiation: Client and server exchange capabilities during the Hello handshake (protocol version 1.1), enabling safe feature evolution.
  • Concurrent connection limits: Server limits concurrent handlers (default 100) to prevent connection floods.

Server Resource Usage

Each TCP stream allocates a 128 KB application read/write buffer. Kernel socket buffers are managed by TCP autotuning unless the client passes -w/--window, in which case the requested size is applied via SOSNDBUF/SORCVBUF on both ends. Memory usage scales with concurrent clients:

| Streams per client | App buffer per client | 10 clients (app buffers) | |-------------------|-----------------------|--------------------------| | 1 (-P 1) | 128 KB | 1.3 MB | | 8 (-P 8) | 1 MB | 10 MB | | 128 (-P 128) | 16 MB | 160 MB |

On top of that, the kernel holds autotuned socket buffers (typically a few hundred KB per stream, capped by net.ipv4.tcprmem[2]/tcpwmem[2]). When a client passes -w N, add roughly N bytes per stream on each side. The server limits concurrent handlers (default 100) to prevent resource exhaustion. Use --rate-limit to restrict tests per IP.

Platform Support

| Platform | Status | |----------|--------| | Linux x86_64/ARM64 | Full support, pre-built binaries | | macOS Apple Silicon | Full support, pre-built binaries | | macOS Intel | Full support, build from crate: cargo install xfr | | Android (Termux) | Full support, pre-built binaries | | NetBSD | Full support, via pkgsrc: pkgin install xfr | | Windows | Experimental (WSL2 recommended). Native builds work but lack TCP_INFO metrics. |

Troubleshooting

Permission denied on port 5201

Use a port above 1024 or run with elevated privileges:

xfr serve -p 9000

Connection refused

Ensure the server is running and the port is not blocked by a firewall. TCP, UDP, and QUIC all run entirely on port 5201 (or your custom port) when both ends are current -- no server-side ephemeral data ports are needed (UDP single-port requires both ends โ‰ฅ v0.9.18; older pairings fall back to ephemeral server ports automatically). For strict egress policies or ECMP testing, use --cport to pin client source ports.

Low throughput

  • Try multiple parallel streams: -P 4
  • Disable Nagle's algorithm: --tcp-nodelay
  • Increase TCP socket buffer: --window 4M
  • On CPU-bound senders (embedded routers, SBCs), the per-write userspace copy is skipped by default via sendfile(2) (TCP, Linux). This applies to client sends, and to server sends in -R/--bidir when the server supports it; pass -Z to get a warning when zero-copy can't take effect, or --no-zerocopy to disable it

UDP packet loss

  • Reduce bitrate: -b 500M
  • Check for network congestion or firewall issues
  • If the receiver may be CPU-bound or its kernel UDP buffer is small, increase --window (e.g. -w 16M) โ€” -w applies to UDP SOSNDBUF/SORCVBUF and propagates to the server, helping high-rate flows avoid kernel tail-drops that hide as live 0.0% loss

Documentation

See Also

  • Terminal Trove - xfr listing and discovery
  • AUR - Arch Linux package (community-maintained)
  • pkgsrc - NetBSD package (community-maintained)

Acknowledgments

Special thanks to Matthieu Baerts (matttbe), Linux kernel MPTCP co-maintainer, for extensive testing, detailed bug reports with packet traces, and feature suggestions including MPTCP support, kernel TCP pacing, zero-copy IO, and high stream-count hardening. xfr is significantly better because of his contributions.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท lance0/xfr ยท Updated daily from GitHub