# Features Reference

Comprehensive documentation of xfr features.

## Transport Modes

### TCP (Default)

Standard TCP bulk transfer with configurable options:

```bash
xfr <host>                     # Basic TCP test
xfr <host> -b 100M             # TCP at 100 Mbps
xfr <host> --tcp-nodelay       # Disable Nagle's algorithm
xfr <host> --window 4M         # Set TCP window size
xfr <host> --congestion bbr    # Use BBR congestion control
xfr <host> --random            # Random payload (defeat compression/dedup bias)
```

TCP provides:
- Reliable, ordered delivery
- Bitrate pacing (`-b`) — on Linux, uses kernel `SO_MAX_PACING_RATE` with the FQ scheduler for precise per-packet timing; falls back to userspace byte-budget sleep pacing on other platforms or MPTCP sockets. Note: `-b` sets a global bitrate shared across all parallel streams (unlike iperf3 where `-b` is per-stream)
- Selectable congestion control (`--congestion cubic`, `--congestion bbr`, `--congestion reno`)
- TCP_INFO statistics (RTT, retransmits, cwnd) — polled live per interval, not just at test end
- High stream-count teardown resilience — client stops local data loops at local test end and scales teardown wait time with stream count. TCP receivers briefly drain after cancel to cover cross-peer cancel latency. On Linux, TCP senders use `SO_LINGER=0` (abortive close) at end-of-test and clamp `bytes_sent` to `tcpi_bytes_acked` so that bufferbloat-discarded tail bytes are not reported as sent; macOS falls back to graceful `shutdown()` since it lacks the `bytes_acked` counter needed to correct for overcount

#### Single-Port TCP (Default)

By default, all TCP connections (control and data) use a single port (5201) via the DataHello protocol. When the client connects data streams, each one sends a `DataHello` message containing the `test_id` and `stream_index` to identify itself to the server. This eliminates the need to open additional ports for data transfer.

For high stream counts on constrained links, single-port setup is hardened: the client limits concurrent `connect + DataHello` handshakes (max 16 in flight) and the server adapts initial first-line read timeout to active stream count (capped at 20s). This reduces handshake-loss-driven mid-test stream failures.

If the server detects that a client does not advertise the `single_port_tcp` capability, it falls back to multi-port mode (allocating separate ephemeral ports per stream) for legacy compatibility.

### UDP

Unreliable datagram transfer for testing network capacity:

```bash
xfr <host> -u                  # UDP mode
xfr <host> -u -b 1G            # UDP at 1 Gbps
xfr <host> -u -b 0             # UDP unlimited (flood test - use carefully)
xfr <host> -u --random         # Random UDP payload bytes
```

UDP provides:
- Configurable bitrate pacing
- Jitter calculation (RFC 3550)
- Packet loss detection
- Out-of-order detection
- Live receiver-progress feedback at 2 Hz on the data socket (when both peers advertise `udp_feedback_v1`), so the live loss counter stays current under upload-mode saturation even when the TCP control channel back-pressures. Surfaces in the TUI live counter and in `--no-tui --json-stream` / `--csv` / plain interval output via a producer-side cumulative-monotonic filter that admits only the freshest reading from either source

#### Single-Port UDP (Default, v0.9.18+)

When both peers advertise `single_port_udp_v1`, all per-stream UDP data flows to the server's main port (5201/UDP by default) instead of per-stream ephemeral server ports — the UDP firewall story matches single-port TCP. Per stream, the client sends a token-bearing hello datagram to the main port; the server creates a connected same-port socket (`SO_REUSEPORT`) for that stream and acks from it, after which the kernel routes the stream's traffic to its dedicated socket. The server advertises the capability only after a startup self-test proves the running kernel's connected-socket routing; on unsupported platforms (e.g. Windows) or against older peers, it falls back to ephemeral per-stream ports automatically.

**Note**: UDP is not congestion-controlled. High bitrates can cause network congestion.

