A lightweight, RFC 5357 §5 TWAMP-Light server and client written in Go. TWAMP-Light omits the TCP-based TWAMP-Control negotiation phase; both endpoints speak TWAMP-Test UDP packets directly. This makes it simpler to deploy and interoperable with other TWAMP-Light implementations, but it is not compatible with full TWAMP implementations (Cisco, Juniper, IXIA) that require the Control protocol handshake.
- TWAMP-Light profile (RFC 5357 §5) — correct on-wire binary packet format
- NTP 64-bit timestamps per RFC 4656 §4.1.2
- Four-timestamp RTT calculation removes reflector processing delay
- Error Estimate field with S-bit (NTP-sync flag) per RFC 4656 §3.7.1
- Sequence number tracking and out-of-order response handling
- Optional packet padding to negotiate test packet sizes
- Per-source-IP token bucket rate limiter (
-rate-limit) prevents amplification abuse - CIDR allowlist (
-allowed) restricts reflector to known senders
- Dual explicit sockets —
udp4+udp6avoid platform-dependent dual-stack behaviour sync.Poolbuffer reuse — zero per-packet allocations on the server- 1 MB socket buffers for high packet rates
- Semaphore-limited goroutine pool (max 100 concurrent)
- Min / avg / max RTT
- Standard deviation
- Mean absolute jitter (inter-packet delay variation)
- Packet loss percentage
- Graceful shutdown (SIGINT / SIGTERM)
- Daemon mode with full flag forwarding
- Configurable port, timeout, and padding
- Log to file or stdout
RTT = (T4 − T1) − (T3 − T2)
T1 = Client send timestamp
T2 = Reflector receive timestamp
T3 = Reflector send timestamp
T4 = Client receive timestamp
Subtracting (T3 − T2) removes variable reflector processing time, giving
accurate one-way-trip × 2.
Test Request (Client → Reflector):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp (NTP) |
| 64 bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Error Estimate | Padding (opt) ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Minimum: 14 bytes + N padding bytes
Test Response (Reflector → Client):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sender Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sender Timestamp (NTP) |
| 64 bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sender Error Estimate | MBZ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Receive Timestamp (NTP, T2) |
| 64 bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reflector Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Send Timestamp (NTP, T3) |
| 64 bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reflector Error Estimate | MBZ2 | Sender TTL |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Total: 40 bytes
- Go 1.21 or higher
- UDP port 862 access (or use
-portfor a high port without root) - NTP-synchronized clocks for accurate absolute measurements
Automatically detects your OS and architecture, downloads the latest release, verifies the checksum, and installs to /usr/local/bin:
/bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/buraglio/tiny-twamp/main/install.sh)"Supports Linux, macOS, FreeBSD, OpenBSD, NetBSD, DragonFly BSD, and Solaris across amd64, arm64, arm, 386, mips, ppc64le, riscv64, and s390x.
Download the latest release for your platform from the releases page, extract, and place the binary in your $PATH:
# Example: Linux amd64
curl -fsSL https://github.com/buraglio/tiny-twamp/releases/latest/download/tinytwamp_linux_amd64.tar.gz \
| tar -xz
sudo install -m 755 tinytwamp /usr/local/bin/git clone https://github.com/buraglio/tiny-twamp.git
cd tiny-twamp
go build -o tinytwamp .
sudo install -m 755 tinytwamp /usr/local/bin/| Flag | Default | Description |
|---|---|---|
-mode |
client |
client or server |
-server |
localhost |
Server address (client mode) |
-port |
862 |
UDP port (both modes) |
-count |
10 |
Packets to send (client mode) |
-interval |
1s |
Interval between packets (client mode) |
-timeout |
5s |
Per-packet receive timeout (client mode) |
-padding |
0 |
Zero-padding bytes appended to test packets |
-no-sync |
false |
Assert clock is NOT NTP-synchronized (clears S-bit) |
-daemon |
false |
Run server as background daemon |
-logfile |
"" |
Log file path (stdout if empty) |
-rate-limit |
0 |
Max packets/sec per source IP, server (0 = unlimited) |
-allowed |
"" |
Comma-separated CIDR allowlist, server (empty = all) |
# Foreground, port 862 (requires root or CAP_NET_BIND_SERVICE)
sudo ./tinytwamp -mode server
# High port, no root required
./tinytwamp -mode server -port 8620
# With rate limiting and allowlist
sudo ./tinytwamp -mode server \
-rate-limit 100 \
-allowed "10.0.0.0/8,2001:db8::/32" \
-logfile /var/log/twamp.log
# Daemon mode (all flags forwarded)
sudo ./tinytwamp -mode server -daemon -logfile /var/log/twamp.log# Basic test to IPv6 address
./tinytwamp -mode client -server 2001:db8::1
# IPv4 test, 100 packets, 100 ms interval
./tinytwamp -mode client -server 192.168.1.1 -count 100 -interval 100ms
# High port (matching server)
./tinytwamp -mode client -server 192.168.1.1 -port 8620
# With padding (match remote reflector's negotiated size)
./tinytwamp -mode client -server 2001:db8::1 -padding 20
# Clock not synced — assert S=0 in error estimate
./tinytwamp -mode client -server 192.168.1.1 -no-syncExample output:
[TWAMP-Light-Client] 2025/06/01 12:00:00.000000 Starting TWAMP-Light test to 2001:db8::1 (count=10 interval=1s timeout=5s padding=0)
[TWAMP-Light-Client] 2025/06/01 12:00:00.012345 seq=1 RTT=0.823ms
[TWAMP-Light-Client] 2025/06/01 12:00:01.012678 seq=2 RTT=0.791ms
...
[TWAMP-Light-Client] 2025/06/01 12:00:09.015432 === TWAMP-Light Test Statistics ===
[TWAMP-Light-Client] 2025/06/01 12:00:09.015433 Packets sent: 10
[TWAMP-Light-Client] 2025/06/01 12:00:09.015434 Packets received: 10
[TWAMP-Light-Client] 2025/06/01 12:00:09.015435 Packet loss: 0.0%
[TWAMP-Light-Client] 2025/06/01 12:00:09.015436 RTT min/avg/max: 0.778 / 0.812 / 0.857 ms
[TWAMP-Light-Client] 2025/06/01 12:00:09.015437 Std deviation: 0.024 ms
[TWAMP-Light-Client] 2025/06/01 12:00:09.015438 Mean jitter: 0.018 ms
# Every 5 minutes, 5 samples
*/5 * * * * /usr/local/bin/tinytwamp -mode client -server 2001:db8::1 \
-count 5 -logfile /var/log/twamp-monitor.loggo test -v ./...Unit tests cover NTP conversion, error estimates, packet marshal/unmarshal, RTT calculation, rate limiter, allowlist parsing, and statistics math.
Agent mode runs on each probe host simultaneously as a TWAMP-Light reflector and active prober, pushing results directly to InfluxDB for visualization in Grafana.
1. Deploy a config file (served by any HTTP server):
cp deploy/config-example.json /etc/twamp/config.json
# Edit hosts[], influxdb{}, and topology as needed2. Install the agent on each probe host:
sudo cp tinytwamp /usr/local/bin/
sudo cp deploy/tinytwamp-agent.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now tinytwamp-agent3. Import the Grafana dashboard: In Grafana → Dashboards → Import,
upload deploy/grafana-dashboard.json and select your InfluxDB datasource.
Full mesh — every host probes every other host:
{ "topology": "mesh", "hub_spoke": { "enabled": false } }Hub-and-spoke — spokes probe only the hub; hub probes all spokes:
{ "topology": "hub-spoke", "hub_spoke": { "enabled": true, "hub": "probe-a" } }| Flag | Default | Description |
|---|---|---|
-mode agent |
— | Enable agent mode |
-config-url |
— | HTTP URL of topology JSON (required) |
-config-refresh |
from config | Override config re-fetch interval |
-hostname |
auto-detected | Override hostname used for topology lookup |
-port |
862 |
UDP port for TWAMP-Light (both reflector and prober) |
# Build
docker build -f deploy/Dockerfile -t tinytwamp .
# Run — MUST use --network host for accurate RTT on Linux
docker run --network host tinytwamp \
-mode agent \
-config-url http://config-server/twamp-config.jsonWarning: Docker bridge networking introduces NAT that distorts RTT measurements. Use
--network hoston Linux, or run bare-metal/VM for production.
Exporter mode simultaneously reflects TWAMP-Light packets and exposes RTT/jitter/loss metrics via a Prometheus /metrics endpoint (default :9862).
sudo ./tinytwamp -mode exporter \
-config-url http://config-server/twamp-config.json \
-probe-mode background \
-metrics-addr :9862-probe-mode |
Description |
|---|---|
background (default) |
Background scheduler probes continuously; scrapes return cached results instantly |
scrape |
Each Prometheus scrape triggers a fresh burst before responding |
dual |
Background probes push to both InfluxDB and Prometheus simultaneously |
| Flag | Default | Description |
|---|---|---|
-mode exporter |
— | Enable exporter mode |
-probe-mode |
background |
background, scrape, or dual |
-metrics-addr |
:9862 |
Prometheus metrics listen address |
-metrics-tls-cert |
"" |
TLS certificate file (enables HTTPS; requires -metrics-tls-key) |
-metrics-tls-key |
"" |
TLS private key file |
-config-url |
— | HTTP URL of topology JSON (required) |
sudo ./tinytwamp -mode exporter \
-config-url http://config-server/twamp-config.json \
-metrics-addr :9862 \
-metrics-tls-cert /etc/twamp/server.crt \
-metrics-tls-key /etc/twamp/server.keyPrometheus scrape config:
scrape_configs:
- job_name: twamp
scheme: https
tls_config:
insecure_skip_verify: true # or provide ca_file
static_configs:
- targets: [probe-a:9862, probe-b:9862]
scrape_interval: 30s
scrape_timeout: 10s # increase for scrape mode with many targets| Metric | Type | Description |
|---|---|---|
twamp_rtt_min_milliseconds |
Gauge | Minimum RTT (ms) |
twamp_rtt_avg_milliseconds |
Gauge | Average RTT (ms) |
twamp_rtt_max_milliseconds |
Gauge | Maximum RTT (ms) |
twamp_rtt_stddev_milliseconds |
Gauge | RTT standard deviation (ms) |
twamp_jitter_milliseconds |
Gauge | Mean absolute jitter (ms) |
twamp_loss_ratio |
Gauge | Packet loss 0.0–1.0 |
twamp_packets_sent_total |
Counter | Cumulative packets sent |
twamp_packets_received_total |
Counter | Cumulative packets received |
twamp_reflected_packets_total |
Counter | Packets reflected by this host |
Labels on probe metrics: source, target, topology, site.
sudo cp deploy/tinytwamp-exporter.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now tinytwamp-exporter- No TWAMP-Control protocol — TWAMP-Light only; not interoperable with full TWAMP implementations requiring the TCP control session.
- Unauthenticated mode only — authenticated and encrypted modes (RFC 5357 §6) are not implemented.
- TTL not extracted — reflector reports TTL=64 (default); extracting the actual received TTL requires a raw socket.
- No one-way delay — TWAMP-Light measures RTT; one-way delay requires OWAMP (RFC 4656) and tightly synchronized clocks.
- Clock sync: Use NTP/PTP on both endpoints for accurate timestamps.
- Firewall: Open UDP port 862 (or your
-port) bidirectionally. - Amplification: Enable
-rate-limitand-allowedon public-facing servers. - Log rotation: Use
logrotateor systemd'sStandardOutput=file:. - Privileges: Use
setcap cap_net_bind_service+ep ./tinytwampinstead of running as root for port 862.
This software contains components generated by Large Language Model (LLM) or machine intelligence platforms. This generated code may consist of entire code blocks, code reviews, or in some cases, entire applications. The purpose of this document is to provide transparency regarding the use of artificial intelligence in the creation and maintenance of this project. Scope of usage AI tools have been used to assist in various stages of the development lifecycle. Human oversight and verification AI tools were employed to accelerate development and identify potential issues, and a human reviewed AI-generated content. Reliability Code generated by LLMs may occasionally contain subtle logical errors or inefficiencies that were not detected during review.