SpeedTestClient
SpeedTestClient is a synthetic chain-head benchmark tunnel. It creates normal Waterwall lines, sends framed speed-test
traffic into the next tunnel, validates frames returned by SpeedTestServer, and prints interval and final throughput
reports.
It is for measuring a Waterwall chain or transport path. It does not open sockets by itself, it is not an application proxy, and it is not a packet-line tunnel.
Typical placement
SpeedTestClient must be the first node in its chain. It generates test lines and sends them upstream to the configured
transport stack.
SpeedTestClient -> TcpConnector
SpeedTestClient -> UdpConnector
SpeedTestClient -> TlsClient -> TcpConnector
SpeedTestClient -> EncryptionClient -> TcpConnector
The remote side should end with a matching SpeedTestServer:
TcpListener -> SpeedTestServer
UdpListener -> SpeedTestServer
TcpListener -> TlsServer -> SpeedTestServer
TcpListener -> EncryptionServer -> SpeedTestServer
TCP example
Client side:
[
{
"name": "speedtest-client",
"type": "SpeedTestClient",
"settings": {
"mode": "tcp",
"direction": "bidirectional",
"duration-ms": 10000,
"warmup-ms": 1000,
"report-interval-ms": 1000,
"connection-count": 4,
"payload-size": 131072,
"terminate-on-complete": true
},
"next": "speedtest-out"
},
{
"name": "speedtest-out",
"type": "TcpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9000,
"nodelay": true
}
}
]
Server side:
[
{
"name": "speedtest-listener",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 9000,
"nodelay": true
},
"next": "speedtest-server"
},
{
"name": "speedtest-server",
"type": "SpeedTestServer",
"settings": {
"report-interval-ms": 1000,
"json-summary": true
}
}
]
UDP example
Client side:
[
{
"name": "speedtest-client",
"type": "SpeedTestClient",
"settings": {
"mode": "udp",
"direction": "upload",
"duration-ms": 10000,
"warmup-ms": 1000,
"payload-size": 3800,
"udp-target-bits-per-sec": 10000000,
"terminate-on-complete": true
},
"next": "speedtest-udp-out"
},
{
"name": "speedtest-udp-out",
"type": "UdpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9001
}
}
]
Server side:
[
{
"name": "speedtest-udp-listener",
"type": "UdpListener",
"settings": {
"address": "0.0.0.0",
"port": 9001
},
"next": "speedtest-server"
},
{
"name": "speedtest-server",
"type": "SpeedTestServer"
}
]
Required fields
SpeedTestClient requires a next node. The next node is the path that receives generated test traffic.
settings is optional in practice; if it is absent or empty, the client uses defaults. Add settings when you need a
specific mode, duration, direction, payload size, stream count, pacing rate, or output behavior.
Optional settings
| Field | Default | Description |
|---|---|---|
mode | tcp | Framing mode. Accepted values: tcp, TCP, udp, UDP. |
direction | upload | Test direction. Accepted values: upload, UPLOAD, send, download, DOWNLOAD, receive, bidirectional, both, BOTH. |
duration-ms | 10000 | Measured test duration after warmup. Must be positive. |
warmup-ms | 0 | Warmup duration. Warmup frames are sent and validated but excluded from measured byte totals. Must be non-negative. |
report-interval-ms | 1000 | Interval report cadence. Must be positive. |
start-delay-ms | 50 | Delay before each generated stream is started after tunnel startup. Must be non-negative. |
timeout-ms | warmup-ms + duration-ms + 30000 | Per-stream watchdog. Must be greater than warmup-ms + duration-ms. |
connection-count | 1 | Number of parallel normal Waterwall lines to create. Must be positive. |
payload-size | TCP: 131072, UDP: 3800 | Data bytes inside each DATA frame, excluding the 48 byte speed-test header. |
target-bits-per-sec | TCP: 0 | Optional pacing rate in bits per second. 0 means send as fast as backpressure allows. |
udp-target-bits-per-sec | UDP: 10000000 | UDP-specific pacing rate in bits per second. |
target-megabits-per-sec | unset | Alternative pacing field in megabits per second. |
json-summary | false | Print a compact final JSON-style summary in addition to normal logs. |
terminate-on-complete | true | Exit Waterwall after all streams complete. Exit code is 0 on success and 1 if any stream failed. |
Only one of target-bits-per-sec, udp-target-bits-per-sec, and target-megabits-per-sec may be configured at the
same time. The bandwidth values must be non-negative.
payload-size must be between 1 and 16777216. In UDP mode it must not exceed 64952, because each Waterwall UDP
payload carries one complete speed-test frame and the speed-test header is 48 bytes.
Protocol flow
Each generated stream uses these frame types:
| Frame | Direction | Purpose |
|---|---|---|
HELLO | Client to server | Announces mode, direction, duration, warmup, report interval, payload size, pacing rate, stream count, and stream id. |
ACK | Server to client | Confirms the HELLO. In UDP mode the client waits for this before sending data. |
DATA | Sender to receiver | Carries deterministic payload bytes for upload or download. |
END | Sender to receiver | Marks the end of a sender direction. |
REPORT | Server to client | Carries final sender/receiver statistics. |
ERROR | Server to client | Reports a protocol failure. |
The frame header is 48 bytes. payload-size describes only the DATA payload, not the header.
Startup and line ownership
On tunnel start, the client schedules one startup task per connection-count stream. For each stream it:
- chooses a worker using
stream_id % workers_count - creates a normal Waterwall
line_t - initializes
SpeedTestClientline state - sends upstream
Initto the next tunnel - arms a per-stream watchdog using
timeout-ms
Because the client creates these lines, it owns their destruction. When a stream completes or fails, it destroys its own
line after propagating upstream Finish when appropriate.
TCP behavior
In TCP mode, received bytes are accumulated in a stream buffer and decoded as speed-test frames. TCP mode has no default pacing rate, so it sends as fast as the chain and normal socket/backpressure behavior allow unless you configure a target bandwidth.
The client begins sending after downstream Est from the next tunnel. It also respects downstream Pause and Resume
callbacks by pausing and resuming its send task.
UDP behavior
In UDP mode, each Waterwall payload buffer is one complete speed-test frame. The client retransmits HELLO until it sees
an ACK, using the smaller of report-interval-ms and 250 ms as the retry interval. Final upload END frames are sent
three times to reduce the chance that one lost datagram prevents completion.
UDP mode defaults to udp-target-bits-per-sec: 10000000. It reports loss, duplicates, out-of-order packets, validation
errors, and jitter instead of hiding datagram behavior.
Statistics
The receiver side tracks:
- bytes
- packets
- valid packets
- lost packets
- duplicate packets
- out-of-order packets
- validation errors
- jitter estimate
Payloads use a deterministic pattern keyed by stream id, sequence number, and direction, so both sides can detect corruption or unexpected frame ordering.
At the end, the client prints per-stream remote reports from the server and an aggregate final summary. With
json-summary: true, it also prints one compact JSON-style aggregate line.
Node metadata
| Property | Value |
|---|---|
| Node flag | kNodeFlagChainHead |
| Previous node | Not allowed |
| Next node | Required |
| Layer group | kNodeLayer4 |
required_padding_left | 0 |
Common mistakes
- Do not put
SpeedTestClientin the middle of a normal user traffic chain; it is a chain head and generates its own lines. - Do not use it without a matching
SpeedTestServer. - Do not expect it to open sockets directly; use
TcpConnector,UdpConnector, or another transport stack after it. - Do not use very large UDP payloads unless the whole path can carry them without fragmentation or loss.