SpeedTestServer
SpeedTestServer is the chain-end peer for SpeedTestClient. It accepts speed-test frames from upstream, validates
upload data, optionally sends download data back downstream, and returns final sender/receiver reports to the client.
It is a normal connection-oriented tunnel. It does not create lines, it does not own listener-created line destruction, and it is not a packet-line tunnel.
Typical placement
SpeedTestServer must be the last node in its chain.
TcpListener -> SpeedTestServer
UdpListener -> SpeedTestServer
TcpListener -> TlsServer -> SpeedTestServer
TcpListener -> EncryptionServer -> SpeedTestServer
The matching client side normally starts with SpeedTestClient:
SpeedTestClient -> TcpConnector
SpeedTestClient -> UdpConnector
SpeedTestClient -> TlsClient -> TcpConnector
SpeedTestClient -> EncryptionClient -> TcpConnector
TCP example
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,
"quiet": false
}
}
]
Matching client side:
[
{
"name": "speedtest-client",
"type": "SpeedTestClient",
"settings": {
"mode": "tcp",
"direction": "bidirectional",
"duration-ms": 10000,
"warmup-ms": 1000,
"connection-count": 4,
"payload-size": 131072
},
"next": "speedtest-out"
},
{
"name": "speedtest-out",
"type": "TcpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9000,
"nodelay": true
}
}
]
UDP example
Server side:
[
{
"name": "speedtest-udp-listener",
"type": "UdpListener",
"settings": {
"address": "0.0.0.0",
"port": 9001
},
"next": "speedtest-server"
},
{
"name": "speedtest-server",
"type": "SpeedTestServer",
"settings": {
"report-interval-ms": 1000
}
}
]
Matching 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
},
"next": "speedtest-udp-out"
},
{
"name": "speedtest-udp-out",
"type": "UdpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9001
}
}
]
Required fields
SpeedTestServer is a chain end. It must not have a next node.
settings is optional. If it is absent or empty, defaults are used.
Optional settings
| Field | Default | Description |
|---|---|---|
report-interval-ms | 1000 | Fallback interval log cadence before a client HELLO is received. After HELLO, the client's requested interval is used for that line. Must be positive. |
json-summary | false | Default JSON-style per-stream summary setting. A client HELLO can also request JSON summaries per line. |
quiet | false | Suppresses runtime logs such as accepted-stream, interval, final summary, and per-stream failure messages. Fatal setup diagnostics may still appear. |
Init and establishment
On upstream Init, the server:
- initializes per-line state
- creates a receive stream buffer for TCP-mode framing
- immediately sends downstream
Esttoward the previous tunnel
That downstream Est is the signal that lets SpeedTestClient start sending its HELLO.
HELLO and ACK
The first client frame must be HELLO. It configures the server's per-line behavior:
tcporudpmode- upload, download, or bidirectional direction
- duration and warmup
- report interval
- payload size
- target bandwidth
- total stream count
- stream id
After accepting HELLO, the server sends ACK downstream. In UDP mode, duplicate HELLO frames receive another ACK
so a lost ACK does not leave the client waiting forever.
The server validates that duration-ms, report-interval-ms, payload-size, and total stream count are usable. UDP
payloads must fit the UDP maximum of 64952 payload bytes.
Upload receive path
When upload is enabled, upstream DATA frames are validated using the deterministic pattern generated by
SpeedTestClient.
The server tracks:
- bytes
- packets
- valid packets
- lost packets
- duplicate packets
- out-of-order packets
- validation errors
- jitter estimate
When the client sends upload END, the server finalizes receiver statistics and sends a receiver REPORT downstream.
In UDP mode, duplicate final END frames are ignored after the receiver has already finished.
Download send path
When download is enabled, the server schedules downstream DATA frames after HELLO is accepted. It uses the payload
size, warmup, duration, and optional target bandwidth requested by the client.
At the end of the download interval, the server sends:
- download
END - sender
REPORT
In UDP mode, final END and REPORT frames are repeated three times.
Stream and datagram framing
In TCP mode, incoming bytes are accumulated in a buffer_stream_t and decoded as speed-test frames.
In UDP mode, each Waterwall payload buffer must contain exactly one speed-test frame. A first packet carrying a UDP
flagged HELLO switches that line into UDP-frame handling.
Malformed frame headers, unexpected frame types, impossible payload lengths, or invalid protocol order fail the line.
When possible, the server sends an ERROR frame before closing.
Finish behavior
If upstream closes before a complete test, the server destroys only its own per-line state. It does not call
lineDestroy(), because that line was created by the listener or another upstream adapter.
When the server completes or fails a test itself, it sends any required final protocol bytes, destroys its own line
state, and forwards downstream Finish.
Node metadata
| Property | Value |
|---|---|
| Node flag | kNodeFlagChainEnd |
| Previous node | Required |
| Next node | Not allowed |
| Layer group | kNodeLayer4 |
required_padding_left | 0 |
Common mistakes
- Do not place any node after
SpeedTestServer; it is the chain end. - Do not use it without a matching
SpeedTestClient. - Do not expect it to accept arbitrary application traffic.
- Do not treat it as a packet-line node. It works with normal connection lines created by listeners or upstream adapters.