### Payload Pattern

Both client and server use random payloads by default for TCP and UDP, avoiding inflated results from WAN optimizers, compression offload, and dedup hardware. Buffers are filled with random bytes once at allocation time (not per write), so the CPU cost is negligible.

- `--zeros` forces zero-filled client-sent payloads for compression/dedup testing.
- `--zeros` only affects client-sent traffic; the server always sends random. Payload mode is not negotiated over the wire (future enhancement).
- QUIC payloads are ignored since QUIC encrypts all traffic via TLS 1.3.

### Zero-Copy Sends (`--zerocopy`, Linux)

TCP sends can skip the per-write userspace-to-kernel copy with `-Z`/`--zerocopy` (issue #33), which serves the payload from an anonymous in-memory file via `sendfile(2)` — the same mechanism as iperf3's `-Z`. On constrained CPUs (embedded routers, SBCs) the copy itself is often the bottleneck, so this raises the throughput ceiling and lowers CPU load at any given rate.

- Client-sent traffic uses zero-copy directly; for `-R`/`--bidir`, the request is forwarded in `TestStart` and servers that advertise the `zerocopy_v1` capability apply it to their sends (older servers ignore it, and the client warns).
- Works with MPTCP (`sendfile` goes through the regular splice path, unlike `MSG_ZEROCOPY`).
- Falls back to regular writes — with a warning, never a failed test — on non-Linux platforms, kernels without `memfd_create`, or sockets that reject `sendfile`.
- Payload semantics are unchanged: the in-memory file is filled once with the same random (or `--zeros`) pattern the regular path uses.

### QUIC

Encrypted transport over UDP using TLS 1.3:

```bash
xfr <host> --quic              # QUIC transport (short: -Q)
xfr <host> --quic -P 4         # QUIC with 4 multiplexed streams
xfr <host> --quic --psk secret # QUIC with PSK authentication
```

QUIC provides:
- Built-in TLS 1.3 encryption
- Multiplexed streams over single connection
- Connection migration capability
- Head-of-line blocking avoidance

**Security Note**: QUIC encrypts traffic but uses self-signed certificates by default. For authenticated connections, combine with `--psk` to prevent MITM attacks.

### MPTCP (Multi-Path TCP)

Multi-Path TCP enables a single connection to use multiple network paths simultaneously (e.g., WiFi + Ethernet, or multiple WAN links). Requires Linux 5.6+ with `CONFIG_MPTCP=y`.

```bash
# Server — MPTCP is automatic, no flag needed
xfr serve

# Client
xfr <host> --mptcp                    # MPTCP single stream
xfr <host> --mptcp -P 4               # MPTCP with 4 parallel streams
xfr <host> --mptcp --bidir            # MPTCP bidirectional
xfr <host> --mptcp --congestion bbr   # MPTCP with BBR congestion control
```

MPTCP is transparent at the application layer — all TCP features work unchanged:
- Single-port mode, multi-stream, bidirectional
- TCP_INFO stats (RTT, retransmits, cwnd)
- Congestion control, window size, nodelay
- PSK authentication

**Server auto-MPTCP**: The server always tries to create MPTCP listeners. MPTCP listeners accept both MPTCP and regular TCP clients transparently — the kernel handles the fallback. If the kernel doesn't support MPTCP, the server silently falls back to regular TCP. No `--mptcp` flag is needed on the server side.

**Client `--mptcp`**: The client must explicitly opt in with `--mptcp` to request MPTCP connections. Both sides need MPTCP-capable sockets for actual multi-path behavior; otherwise the kernel falls back to regular TCP.

**Platform**: Linux only. Non-Linux platforms receive a clear error message on the client. The server silently falls back to TCP on non-Linux. The kernel must have MPTCP enabled (`CONFIG_MPTCP=y`, default on most modern distros).

## Protocol

### Version and Compatibility

The control protocol version is 1.1. Client and server exchange version information during the Hello handshake. Compatibility is checked by comparing the major version number numerically -- major versions must match exactly, while minor version differences are allowed (backwards compatible).

### Capability Negotiation

Both client and server advertise capabilities in their Hello messages. Current capabilities include: `tcp`, `udp`, `quic`, `multistream`, `single_port_tcp`, `pause_resume`, `udp_feedback_v1`, `zerocopy_v1`, `mtu_probe_v1`, and `single_port_udp_v1`. The server inspects the client's capabilities to determine which features to use (e.g., single-port vs. multi-port mode); a feature activates only when both peers advertise it. Older peers that don't list a capability fall back to the prior behavior — adding a capability is wire-additive. The server may withhold a capability it can't honor at runtime (e.g. `single_port_udp_v1` when the startup self-test fails).

The `udp_feedback_v1` capability (added in v0.9.14) lets the server send 36-byte cumulative-counts packets back to the client at 2 Hz on the same UDP data socket, providing live UDP loss visibility without depending on the TCP control channel that may be competing for ACKs against a saturated UDP uplink.

### Message Flow

The control channel uses newline-delimited JSON over TCP or QUIC:

```
Client                          Server
  |                               |
  |-------- Hello --------------->|  (control connection)
  |<------- Hello (+ auth?) -----|
  |-------- AuthResponse? ------>|
  |<------- AuthSuccess? --------|
  |-------- TestStart ---------->|
  |<------- TestAck -------------|
  |                               |
  |-------- DataHello ---------->|  (data connection 1, same port)
  |-------- DataHello ---------->|  (data connection 2, same port)
  |      [Data Transfer]          |
  |                               |
  |<------- Interval ------------|  (periodic, on control)
  |<------- Result --------------|
```

## Multi-Stream Testing

Test with parallel streams to utilize multiple CPU cores or aggregated links:

```bash
xfr <host> -P 4                # 4 parallel streams
xfr <host> -P 8                # 8 parallel streams
```

Each stream reports individual statistics. Aggregate throughput is also shown.

Use cases:
- Bonded/LACP interface testing
- Multi-core CPU utilization
- Identifying per-flow rate limits

## Bidirectional Mode

Test upload and download simultaneously:

```bash
xfr <host> --bidir             # Bidirectional
xfr <host> --bidir -P 2        # Bidir with 2 streams each direction
```

Plain text summary shows per-direction bytes and throughput plus the
combined total:

```
  Transfer:    Send: 5.00 GB    Recv: 7.00 GB    (Total: 12.00 GB)
  Throughput:  Send: 40.0 Gbps  Recv: 56.0 Gbps  (Total: 96.0 Gbps)
```

JSON adds `bytes_sent`, `bytes_received`, `throughput_send_mbps`, and
`throughput_recv_mbps` fields; CSV gets four matching columns; the TUI
shows `↑` / `↓` throughput in the stats panel. Unidirectional tests
continue to report only the combined number (which equals the
single-direction throughput).

## Direction Control

```bash
xfr <host>                     # Upload (client → server)
xfr <host> -R                  # Reverse/download (server → client)
xfr <host> --bidir             # Both directions
```

## Infinite Duration

Run tests indefinitely until manually stopped:

```bash
xfr <host> -t 0                # Run until Ctrl+C or 'q'
xfr <host> -u -t 0 -b 100M     # Continuous UDP at 100 Mbps
```

The TUI shows elapsed time as `Xs/∞`. Press `q` to stop.

### Early Exit Summary

Ctrl+C during any test (infinite or finite) triggers a graceful cancel and displays the full test summary with whatever stats have accumulated — rather than silently killing the process. Works in both plain text and TUI modes. A second Ctrl+C force-exits immediately (exit code 130). If the server is unreachable, a partial summary is printed from the client's cumulative counters instead of saving a synthetic result to `--output`.

Servers can cap infinite requests with `--max-duration`:

```bash
xfr serve --max-duration 300s  # Cap all tests at 5 minutes
```

## Local Bind Address

Bind the client to a specific local IP (useful for multi-homed hosts):

```bash
xfr <host> --bind 192.168.1.100         # Bind to specific IPv4
xfr <host> --bind 10.0.0.1:0            # IP with auto-assigned port
xfr <host> --bind [::1]                 # IPv6 address
```

**Note:** Plain `--bind IP:PORT` is still not supported for TCP control sockets. Use `--bind IP` for source-IP selection, or combine `--bind IP --cport PORT` to pin TCP data-stream source ports.

## Client Source Port (`--cport`)

Pin the client's local source port for firewall traversal:

```bash
xfr <host> --cport 5300                 # TCP data stream with source port 5300
xfr <host> --cport 5300 -P 4            # 4 TCP data streams on ports 5300-5303
xfr <host> -u --cport 5300              # UDP with source port 5300
xfr <host> -u --cport 5300 -P 4         # 4 UDP streams on ports 5300-5303
xfr <host> --quic --cport 5300          # QUIC with source port 5300
xfr <host> --bind 10.0.0.1 --cport 5300 # Combine with --bind for IP + port
```

Multi-stream TCP and UDP assign sequential ports starting from the specified port. QUIC multiplexes all streams on a single port, so only one port is needed regardless of `-P`.

For TCP, `--cport` only applies to data streams. The control connection still uses an ephemeral source port, and xfr fails fast if that ephemeral port overlaps the requested TCP data-port range.

## DSCP / TOS Marking (`--dscp`)

Mark client TCP or UDP sockets for QoS / DSCP testing:

```bash
xfr <host> --dscp EF                    # Expedited Forwarding (46 << 2)
xfr <host> -u --dscp AF31               # Assured Forwarding class 3 drop precedence 1
xfr <host> --dscp 184                   # Raw TOS byte value
```

`--dscp` accepts either a raw TOS byte (`0-255`) or DSCP names such as `EF`, `AF11-AF43`, `CS0-CS7`, and `VA`.

- TCP and UDP apply the marking on client sockets after connect.
- QUIC ignores `--dscp` because the underlying UDP socket is owned by Quinn.
- On non-Unix platforms, xfr currently warns instead of applying socket marking.

## Dual-Stack and IPv6 Support

The server defaults to dual-stack mode, accepting both IPv4 and IPv6 connections on a single socket. You can restrict to a specific address family:

```bash
xfr serve -4                   # IPv4 only
xfr serve -6                   # IPv6 only
xfr <host> -4                  # Client: force IPv4
xfr <host> -6                  # Client: force IPv6
```

IPv4-mapped IPv6 addresses (`::ffff:x.x.x.x`) are automatically normalized to their IPv4 form when comparing peer IPs. This ensures consistent behavior in dual-stack environments, such as when validating that a DataHello connection comes from the same client as the control connection.

## Output Formats

### Plain Text (Default)

Human-readable output with optional TUI:

```bash
xfr <host>                     # TUI with live graphs
xfr <host> --no-tui            # Plain text output
xfr <host> -q                  # Quiet mode (summary only)
```

### JSON

Machine-readable JSON output:

```bash
xfr <host> --json              # JSON summary at end
xfr <host> --json-stream       # JSON per interval (one object per line)
xfr <host> -o result.json      # Save to file
```

### CSV

Spreadsheet-compatible output:

```bash
xfr <host> --csv               # CSV output
xfr <host> --csv -o data.csv   # Save to file
```

### Timestamps

Control timestamp format in output:

```bash
xfr <host> --timestamp-format relative   # "1.0s", "2.0s" (default)
xfr <host> --timestamp-format iso8601    # "2024-01-15T10:30:00Z"
xfr <host> --timestamp-format unix       # "1705316400"
```

## Configuration

### Config File

xfr reads defaults from:
- **Linux/macOS:** `~/.config/xfr/config.toml`
- **Windows:** `%APPDATA%\xfr\config.toml`

```toml
[client]
duration_secs = 10
parallel_streams = 1
tcp_nodelay = false
window_size = "1M"
json_output = false
no_tui = false
theme = "default"
timestamp_format = "relative"  # relative, iso8601, unix
address_family = "dual"        # ipv4, ipv6, dual
omit_secs = 0                  # omit first N seconds (TCP ramp-up)
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"
psk = "my-secret-key"
rate_limit = 5
rate_limit_window = 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"
```

`[client] omit_secs` sets the default for `--omit`. An explicit CLI `--omit` value, including `--omit 0`, overrides the config file.

### Environment Variables

Environment variables override config file settings:

| Variable | Description |
|----------|-------------|
| `XFR_PORT` | Server/client port |
| `XFR_DURATION` | Test duration (e.g., "30s") |
| `XFR_LOG_LEVEL` | Log level (error, warn, info, debug, trace) |
| `XFR_LOG_FILE` | Log file path |

### User Preferences

Theme preference is saved to `~/.config/xfr/prefs.toml`:

```toml
theme = "dracula"
```

Press `t` during a test to cycle themes. Your choice is auto-saved.

## Server Features

### Multi-Client Support

Unlike iperf3, xfr's server handles multiple simultaneous clients:

```bash
xfr serve                    # Accept unlimited clients
xfr serve --one-off          # Exit after one test (TCP or QUIC)
```

The `--one-off` flag causes the server to shut down after a single test completes. This works for both TCP and QUIC connections. When one-off mode is active, the shutdown signal is shared between the TCP accept loop and the QUIC acceptor task, so whichever protocol completes a test first triggers the server exit.

### Server TUI Dashboard

Monitor active tests in real-time:

```bash
xfr serve --tui              # Live dashboard
```

Shows:
- Active test count
- Per-client throughput
- Connection duration
- Protocol and direction

### Connection Security

The server implements several protections against connection-based attacks:

- **Slow-loris protection**: Each accepted TCP connection is immediately spawned into its own task. A 5-second initial read timeout (`INITIAL_READ_TIMEOUT`) is applied to the first line read, preventing idle connections from blocking the accept loop.
- **DataHello flood protection**: When a data connection sends a `DataHello`, the server validates the `test_id` against active tests before processing. Unknown test IDs are rejected immediately. The server also verifies that the DataHello originates from the same IP address as the control connection for that test (using IPv4-mapped IPv6 normalization for dual-stack compatibility).
- **Bounded message length**: Control messages are limited to 8192 bytes to prevent memory exhaustion.
- **Handshake timeouts**: A 30-second timeout is applied to control-plane handshake reads after the initial connection.
- **Concurrent handler limit**: A configurable semaphore (default: 100) limits the number of concurrent client handlers.

### Rate Limiting

Limit concurrent tests per IP:

```bash
xfr serve --rate-limit 2     # Max 2 concurrent tests per IP
```

### Access Control Lists

Restrict which IPs can connect:

```bash
xfr serve --allow 192.168.0.0/16           # Only allow local network
xfr serve --allow 10.0.0.0/8 --deny 0.0.0.0/0   # Allow 10.x, deny all else
xfr serve --acl-file /etc/xfr/acl.txt      # Load ACL from file
```

ACL file format:
```
allow 192.168.0.0/16
allow 10.0.0.0/8
deny 0.0.0.0/0
```

Rules are evaluated in order. First match wins.

### PSK Authentication

Require pre-shared key for connections:

```bash
# Server
xfr serve --psk mysecretkey
xfr serve --psk-file /etc/xfr/psk.txt

# Client
xfr <host> --psk mysecretkey
```

Authentication uses HMAC-SHA256 challenge-response.

### Duration Limits

Limit maximum test duration server-side:

```bash
xfr serve --max-duration 60s   # Cap tests at 60 seconds
```

## Prometheus Metrics

Build with `--features prometheus` for metrics support.

### Metrics Endpoint

```bash
xfr serve --prometheus 9090
```

Available at `http://localhost:9090/metrics`:

```
xfr_bytes_total{direction="upload"} 1234567890
xfr_throughput_mbps{direction="upload"} 987.65
xfr_active_tests 2
xfr_retransmits_total 42
```

### Push Gateway

Push metrics on test completion:

```bash
xfr serve --push-gateway http://pushgateway:9091
```

## LAN Discovery

Find xfr servers on the local network using mDNS:

```bash
xfr discover                 # Scan for servers
xfr discover --timeout 10s   # Extended search
```

Servers automatically register with mDNS when started.

## Result Comparison

Compare test results to detect regressions:

```bash
xfr diff baseline.json current.json
xfr diff baseline.json current.json --threshold 5%
```

Exit code:
- 0: Within threshold
- 1: Regression detected

## Logging

Enable file logging for debugging:

```bash
xfr serve --log-file /var/log/xfr.log --log-level debug
xfr <host> --log-file ./test.log --log-level trace
```

Log levels: `error`, `warn`, `info`, `debug`, `trace`

Logs rotate daily when using a log file.

## Update Notifications

xfr checks GitHub for newer versions in the background. If an update is available, a yellow banner appears below the title bar showing:

- Current version → new version
- Appropriate update command (based on how you installed xfr)

Press `u` to dismiss the banner.

The check uses a 1-hour cache to avoid GitHub API rate limits.

## CLI Reference

See `xfr --help` for complete CLI documentation.

| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--port` | `-p` | 5201 | Server/client port |
| `--time` | `-t` | 10s | Test duration (use 0 for infinite) |
| `--udp` | `-u` | false | UDP mode |
| `--bitrate` | `-b` | unlimited | Target bitrate for TCP and UDP (e.g., 1G, 100M). 0 = unlimited. Global across all streams (unlike iperf3 per-stream) |
| `--parallel` | `-P` | 1 | Parallel streams |
| `--reverse` | `-R` | false | Reverse direction (download) |
| `--bidir` | | false | Bidirectional test |
| `--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 |
| `--theme` | | default | Color theme |
| `--tcp-nodelay` | | false | Disable Nagle algorithm |
| `--window` | `-w` | OS default | TCP window size; when unset, kernel autotunes |
| `--congestion` | | OS default | TCP congestion control algorithm (e.g. cubic, bbr, reno) |
| `--quic` | `-Q` | false | Use QUIC transport |
| `--psk` | | none | Pre-shared key |
| `--timestamp-format` | | relative | Timestamp format |
| `--log-file` | | none | Log file path |
| `--log-level` | | info | Log level |
| `--ipv4` | `-4` | false | Force IPv4 only |
| `--ipv6` | `-6` | false | Force IPv6 only |
| `--bind` | | none | Local address to bind (IP or IP:port) |
| `--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 (Multi-Path TCP, Linux 5.6+) |
| `--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` | false | Zero-copy TCP sends via sendfile(2) (Linux; lowers sender CPU overhead) |

### Server-Specific Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--tui` | false | Enable server dashboard |
| `--one-off` | false | Exit after one test (TCP or QUIC) |
| `--max-duration` | none | Maximum test duration |
| `--rate-limit` | none | Max concurrent tests per IP |
| `--allow` | none | Allow IP/subnet (repeatable) |
| `--deny` | none | Deny IP/subnet (repeatable) |
| `--acl-file` | none | Load ACL from file |
| `--psk-file` | none | Read PSK from file |
| `--push-gateway` | none | Prometheus Push Gateway URL |
| `--prometheus` | none | Metrics endpoint port |
