A modern iperf3 alternative with a live TUI, multi-client server, and QUIC support. Built in Rust.
xfr
A modern iperf3 alternative with a live TUI, multi-client server, MPTCP, and QUIC support. Built in Rust.
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
Features
- Live TUI with real-time throughput graphs and per-stream stats
- Server dashboard -
xfr serve --tuifor 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
--cportfor pinning data source ports - Bidirectional testing - measure upload and download simultaneously
- Multiple output formats - plain text, JSON, JSON streaming, CSV
- Result comparison -
xfr diffto 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,XFRDURATIONoverrides
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 transferredxfrthroughputmbps- Current throughputxfractivetests- Number of active testsxfrretransmitstotal- TCP retransmissions
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:--pskand theXFR_PSKenvironment 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-limiton public servers to prevent abuse. - ACLs: Use
--allow/--denyto 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/--bidirwhen the server supports it; pass-Zto get a warning when zero-copy can't take effect, or--no-zerocopyto 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) โ-wapplies to UDPSOSNDBUF/SORCVBUFand propagates to the server, helping high-rate flows avoid kernel tail-drops that hide as live0.0%loss
Documentation
- Comparison with iperf3 - Feature matrix and migration guide
- Scripting & CI/CD - Automation, Docker, Prometheus
- Features Reference - Detailed feature documentation
- Architecture - For contributors
- Changelog - Release history
- Known Issues - Edge cases and limitations
- Roadmap - Planned features
- Contributing - Development guidelines
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.