Iperf(at)Work is a browser interface for network performance tests with iperf3. This document describes the architecture, the components, the API, the security mechanisms, the packaging and installation model and operation. It is written for operations, security review and further development.
1. Purpose and scope
Product |
Iperf(at)Work 2.1.0 |
Components |
Caddy 2.11.4 (web server and reverse proxy), |
Use |
Measuring bandwidth, retransmissions, jitter and packet loss between two machines; troubleshooting and acceptance tests of networks, VPN, WAN and virtual switches |
Not intended for |
Continuous monitoring, measurement from many agents, long-term storage of measurement series |
Target platforms |
Rocky Linux 9 / RHEL 9 / AlmaLinux 9 and VMware Photon OS 4/5, on bare metal, VMware VM/appliance and Proxmox VE VM (x86_64) |
Run model |
Ordinary Linux processes managed by systemd, installed from three RPM packages without repositories or internet |
Languages |
English and Norwegian (bokmål) in the web UI, API messages, manuals, license pages and scripts |
2. Architecture
2.1. Overview
Browser │ HTTP :80 (optional HTTP Basic password) ▼ ┌────────────────────────── caddy.service (user caddy) ────────────────────────────┐ │ /api/* ──► reverse_proxy 127.0.0.1:9000 (flush_interval -1 for SSE) │ │ /* ──► file_server /usr/share/iperf-by-web/html │ │ index.html, ui.css, i18n.js, app.js, manual(.no).html, licenses(.no) │ │ security headers (CSP, X-Frame-Options, nosniff, Referrer-Policy) │ └────────────────────────────────────────────────────────────────────────────────────┘ │ ▼ 127.0.0.1:9000 ┌────────────────────────── iperf-api.service (user iperf-by-web) ──────────────────┐ │ ThreadingHTTPServer ─► /api/start ─► subprocess iperf3 -c … (new process group) │ │ watcher thread ─► tests/test-<id>.log + .meta.json │ │ /api/output ─► Server-Sent Events (follows the log file) │ │ /api/server/status ─► systemctl is-active, ss, server log │ └────────────────────────────────────────────────────────────────────────────────────┘ iperf3-server@5201.service, iperf3-server@5202.service (user iperf-by-web, server/both) iperf3 --server --port N --forceflush --timestamps --logfile /var/lib/iperf-by-web/server-N.log
2.2. Components
| Component | Language | Responsibility |
|---|---|---|
Caddy 2.11.4 ( |
Go |
Listens on port 80, serves the static pages, proxies |
|
Python 3 |
JSON API: validates test parameters, starts and stops iperf3 without a shell, stores log and metadata per test, parses the summary, streams the output with Server-Sent Events, reads the server status |
iperf3 3.21 ( |
C |
The measurement itself, as client (started by the API) and as server ( |
Web UI ( |
HTML/CSS/JS |
Form, live graph (canvas), result tiles, summary, history with CSV export, server status, EN/NO |
|
bash |
Applies the configuration (services, firewall), status, role change, language, password, self test |
|
bash |
Offline installation, requirement check, uninstallation – in English and Norwegian |
2.3. Process and thread model
The API uses ThreadingHTTPServer with one thread per request. Each test is started as a separate process in a new process group (start_new_session=True), with stdout and stderr to the log file. A watcher thread waits for the process, writes the final state and the parsed result to the metadata file and releases the "running test". Only one client test can run at a time (lock + process check). SSE requests hold one thread each while the test runs and send a keep-alive comment about every 10 seconds.
On stop, SIGINT is sent to the process group so that iperf3 prints its summary; after 3 seconds SIGKILL follows. When the API starts, tests still marked running (interrupted by a restart) are marked failed with a note.
3. Measurement
3.1. The command
The API builds an argument list (never a shell string):
iperf3 -c <server> -p <port> -t <duration> -i <interval> -P <streams> --forceflush -f m
[-O <omit>] [-u -b <bandwidth|1G>] [-b <bandwidth>] [-w <window>] [-l <length>]
[-R | --bidir] [-B <source address>] [-4 | -6]
--forceflush makes every interval line appear at once (live view), and -f m gives every rate in Mbit/s.
3.2. Validation
| Field | Rule | iperf3 |
|---|---|---|
server |
|
|
port |
1–65535 |
|
duration |
1– |
|
parallel |
1–128 |
|
interval |
1–60 |
|
omit |
0–60 and less than the duration |
|
protocol |
|
|
direction |
|
|
bandwidth |
|
|
window, length |
|
|
bind |
|
|
ipversion |
|
|
3.3. Parsing the result
The summary lines (ending in sender or receiver) are parsed with a regular expression that handles [ 5], [SUM], [ 5][TX-C] and [SUM][RX-C]. When [SUM] lines exist (several streams), only they are used. For each line the direction (up = TX-C, down = RX-C, otherwise none), the measuring end, the rate converted to Mbit/s, the amount transferred and either retransmissions (TCP, sender) or jitter, lost/total datagrams and loss in percent (UDP) are stored. The message iperf3: error - … is stored for failed tests.
The web UI parses the interval lines in the same way as they arrive, for the graph and the tiles; lines marked (omitted) are skipped.
3.4. Storage
File |
Content |
|
Command and complete iperf3 output |
|
Parameters, state ( |
|
Server log with time stamps (logrotate: weekly, 8 weeks, compressed) |
The ID is the start time YYYYMMDD-HHMMSS (with -n on collision). Metadata is written atomically (temporary file + os.replace). When the number passes KEEP_TESTS, the oldest are deleted.
4. Web interface
4.1. Structure
The page follows the style of SYSLOG-Tools(at)Work and SNMP-Trap-Tools(at)Work: dark green top bar with icon, name, status badge and host name; ? User Manual and EN | NO on the right; tabs; Bootstrap-like form controls; footer with Licenses · Third-party notices · User Manual. Everything is written for the project – no external scripts, style sheets, fonts or images – so the pages work without internet.
Tab |
Content |
Run test |
Network setup, test parameters, advanced options, start/stop, command line, four tiles (last interval, average, maximum, progress), graph (two lines with |
History |
Filterable table of the newest 50 tests, expandable rows with command and summary, log download, CSV export (UTF-8 with BOM) |
Server |
State per port (systemd + listening socket), number of connections and peers ( |
The tabs shown depend on the role (client: Run test and History, server: Server, both: all). The chosen tab is remembered in the address (#tab=). If the browser is closed during a test, the page reconnects to the running test on the next visit.
4.2. Language (i18n)
English is the source language. i18n.js holds the English → Norwegian dictionary and an engine that at start-up records every text node, placeholder, title and aria-label found in the dictionary, and switches them when the user presses EN or NO. Texts built by script use t('English text', {var}). The choice is stored in localStorage and a cookie (iperf_by_web_lang). Without a stored choice, UI_LANG from the configuration is used (through /api/status).
The API chooses the language of error messages from the header X-UI-Lang (sent by the page), otherwise UI_LANG. The manuals and license pages are separate files per language (manual.html / manual.no.html, licenses.html / licenses.no.html); the ? User Manual button opens the right language at the chapter of the tab (#s3, #s4, #s5). iperf3 output is not translated.
4.3. HTTP API
Method |
Path |
Description |
GET |
|
Mode, host name, IP, versions (API, iperf3, Caddy), running test, defaults, |
POST |
|
Starts a test (JSON, see validation). 200 |
POST |
|
Stops the running test (SIGINT, then SIGKILL) |
GET |
|
SSE: |
GET |
|
The last 50 tests (without PID) |
GET |
|
The log as a |
GET |
|
Ports, systemd state, listening, connections, peers, log excerpt (80 lines) |
5. Security
5.1. Threat model
Iperf(at)Work is an administration tool for a closed network. The main risk is that someone without permission uses the web UI to send large amounts of traffic (fill links) towards arbitrary targets. The measures below limit this; the web UI should still only be reachable from a trusted management network.
5.2. Measures in the web layer
Measure |
Description |
Access |
Optional HTTP Basic password (bcrypt, cost 14) for the whole site including the API ( |
CSRF |
Every POST request must carry |
Injection |
iperf3 is started with an argument list (no shell); every field is validated with allow-lists (see 3.2). Test IDs are validated ( |
XSS |
All text from the server is inserted with |
Headers |
|
Resources |
One test at a time, duration at most |
API binding |
The API listens only on 127.0.0.1:9000 and is reached only through Caddy. Caddy’s admin API listens only on localhost:2019. |
5.3. Process isolation (systemd)
Service |
Hardening |
|
User |
|
User |
|
User |
SELinux: the services run as unconfined_service_t; the installation sets the labels with restorecon.
5.4. Known limitations
-
The web UI uses plain HTTP. The password (if used) is sent base64 encoded. Use a trusted network and
ADMIN_NET. HTTPS can be set up in the Caddyfile with your own certificates (tls /path/cert.pem /path/key.pem). -
iperf3 is built without OpenSSL, so iperf3’s own authentication (
--username,--rsa-public-key-path) is not available. The iperf3 server accepts tests from everyone who can reach the ports; restrict with the firewall. -
One iperf3 server can only run one test at a time per port.
-
With
-Bover loopback aliases, UDP tests fail in iperf3 (affects only test environments, not real network cards). -
No user roles: everyone who gets in can start and stop tests.
6. Packaging and installation
6.1. The RPM packages
Package |
Architecture |
Content and dependencies |
|
x86_64 |
|
|
x86_64 |
|
|
noarch |
API, web UI, manuals, license pages, Caddyfile, configuration, systemd units, |
The dependencies are written so that they resolve on both Rocky/RHEL 9 and Photon OS (boolean dependencies, rpm ≥ 4.13). Configuration files are %config(noreplace).
6.2. Installed layout
/etc/caddy/Caddyfile %config(noreplace)
/etc/caddy/iperf-by-web.d/ 0750 root:caddy (auth.caddy when a password is set)
/etc/iperf-by-web/iperf-by-web.conf %config(noreplace)
/etc/logrotate.d/iperf-by-web
/usr/bin/caddy, /usr/bin/iperf3
/usr/libexec/iperf-by-web/iperf-api
/usr/sbin/iperf-by-web-ctl
/usr/share/iperf-by-web/html/ web UI, manual(.no).html, licenses(.no).html
/usr/share/iperf-by-web/uninstall.sh, lib/common.sh
/usr/share/doc/iperf-by-web/ documents (no + en/ + img/)
/usr/share/licenses/{iperf-by-web,caddy,iperf3}/
/usr/lib/systemd/system/{caddy,iperf-api,iperf3-server@}.service
/var/lib/iperf-by-web/ 0750 iperf-by-web (tests/, server-<port>.log)
/var/lib/caddy, /var/log/caddy 0750 caddy
6.3. The installer
install.sh runs seven steps: requirements (architecture, platform, systemd, rpm, Python ≥ 3.7, ss, useradd, runuser, checksums, free ports), packages (dnf --disablerepo='*' install or rpm -Uvh; packages at the right version are skipped), configuration (new keys are added to an older file), password, SELinux, iperf-by-web-ctl apply and verification (services, self test, reachability of the default server). All messages exist in English and Norwegian (--lang).
iperf-by-web-ctl apply validates the Caddyfile as the user caddy, stops every iperf3-server@ instance and starts only those the role needs, restarts/reloads the API and Caddy and updates firewalld (removes old iperf ports 5200–5299, opens the web port – as a rich rule for ADMIN_NET if set – and the server ports for tcp and udp).
7. Configuration
/etc/iperf-by-web/iperf-by-web.conf is read by the API (systemd EnvironmentFile) and by iperf-by-web-ctl.
Key |
Default |
Meaning |
|
|
|
|
|
Ports for |
|
|
Pre-filled in the UI |
|
|
Default language of the UI, API messages and |
|
empty |
Shown IP address (empty: the address used towards |
|
|
Longest test |
|
|
Tests in the history |
|
|
API address (also in the Caddyfile) |
|
|
Web port (firewall; also in the Caddyfile) |
|
|
firewalld managed by |
|
empty |
The web port is opened only for this network |
|
|
Paths |
8. Operations
Task |
Command |
Status |
|
Logs |
|
Role change |
|
Password |
|
Self test |
|
Restart |
|
Uninstall |
|
Disk use: one test typically takes 2–20 KB (log + metadata); 200 tests stay below 5 MB. The server logs are rotated weekly.
9. Build and quality assurance
9.1. Build
Tool |
Does |
|
Builds iperf3 from |
|
Reads the module list in caddy ( |
|
Builds |
|
Builds |
|
Starts the whole stack locally, runs real tests and takes screenshots in both languages (Playwright) |
|
Builds the three RPMs (reproducible time stamps) |
|
All of the above + self test + |
9.2. Self test
python3 tools/selftest.py runs static checks (Python and bash syntax, ShellCheck, version numbers, translation coverage, no external resources, documents), unit tests of the summary parsing, integration tests against a real API and a real iperf3 server (TCP, UDP, --bidir, SSE, stop, failed test, validation, CSRF, keep-alive, language, history) and package tests (names, versions, dependencies, files). See Revisjonsrapport.md (Norwegian review report) for the results.