Skip to main content

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​

FieldDefaultDescription
report-interval-ms1000Fallback 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-summaryfalseDefault JSON-style per-stream summary setting. A client HELLO can also request JSON summaries per line.
quietfalseSuppresses 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:

  1. initializes per-line state
  2. creates a receive stream buffer for TCP-mode framing
  3. immediately sends downstream Est toward 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:

  • tcp or udp mode
  • upload, download, or bidirectional direction
  • duration and warmup
  • report interval
  • payload size
  • target bandwidth
  • total stream count
  • payload verification policy
  • 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 counted. If the client enables verify-payload, their bodies are also validated against its deterministic pattern.

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 splice_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.

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.

Payload verification and splice​

verify-payload on SpeedTestClient defaults to false and selects the policy for both endpoints through the HELLO flags. Set it to true to generate and check the deterministic payload pattern. With verification disabled, senders generate initialized zero-filled bodies and receivers count their lengths without checking payload bytes. Frame headers, lengths, stream IDs, sequence accounting and final reports are still processed. valid_packets and payload validation_errors stay zero when verification is disabled; JSON summaries include verification_enabled.

Both nodes support splice on eligible TCP chains when misc.splice=true. The receiver caches the 48-byte header, returns immediately for incomplete frames, and discards unchecked DATA bodies from their private pipes through /dev/null on Linux without copying the body into userspace. Unavailable sink/splice support falls back to bounded ordinary reads. Control bodies and verified DATA bodies are materialized when needed. UDP retains per-datagram handling. Generated output is ordinary memory; enabling splice does not make payload generation zero-copy.

Each line bounds retained incomplete-frame storage to 2 * (16 MiB + 48) charged bytes, compacting before refusing excess storage. This is a queue accounting bound, not a limit on total process or kernel memory. Send tasks respect Pause and yield after at most 32 frames; pacing uses scheduled tasks rather than blocking sleeps.

Node Metadata​

Source-backed metadata:

PropertyValue
node flagskNodeFlagChainEnd + kNodeFlagSupportsSplice
can_have_prevtrue
can_have_nextfalse
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayerNone
required_padding_left0 bytes