Skip to main content

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

FieldDefaultDescription
modetcpFraming mode. Accepted values: tcp, TCP, udp, UDP.
directionuploadTest direction. Accepted values: upload, UPLOAD, send, download, DOWNLOAD, receive, bidirectional, both, BOTH.
duration-ms10000Measured test duration after warmup. Must be positive.
warmup-ms0Warmup duration. Warmup frames are sent and validated but excluded from measured byte totals. Must be non-negative.
report-interval-ms1000Interval report cadence. Must be positive.
start-delay-ms50Delay before each generated stream is started after tunnel startup. Must be non-negative.
timeout-mswarmup-ms + duration-ms + 30000Per-stream watchdog. Must be greater than warmup-ms + duration-ms.
connection-count1Number of parallel normal Waterwall lines to create. Must be positive.
payload-sizeTCP: 131072, UDP: 3800Data bytes inside each DATA frame, excluding the 48 byte speed-test header.
target-bits-per-secTCP: 0Optional pacing rate in bits per second. 0 means send as fast as backpressure allows.
udp-target-bits-per-secUDP: 10000000UDP-specific pacing rate in bits per second.
target-megabits-per-secunsetAlternative pacing field in megabits per second.
json-summaryfalsePrint a compact final JSON-style summary in addition to normal logs.
terminate-on-completetrueExit 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:

FrameDirectionPurpose
HELLOClient to serverAnnounces mode, direction, duration, warmup, report interval, payload size, pacing rate, stream count, and stream id.
ACKServer to clientConfirms the HELLO. In UDP mode the client waits for this before sending data.
DATASender to receiverCarries deterministic payload bytes for upload or download.
ENDSender to receiverMarks the end of a sender direction.
REPORTServer to clientCarries final sender/receiver statistics.
ERRORServer to clientReports 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:

  1. chooses a worker using stream_id % workers_count
  2. creates a normal Waterwall line_t
  3. initializes SpeedTestClient line state
  4. sends upstream Init to the next tunnel
  5. 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

PropertyValue
Node flagkNodeFlagChainHead
Previous nodeNot allowed
Next nodeRequired
Layer groupkNodeLayer4
required_padding_left0

Common mistakes

  • Do not put SpeedTestClient in 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.