A small, fast, dependency-free iperf-style network quality tester. A single binary runs as either a server or a client and measures:
- Throughput — TCP download / upload, single or parallel streams (Mbps, per-second intervals)
- Latency / RTT — UDP echo (min / avg / max / stddev)
- Jitter — RFC 3550 interarrival jitter
- Packet loss — derived from UDP sequence numbers
It is written in portable C and runs on Linux (gcc) and Windows x64 (MSVC / Visual Studio). There is no TLS and no authentication by design — run it on trusted networks or behind a firewall (see Security).
make # builds ./nqts
sudo make install # installs to /usr/local/bin
sudo make install-service # also installs + reloads the systemd unitRequirements: gcc/cc, make, libpthread, libm (all standard on Debian).
Open a Developer Command Prompt for VS (so cl.exe and the Windows SDK are
on PATH), then either:
build-windows.bator:
nmake /f Makefile.winBoth produce nqts.exe and link ws2_32.lib.
nqts is one binary with two modes: a server that waits for tests, and a
client that connects to a server and runs them. Start the server on the
machine you want to test to, then run the client from the machine you want to
test from.
# On the server machine:
nqts server
# On the client machine (replace with the server's IP/hostname):
nqts client 192.0.2.10 --mode download --time 10nqts server [--bind ADDR] [--port N] [--udp-port N]| Option | Default | Description |
|---|---|---|
--bind ADDR |
all | Restrict to one interface (e.g. --bind 10.0.0.5) |
--port N |
5210 | TCP control + throughput port |
--udp-port N |
=--port |
UDP port for ping / jitter-loss tests |
-h, --help |
Show help |
The server runs in the foreground and prints a line such as
nqts server listening on TCP *:5210, UDP *:5210 (no TLS/auth). It handles many
clients concurrently (one thread per connection). Stop it with Ctrl+C
(graceful shutdown). To run it unattended, see
Running as a service.
Open the firewall so clients can reach both the TCP and UDP port:
# Example with ufw (Debian/Ubuntu); adjust the port if you changed --port
sudo ufw allow 5210/tcp
sudo ufw allow 5210/udpnqts client <host> --mode <download|upload|ping|udp> [options]<host> is the server's IP address or hostname. The client connects to the TCP
--port, runs the selected test, prints per-second progress (for throughput
tests) and a final summary.
| Mode | Measures | Transport |
|---|---|---|
download |
throughput, server → client | TCP |
upload |
throughput, client → server | TCP |
ping |
round-trip latency + probe loss | UDP echo |
udp |
jitter + packet loss (uplink) | UDP |
| Option | Applies to | Default | Description |
|---|---|---|---|
--mode |
all | download | download | upload | ping | udp |
--port N |
all | 5210 | server TCP control/data port |
--time N |
download/upload/udp | 10 | test duration, seconds |
--streams N |
download/upload | 1 | parallel TCP streams (1–64) |
--size N |
all | mode | block / datagram size in bytes |
--rate N |
udp | 1000 | datagrams per second |
--count N |
ping | 10 | number of probes |
--interval N |
ping | 1000 | inter-probe interval, ms |
# --- on the server ---
nqts server --bind 0.0.0.0 --port 5210
# --- on the client (192.0.2.10 is the server) ---
# 10-second download with 4 parallel streams
nqts client 192.0.2.10 --mode download --time 10 --streams 4
# upload test
nqts client 192.0.2.10 --mode upload --time 10
# 20 latency probes, 200ms apart
nqts client 192.0.2.10 --mode ping --count 20 --interval 200
# 5-second UDP jitter/loss test at 5000 pps, 1200-byte datagrams
nqts client 192.0.2.10 --mode udp --time 5 --rate 5000 --size 1200Example client output for a download test:
Connecting 4 stream(s) to 192.0.2.10:5210 for download test (10 s)
[ 1 s] 942.10 Mbps
[ 2 s] 948.73 Mbps
...
------------------------------------------------
Download: 1180.42 MB in 10.00 s = 944.34 Mbps (4 streams)
| Purpose | Default | Override |
|---|---|---|
| TCP control + throughput | 5210 | --port |
| UDP ping + jitter/loss | 5210 | --udp-port |
The client always connects to the TCP --port first; the server then tells it
which UDP port to use for ping/udp tests.
On Linux the server can run unattended via systemd. The provided
systemd/nqts-server.service is a hardened unit (DynamicUser, NoNewPrivileges,
ProtectSystem=strict, restricted syscalls/capabilities), so it runs as a
transient unprivileged user with no manual account setup.
From the repo directory:
sudo make install-serviceThis installs the nqts binary to /usr/local/bin/, copies the unit to
/etc/systemd/system/nqts-server.service, and runs systemctl daemon-reload.
Then enable and start it (now and on every boot):
sudo systemctl enable --now nqts-serversystemctl status nqts-server
journalctl -u nqts-server -f # live logsYou should see a listening on TCP *:5210, UDP *:5210 line. Remember to open
the firewall for both the TCP and UDP port (see Server).
Edit the ExecStart line in /etc/systemd/system/nqts-server.service, e.g.:
ExecStart=/usr/local/bin/nqts server --bind 10.0.0.5 --port 6000 --udp-port 6000Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart nqts-serversudo systemctl stop nqts-server # stop now
sudo systemctl disable nqts-server # don't start on boot
sudo rm /etc/systemd/system/nqts-server.service
sudo systemctl daemon-reloadThe service auto-restarts on failure (Restart=on-failure).
nqts.exe runs as a normal console program in either mode. To stop a running
server, press Ctrl+C (handled gracefully). Running it as a Windows Service is
not included in this version.
There is no encryption and no authentication. A reachable server will run throughput and UDP tests for anyone who can connect, which can consume significant bandwidth. Therefore:
- Bind to a specific interface with
--bind(e.g.--bind 10.0.0.5) instead of all interfaces. - Restrict access with a firewall (open the TCP/UDP port only to trusted hosts).
- Do not expose it directly to the public internet.
The client opens a TCP control connection and sends a fixed binary request
(magic NQTS, version, test type, duration, sizes, stream count). The request
and all control messages are serialized field-by-field in network byte order
(no packed structs), so the wire format is identical across compilers and
architectures.
- TCP throughput streams payload blocks for the requested duration; the
receiver counts bytes. Parallel streams open additional TCP connections and
are multiplexed on the client with
select()(no threads). Upload byte totals are reported authoritatively by the server. - UDP ping sends timestamped datagrams the server echoes; the client computes RTT from its monotonic clock.
- UDP jitter/loss sends sequenced, timestamped datagrams at a target rate; the server tracks sequence gaps (loss) and RFC 3550 interarrival jitter and returns the summary over the control channel.
All OS-specific code (Winsock2 vs POSIX sockets, monotonic time, threads,
shutdown signals) is isolated in src/platform.{h,c}.
Makefile Linux build
Makefile.win Windows (MSVC nmake) build
build-windows.bat Windows (MSVC) one-shot build
src/
protocol.h wire format + serialization helpers
platform.{h,c} OS-compatibility layer
common.{h,c} sockets, robust I/O, statistics, formatting
server.c server mode
client.c client mode
nqts.c entry point / mode dispatch
systemd/
nqts-server.service