Skip to main content

TcpUdpListener

TcpUdpListener is a chain-head adapter wrapper around TcpListener and UdpListener. It creates one internal TCP listener and one internal UDP listener with the same listener settings, so a single WaterWall entry node can accept both TCP and UDP traffic on the same address and port set.

This node does not translate TCP into UDP, and it does not translate UDP into TCP. It only lets both transport adapters feed one shared next node.

Typical Placement​

Use TcpUdpListener when the same service entry should accept both TCP and UDP:

TcpUdpListener -> SpeedTestServer
TcpUdpListener -> SniffRouter -> TcpUdpConnector
TcpUdpListener -> Router -> ...
TcpUdpListener -> [optional middle tunnels] -> Socks5Server -> TcpUdpConnector

The next node must be valid for the traffic you expect. If both TCP and UDP will arrive in production, the next node must understand both normal TCP lines and UDP peer lines, or it must route them by protocol.

TcpUdpListener is the standard built-in entry node for Socks5Server with udp: true: its TCP child accepts the SOCKS5 control connection, while its UDP child supplies the dynamic endpoint provider. A plain TcpListener supports only SOCKS5 TCP/CONNECT and cannot satisfy UDP ASSOCIATE.

Basic Example​

[
{
"name": "mixed-in",
"type": "TcpUdpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"nodelay": true,
"large-send-buffer": true,
"large-recv-buffer": true
},
"next": "mixed-out"
},
{
"name": "mixed-out",
"type": "TcpUdpConnector",
"settings": {
"address": "127.0.0.1",
"port": 8443,
"reuseaddr": true,
"nodelay": true
}
}
]

In this example, inbound TCP on port 443 is forwarded through the internal TcpListener, and inbound UDP on port 443 is forwarded through the internal UdpListener. The shared TcpUdpConnector at the end chooses its own TCP or UDP child connector from the line protocol metadata.

Multiport Example​

{
"name": "mixed-public-entry",
"type": "TcpUdpListener",
"settings": {
"address": "0.0.0.0",
"port": [
443,
8443
],
"nodelay": true,
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"fwmark": 10,
"balance-group": "public-mixed",
"balance-interval": 30000,
"multiport-backend": "socket",
"whitelist": [
"192.0.2.0/24",
"2001:db8::/64"
],
"blacklist": [
"192.0.2.99/32"
]
},
"next": "next-node"
}

The same port list is used for both TCP and UDP. TCP port 443 and UDP port 443 are different socket protocols, so they can be bound at the same time.

Required Fields​

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "TcpUdpListener".
settingsobjectShared listener settings passed to both internal child listeners. Must be a non-empty object.
nextstringThe node that receives both TCP lines and UDP peer lines.

Required settings fields:

FieldTypeDescription
addressstringLocal bind address for both child listeners.
port or port-rangenumber, array, or range arrayPort definition used by both TCP and UDP listeners. Exactly one of these fields is required.

Port Configuration​

TcpUdpListener inherits the listener port syntax from TcpListener and UdpListener.

Single Port​

{
"settings": {
"address": "0.0.0.0",
"port": 443
}
}

Explicit Port List​

{
"settings": {
"address": "0.0.0.0",
"port": [
443,
8443,
2083
]
}
}

This listens only on the listed ports for both TCP and UDP.

Contiguous Port Range​

{
"settings": {
"address": "0.0.0.0",
"port-range": [
40000,
40100
]
}
}

port-range must be a two-item array: [min, max].

Optional Settings​

Because the same settings object is passed to both child listeners, most options are the same options documented on TcpListener and UdpListener.

SettingApplies toDescription
nodelayTCP childEnables TCP_NODELAY on accepted TCP sockets.
large-send-bufferTCP and UDP childrenSets SO_SNDBUF where supported.
large-recv-bufferTCP and UDP childrenSets SO_RCVBUF where supported.
interfaceTCP and UDP childrenRestricts sockets to a local network interface where supported.
fwmarkTCP and UDP childrenApplies a Linux-style socket mark where supported.
balance-groupTCP and UDP childrenGroups compatible same-port listeners for sticky distribution.
balance-intervalTCP and UDP childrenSticky interval for repeated clients or peers in a balance group.
initial-idle-timeout-msTCP childInitial idle timeout before listener-side TCP payload activity.
active-idle-timeout-msTCP childIdle timeout after TCP payload activity has started.
multiport-backendTCP and UDP childrenBackend used for contiguous port ranges: "iptables" or "socket".
whitelistTCP and UDP childrenAllowed client or peer IPs and CIDR ranges.
blacklistTCP and UDP childrenBlocked client or peer IPs and CIDR ranges.

