TcpOverUdpClient
TcpOverUdpClient carries a TCP-like byte stream over a packet-preserving UDP path by running KCP internally. It accepts
stream payload from the previous node, wraps that byte stream into KCP segments, and forwards the resulting datagrams to
the next node.
It is normally paired with TcpOverUdpServer on the remote side.
Typical placement
Client side:
TcpListener -> TcpOverUdpClient -> UdpConnector
TcpListener -> MuxClient -> TcpOverUdpClient -> UdpConnector
Server side:
UdpListener -> TcpOverUdpServer -> TcpConnector
UdpListener -> TcpOverUdpServer -> MuxServer -> TcpConnector
The node after TcpOverUdpClient must preserve datagram boundaries. A normal UDP-style path is the intended transport.
Basic example
{
"name": "tcp-over-udp-client",
"type": "TcpOverUdpClient",
"settings": {
"fec": false,
"kcp-send-window": 8192,
"kcp-recv-window": 8192,
"ping-interval-ms": 10000,
"no-recv-timeout-ms": 30000
},
"next": "udp-out"
}
Matching connector:
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"address": "198.51.100.10",
"port": 9000
}
}
FEC example
{
"name": "tcp-over-udp-client",
"type": "TcpOverUdpClient",
"settings": {
"fec": true,
"fec-data-shards": 10,
"fec-parity-shards": 3
},
"next": "udp-out"
}
FEC must be enabled on both TcpOverUdpClient and TcpOverUdpServer with matching shard settings. If one side uses FEC
and the other side does not, or if the shard counts differ, the peer will not decode the packet stream correctly.
Required fields
settings is optional. If it is absent or not an object, the node uses defaults.
next is required in normal use. It should point to a packet-preserving transport or packet-preserving tunnel stack.
Optional settings
| Field | Default | Validation | Description |
|---|---|---|---|
fec | false | boolean | Enables Reed-Solomon forward error correction around KCP datagrams. |
fec-data-shards | 10 | positive, with total shards less than or equal to 255 | Number of data shards per FEC block. Used only when fec is enabled. |
fec-parity-shards | 3 | positive, with total shards less than or equal to 255 | Number of parity shards per FEC block. Used only when fec is enabled. |
kcp-nodelay | true | boolean | Enables KCP nodelay mode. |
kcp-interval-ms | 10 | 10..5000 | KCP update interval. |
kcp-resend | 2 | >= 0 | KCP fast-resend threshold. |
kcp-no-congestion-control | true | boolean | Disables KCP congestion control. |
kcp-send-window | 8192 | >= 1 | KCP send window in segments. |
kcp-recv-window | 8192 | >= 1 | KCP receive window in segments. |
kcp-initial-cwnd | half of kcp-send-window | 1..kcp-send-window | Initial KCP congestion window. |
kcp-rx-minrto-ms | 30 | >= 1 | Minimum KCP retransmission timeout. |
kcp-send-buffer-limit | 0 | >= 0 | Backpressure threshold. 0 derives the limit from local send window + remote window + 10, capped at 2147483647. |
ping-interval-ms | 10000 | >= 1 | Idle receive interval before sending an internal ping frame. |
no-recv-timeout-ms | 30000 | greater than ping-interval-ms | Idle receive interval before closing the line. |
KCP transport model
On upstream Init, the client initializes per-line state, creates a KCP session, starts the KCP timer loop, creates FEC
state if enabled, queues an initial internal ping frame, and forwards upstream Init to the next node.
When stream payload arrives from the previous node:
- The payload is split into chunks no larger than the effective KCP write MTU.
- Each chunk is prefixed with a one-byte internal frame flag.
- The framed chunk is sent through KCP.
- KCP output datagrams are forwarded upstream to the next node.
When datagrams come back from the next node:
- FEC decoding is applied if enabled.
- KCP receives the datagram.
- Completed KCP payloads are drained.
- The one-byte internal frame flag is removed.
- Data frames are forwarded downstream to the previous node as reconstructed stream payload.
Internal frame flags
Inside the KCP payload, TcpOverUdpClient uses a one-byte flag:
| Flag | Meaning |
|---|---|
0x00 | Data frame. Bytes after the flag are real stream payload. |
0xF0 | Ping frame. Used as an internal keepalive marker. |
0xFF | Close frame. Requests shutdown of the paired line. |
Empty data frames are discarded.
MTU and padding
Optional integer mtu inherits core misc.mtu at construction (the core fallback
is 1500). The supported range is 78..65535 without FEC, or 86..65535 with
FEC. It is an IPv4-based packet budget in bytes. Invalid explicit or inherited
values reject construction; an explicit supported override can replace an
unsupported core default. Null, wrong types, fractions, zero, negatives and
out-of-range numbers are errors. Each instance keeps its validated MTU unchanged.
With F = 0 without FEC or F = 8 with FEC:
KCP MTU = min(mtu - 20 - 8 - F, 64000)
application chunk = KCP MTU - 24 - 1
The 20-byte IPv4 and 8-byte UDP allowances are fixed. The 64,000-byte decimal cap
covers the complete KCP packet, including its 24-byte header. The one-byte frame
flag occupies KCP payload space; required_padding_left = 1 provides room for it.
The effective KCP MTU governs data and aggregated acknowledgement/control output.
FEC adds at most 8 bytes to that output, including parity packets.
At mtu: 1500, KCP MTU/application chunk are 1472/1447 without FEC and
1464/1439 with FEC. Budgets of 64028 without FEC or 64036 with FEC reach the
cap; larger valid settings, including 65535, retain KCP MTU 64000 and application
chunk 63975. The configured budget itself is not capped to 64000.
This is not path-MTU discovery or negotiation. An ordinary IPv6 header needs 20 more bytes than the fixed IPv4 allowance, so a 1500 budget can permit a 1520-byte IPv6 packet. IPv4 options, IPv6 extension headers and later wrappers need extra operator headroom. No family-dependent adjustment occurs.
FEC behavior
With fec: true, outbound KCP datagrams are wrapped in Reed-Solomon FEC packets. Data shards are emitted immediately.
After each configured data-shard block, parity shards are emitted.
On receive, the decoder:
- accepts FEC-wrapped KCP datagrams
- feeds valid data shards into KCP
- tries to recover missing KCP datagrams from parity shards
- drops invalid FEC packets conservatively
FEC adds bandwidth overhead. With the default 10 data shards and 3 parity shards, a full block carries about 30
percent extra parity traffic, plus FEC headers.
Backpressure
Incoming Pause/Resume are advisory and forwarded, including duplicates. They do not gate KCP updates, retransmissions, pings or ready output.
After admitting the complete input and draining generated packet output, the
client checks queued and unacknowledged KCP segments. Above kcp-send-buffer-limit
it sends its own downstream Pause toward the previous stream side; below the
threshold it releases that local hold with Resume. Equality preserves the current
hold. An ACK-only update can release it without waiting for another output packet.
The hold is recorded before notification, and Finish prevents signals toward the
closed side. No separate line task or Resume queue is needed.
This setting is an advisory pressure threshold, not a hard memory or admission cap. KCP/FEC processing and session timers remain active throughout the line, so the node cannot permanently bypass itself.
Close and timeout behavior
If the previous stream side finishes, TcpOverUdpClient:
- marks downstream as closed so re-entrant callbacks are not reflected back toward the finished side
- sends an internal close frame through KCP
- flushes KCP output
- destroys local line state
- forwards upstream
Finishto the packet side
If a close frame is received from the remote peer, the client destroys local state, finishes the packet side, and then finishes the stream side if the line is still alive.
If no receive activity happens for ping-interval-ms, a ping frame is sent. If receive inactivity exceeds
no-recv-timeout-ms, the line is closed.
Common mistakes
- Do not put a stream-only tunnel after
TcpOverUdpClient; the next side must preserve packet boundaries. - Do not enable FEC on only one side.
- Do not use mismatched FEC shard counts.
- Do not assume KCP removes all loss; it retransmits, and optional FEC can help with moderate loss, but severe or bursty loss still hurts.
- Do not set
no-recv-timeout-msless than or equal toping-interval-ms.
Node Metadata
Source-backed metadata:
This node explicitly disables splice for the entire chain through
kNodeFlagBlocksSplice, even if kNodeFlagSupportsSplice is also set.
| Property | Value |
|---|---|
| node flags | kNodeFlagBlocksSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 1 byte |