iperf2 2.2.2 - Android Client
==============================
This is the Android client for iperf2 (a thin Kotlin/Compose wrapper
around the iperf2 CLI, running it as a subprocess). Built from the 2-2-2
branch with --enable-udp-l4s --enable-professional.
Professional Edition
---------------------
Use ./configure --enable-professional and then compile from source to
enable the professional feature set: server access-gating (--permit-key,
--permit-key-timeout, --permit-key-file), a client remotely enabling
server-side latency histograms (--histograms paired with --trip-times),
and extended UDP L4S telemetry (--udp-l4s=80211) on top of the UDP L4S
support (Prague congestion control) available in both editions. Builds
also tag their version string with the "professional" branch label and a
"(compiled by ...)" tag. On the udp-l4s git branch, base UDP L4S support
is enabled by default; on other branches (including master),
--enable-professional is the way to opt in (and implies UDP L4S support
there too).
This is a compile-time option, not a source restriction: the professional
feature set's source lives in this same repository, free to read and free
to build - anyone can run --enable-professional themselves at no cost.
The distinction exists so operators who find real value in these
features have a natural way to fund iperf2's continued development, not
to gate who can inspect or build the code.
Note: this Android build's silently-derived permit-key is specific to
this exact build (its own `iperf -v` string feeds the key derivation -
see doc/PERMIT_KEY_NOTES in the source repo). A server's
--permit-key-file needs the matching key for this build, computed via
android/compute_permit_key.py; a key derived from a different build
(different branch, edition, or build date) will not match.
Release Notes - 2.2.2 (as of July 7th, 2026)
----------------------------------------------
o android: cut a signed release build (app-release.apk) from this branch
(2-2-2) rather than udp-l4s - still built with --enable-udp-l4s
--enable-professional (see app/build.gradle.kts), but the bundled
binary's own `iperf -v` string differs from a udp-l4s-branch build (no
"(udp-l4s)" tag), which changes the app's silently-derived permit-key
(see doc/PERMIT_KEY_NOTES) - a udp-l4s-branch client's key will NOT
match this build. Key values themselves are never committed here;
derive the matching one with android/compute_permit_key.py against
this build specifically before pointing a --permit-key-file at it.
o fix: --permit-key-file now refuses to start the server (rather than WARN
and continue) when it can't be opened or contains no usable keys -
previously a misconfigured/unreadable file left a permit-key-gated
server listening and looking perfectly healthy (process up, port open)
while silently rejecting every single connection
o fix: recvn()'s fatal-read-error paths (e.g. ECONNRESET, a truncated
handshake read) set the process-wide sInterupted flag instead of the
per-thread tInterupted one, so a single client resetting its connection
could silently take down the entire listener - a clean exit, so
Restart=on-failure wouldn't recover it. Same class of cross-connection
bug as the per-thread test-duration timer fix below, just not fully
applied to this path when that fix went in; seen in production taking
down a --permit-key-timeout-gated server hours into what should have
been a week-long run
o feat: android/compute_permit_key.py can auto-detect --version-info from
a connected, authorized adb device (finds the app's own bundled
libiperf.so via `dumpsys package` + `find`, runs it with -v) instead of
requiring a manual adb run-as/pm-path incantation pasted in by hand;
--version-info remains available as a manual override
o fix: --permit-key/--permit-key-timeout/--permit-key-file now require the
professional edition (community was never intended to support this
feature) - a community edition build refuses to start (client or
server) with any of these options set, rather than silently running
with an access-control scheme the operator believes is enforced
o feat: --permit-key-file [=<path>] loads a reloadable (SIGHUP, no
restart) list of valid permit keys, in addition to (or instead of)
the single --permit-key value - lets a key be rotated in (e.g. a new
Android app build deriving a new key, see doc/PERMIT_KEY_NOTES) with
an overlap window instead of a hard cutover that drops already-
connected clients using the still-valid old key. File format is
systemd EnvironmentFile=-compatible, so the same file already used
for a systemd unit's own EnvironmentFile= can be pointed at directly.
Defaults to /etc/iperf2/permit-keys.env when no path is given.
o feat(android): live meters now also show a running min/avg/max per
metric (throughput, rtt/jitter, bytes-in-flight/pps), computed in the
app itself since iperf2's own final report only averages bytes/speed,
not rtt/cwnd/bytes-in-flight; the first sample of a run is excluded
from these stats (still shown live) since its interval still has data
sitting in the send buffer that hasn't crossed the network yet.
o fix: the --reverse-mode bytes/packets-in-flight relay (Server.cpp's
burst_info parsing, Reporter.c's reporter_handle_packet_server_tcp, and
ReportOutputs.c's tcp_output_read_enhanced_reverse{,_csv}) read/wrote
those two tcpstats fields unconditionally, but they only exist in
struct iperf_tcpstats when HAVE_TCP_STATS is set - true on Linux where
this shipped, but not on platforms without TCP_INFO support (e.g.
Windows/mingw), where it failed to compile at all; found via an
overnight cross-compile regression sweep (native Linux, mingw32/64,
OpenWrt ath79-musl/x86_64-musl, Android arm64-v8a/armeabi-v7a/x86_64)
o fix: per-thread test-duration timer (was a single process-wide clock via
setitimer()/ITIMER_REAL plus one global interrupt flag) - on a server
handling overlapping connections, whichever connection's own timer fired
first could truncate a different, unrelated, still-running connection
(seen in production as "FAIL: writen errno = 104" / "shutdown failed"
well short of that connection's own configured duration); now isolated
per-connection via timer_create()+SIGEV_THREAD_ID (Linux/glibc)
o fix: a --permit-key server's listener accept-loop lifetime was tied to
-t (armed relative to when the server PROCESS started), so a persistent
server given -t for connection safety would stop accepting *any new*
connections once that -t elapsed, ignoring a much longer
--permit-key-timeout; -t on a --permit-key server now only bounds each
accepted connection's own duration, with --permit-key-timeout (or -P N)
governing the listener's own lifetime instead. A plain (non-permit-key)
server keeps -t bounding the whole listener lifetime, unchanged, for
compatibility with single-session server invocations
o fix: a client's own -t request in reverse/full-duplex/dual-test modes
could force the server to send for longer than the server's own -t
allowed, with no cap; now clamped to whichever is shorter whenever the
server itself was given its own -t
o feat: server startup with -e/--enhanced now also lists the TCP
congestion control algorithms available on the host (Linux only, via
/proc/sys/net/ipv4/tcp_available_congestion_control), so a client
picking -Z <algo> can check the server's own log to confirm that
algorithm is actually loaded there
(See doc/RELEASE_NOTES in the source repository for the full changelog,
including earlier 2.2.2 entries and prior releases.)