For exact socket behavior, timeout defaults, and multiport implementation details, see the TcpListener and UdpListener pages.

Runtime Flow​

At startup, TcpUdpListener creates two internal child tunnels:

  • TcpListener, named like <node-name>.tcp-listener
  • UdpListener, named like <node-name>.udp-listener

Both children are inserted into the chain. They own the real sockets and line lifecycle:

  • the internal TcpListener accepts TCP sockets and creates TCP lines
  • the internal UdpListener creates UDP peer lines
  • upstream callbacks from either child enter TcpUdpListener
  • TcpUdpListener forwards upstream Init, Payload, Pause, Resume, Est, and Finish to next

On the downstream path, the next node calls back into TcpUdpListener. The wrapper checks the line source protocol:

Source protocol metadataDownstream child
TCPinternal TcpListener
UDPinternal UdpListener
missing or ambiguous TCP/UDP flagsfatal configuration or routing error

This is why nodes after TcpUdpListener should preserve the line source protocol metadata.

Line Ownership​

TcpUdpListener itself has no per-line state. It never creates lines and never calls lineDestroy().

Line ownership stays with the child adapter that created the line:

  • TCP lines are owned by the internal TcpListener
  • UDP peer lines are owned by the internal UdpListener

Dynamic UDP Endpoint Delegation​

For a compatible protocol tunnel later in the same chain, including Socks5Server, TcpUdpListener delegates the typed dynamic UDP endpoint capability to its internal UdpListener. Socks5Server discovers this provider automatically from its finalized preceding path; no provider node name or JSON API setting is required. The child remains the owner of the dynamic socket, its worker-local endpoint registry, and each provider-created normal line; the wrapper only preserves the usual callback chain.

dynamic UDP child endpoint -> UdpListener child -> TcpUdpListener -> next chain

On the return path, the wrapper selects the UDP child from the line source protocol, so downstream replies reach the dedicated endpoint. The wrapper must not separately close, drain, or run the child's endpoint lifecycle. Dynamic allocation uses an ephemeral UDP port from the child, not the wrapper's configured static TCP/UDP port.

The child enforces the endpoint's normalized expected peer IP before line creation, a configured nonzero source port exactly, and a first-valid-packet pin when the configured source port is zero. Mismatches are silent drops and never close or message the associated TCP control line. Its large-*-buffer: false settings likewise leave dynamic child-socket kernel defaults unchanged.

Common Mistakes​

  • Do not use TcpUdpListener as a protocol converter. It accepts both protocols, but it does not convert between them.
  • Do not point it at a next node that only works for TCP if UDP traffic can arrive.
  • Do not replace it with TcpListener in a Socks5Server topology that enables UDP; TcpListener has no dynamic UDP provider.
  • Do not configure different TCP and UDP port sets on this wrapper; both child listeners receive the same settings.
  • Do not remove or corrupt source protocol metadata before callbacks return downstream to this node.

Node Metadata​

Source-backed metadata:

PropertyValue
node flagskNodeFlagChainHead | kNodeFlagSupportsSplice
can_have_prevfalse
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayerNone
layer_group_next_nodekNodeLayer4
required_padding_left0 bytes

UDP splice sending​

UDP receives remain ordinary buffers. On an eligible Linux chain, UDP sends accept ordinary data or a real prefix plus a private-pipe body as one datagram to its explicit destination, including empty datagrams. Supported layouts use pipe-to-socket splice. Highly fragmented pipes, or failure to establish a safe fragment bound before sending, use a complete ordinary fallback before socket assembly. Oversized datagrams are rejected without splitting.

Pressure before assembly retains the existing drop policy, without a UDP retry queue. An incomplete splice or failed commit after priming retires only the affected socket; its associated normal lines close through their owners. Shared-socket peers can therefore close together. No truncated remainder is retried, and unrelated sockets remain active. Ordinary sends retain their existing error policy.

The flag does not bypass platform support, misc.splice, packet exclusion, or support from every node in the final expanded/merged chain. No end-to-end zero-copy or measured performance gain is promised.

Both internal listener nodes retain their capability flags. The TCP branch can use its existing splice receive/send path only when the entire expanded chain is eligible; UDP receives stay ordinary. Protocol selection is unchanged.