Skip to main content

Socks5Server

Socks5Server is the server side of SOCKS5 inside a Waterwall chain. It accepts SOCKS5 control traffic from the previous node, performs method negotiation, authenticates through an existing AuthenticationClient node or explicit no-auth, and then maps SOCKS5 requests into Waterwall traffic.

It supports:

  • SOCKS5 method negotiation
  • username/password authentication through AuthenticationClient
  • CONNECT
  • UDP ASSOCIATE
  • UDP relay associations tied to an accepted TCP control connection

It rejects BIND.

Cached Account Admission​

With AuthenticationClient, admission uses its last valid RAM table and clock basis during ordinary network outages and re-authentication. Cached enabled status, expiry, traffic quota and local connection/IP reservations still apply. No initial cache, stopping, terminal accounting-continuity failure or explicit client credential rejection blocks new admission. Only validated authorization clears that rejection; existing authorized traffic keeps cached enforcement and accounting. Offline administrative changes apply after refresh. Local allowances can overshoot aggregate limits across multiple disconnected clients; they are not global reservations.

Typical placement​

TCP-only SOCKS5 server:

TcpListener -> Socks5Server -> TcpConnector

SOCKS5 server with combined CONNECT and UDP support:

TcpUdpListener -> [optional middle tunnels] -> Socks5Server -> TcpUdpConnector

There is one previous callback path, not separate TCP and UDP chains into one Socks5Server. TcpUdpListener supplies the TCP control ingress and its private dynamic UDP provider on that one path. udp: true fails startup when the finalized preceding path has no UdpListener/TcpUdpListener provider.

Required topology for SOCKS5 UDP

Use TcpUdpListener for the normal built-in deployment. TcpListener alone can carry the SOCKS5 TCP control connection and CONNECT, but it cannot provide the dynamic UDP endpoint required by UDP ASSOCIATE. A bare UdpListener exposes the provider capability, but by itself cannot accept the required TCP control connection. It is useful only inside a custom fan-in topology that supplies TCP on the same callback path.

Socks5Server discovers the provider automatically from its finalized preceding path. There is no provider-name or provider-API JSON setting.

Do not manually place UserController after an authenticated Socks5Server. Authenticated mode creates and inserts an internal UserController before the configured next node.

Complete TCP/UDP topology example​

This minimal configuration shows all three required data-path roles: the combined entry listener, the SOCKS5 protocol node, and a connector that follows each requested TCP or UDP destination.

{
"name": "socks5-service",
"nodes": [
{
"name": "socks-entry",
"type": "TcpUdpListener",
"settings": {
"address": "127.0.0.1",
"port": 1080,
"nodelay": true
},
"next": "socks-server"
},
{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"no-auth": true,
"connect": true,
"udp": true,
"ipv4": "127.0.0.1"
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"nodelay": true,
"fastopen": false
}
}
]
}

This no-auth example is intentionally bound to loopback. For remote clients, configure auth-client-node-name with an AuthenticationClient, then replace both listener and advertised addresses with addresses appropriate for the deployment.

The addresses and ports in this topology have different jobs:

Setting or valueMeaning
TcpUdpListener.settings.addressLocal address on which the TCP listener and dynamically created UDP sockets bind.
TcpUdpListener.settings.portPublic SOCKS5 TCP control port. The internal static UDP child also listens here, but Socks5Server does not accept that fixed port as a SOCKS UDP association.
Socks5Server.settings.ipv4IPv4 address advertised to the SOCKS5 client in BND.ADDR; it must be reachable by that client. It is not the socket bind setting.
Reply BND.PORTPer-association UDP relay port chosen dynamically by the operating system and returned by UDP ASSOCIATE.

Authenticated node example​

{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true,
"ipv4": "203.0.113.10",
"sweep-interval-ms": 1000,
"verbose": false
},
"next": "outbound"
}

auth-client must be an existing node of type AuthenticationClient in the same configuration. Socks5Server uses that node for lookups; it does not create the authentication client.

Replace 203.0.113.10 with the IPv4 address advertised to and reachable by your SOCKS5 clients.

No-auth example​

{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"no-auth": true,
"connect": true,
"udp": false
},
"next": "outbound"
}

Exactly one authentication mode must be selected: either auth-client-node-name or no-auth: true.

Required fields​

FieldTypeDescription
settingsobjectRequired and must not be empty.
settings.auth-client-node-namestringRequired unless no-auth is explicitly true. Must point to an existing AuthenticationClient node and must not point back to the same Socks5Server.
settings.no-authbooleanRequired only when no authentication client is configured. Must be true to enable no-auth mode.
settings.ipv4 / settings.udp-ipv4stringRequired when udp is enabled. Reachable IPv4 address returned in BND.ADDR of the UDP ASSOCIATE reply; this is not the listener bind address.
nextstringRequired. Points to the real outbound path.

At least one of connect or udp must be enabled.

When udp is enabled, the finalized preceding path must expose a dynamic UDP provider. In a normal built-in topology, that means starting the service with TcpUdpListener rather than TcpListener.

Optional fields​

FieldDefaultDescription
settings.connecttrueEnables SOCKS5 CONNECT.
settings.udpfalseEnables SOCKS5 UDP ASSOCIATE and UDP datagram handling.
settings.verbosefalseEnables extra logging, including authentication rejection reasons.
settings.sweep-interval-ms1000Forwarded to the internal UserController in authenticated mode.

The old inline authentication settings username, password, users, and accounts are no longer accepted. If any of those keys are present, node creation fails. Use AuthenticationServer plus AuthenticationClient, or use explicit no-auth: true.

Authentication model​

When authenticated mode is enabled, the SOCKS5 client must offer username/password authentication. Socks5Server receives the SOCKS username and password, builds one lookup string in this exact form:

username:password

It then calls the configured AuthenticationClient and checks that combined string as the user's password.

For AuthenticationServer users intended for SOCKS5, store the SOCKS credential pair in the user object's password field using the exact username:password form. The user object's name is not used for the SOCKS5 wire username; it can remain operator-facing metadata.

After successful authentication, the user identity and compatible credential metadata are attached to the Waterwall line. The internal UserController applies the configured user policy before the line reaches the configured next node. In no-auth mode no authenticated user identity is available for user-specific policy, so deployment-level access controls remain important.

SOCKS usernames and passwords must be non-empty in authenticated mode. Embedded NUL bytes are rejected because the authentication lookup key is passed as a C string.

CONNECT behavior​

After method negotiation and authentication, a CONNECT request is handled as a normal Waterwall stream path:

  1. The requested SOCKS destination is copied into line->dest_ctx.
  2. The destination protocol is set to TCP.
  3. Upstream Init is sent to the next node.
  4. Authenticated upstream payload may proceed after adjacent Init, before transport Est. Init reentry preserves older request bytes.
  5. Every downstream Est is forwarded immediately, including repeated notifications, without repeating the success reply.
  6. The SOCKS5 success reply precedes backend response bytes; ready reply/body backlogs drain in FIFO order without waiting for Resume.

If connect is disabled, CONNECT requests are rejected with command not supported.

UDP ASSOCIATE behavior​

UDP ASSOCIATE creates an association that is owned by the TCP control line. It does not open a normal upstream stream from the control line.

The provider is discovered automatically by walking the finalized preceding callback path. Optional middle tunnels can remain between TcpUdpListener and Socks5Server as long as that path still reaches the provider. No provider setting is added to the Socks5Server JSON.

The UDP associate reply uses:

  • the configured settings.ipv4 or settings.udp-ipv4 as the reply address
  • the dynamically allocated ephemeral UDP port provided by the preceding UdpListener (normally through TcpUdpListener) dynamic endpoint provider as the reply port

When UDP ASSOCIATE is requested, Socks5Server queries its upstream listener (UdpListener or TcpUdpListener) to allocate a dynamic, dedicated bound UDP socket endpoint for this client. Once the dynamic endpoint is bound and activated on the current worker, the assigned ephemeral UDP port is returned to the client in the UDP ASSOCIATE reply. If binding, activation, or reply allocation fails, only that association receives a general-failure reply; existing associations remain intact. The returned endpoint must be reachable through firewall and NAT policy. Static/fixed-port UDP listener traffic is not accepted as a SOCKS association.

At UDP Init and again before its first payload, endpoint metadata must name the line's actual owner WID and an active association handle. A valid-current-worker metadata/WID mismatch is an ordinary fail-closed UDP-only rejection: it drops the datagram, sends no reply, and leaves the TCP control line unchanged. A callback invoked from a different current worker is a fatal core invariant and is rejected before line state or worker-local resources are read.

UDP security model​

The UDP listener is not an open proxy. A UDP datagram is accepted only when:

  • the datagram arrives on the dynamic bound UDP socket assigned to that client association
  • the datagram source IP/port matches the pinned or expected peer address
  • the association was created by a successfully negotiated TCP control line and is active on the current worker

A concrete peer IP in the UDP-associate request that differs from the TCP control peer is rejected. A wildcard peer IP uses the control peer IP; when the requested source port is zero, the first matching datagram pins the port. Standard SOCKS5 does not cryptographically authenticate individual UDP datagrams. A peer sharing the expected public IP that learns a dedicated relay port can therefore race the first packet when the requested port is zero. Stronger protection needs a separately designed authenticated UDP encapsulation or token protocol.

When the TCP control line closes, its dynamic UDP endpoint is closed in UdpListener and its association is removed immediately from the worker-local registry. Later UDP datagrams arriving on that endpoint or referencing that generation are dropped.

Association state is worker-local and uses an opaque dynamic-endpoint identity. It stores copied metadata rather than a usable pointer to the TCP control line, and a closing control line removes only its current matching association.

UDP payload behavior​

When a SOCKS UDP datagram arrives:

  1. The server looks up the matching active association using the opaque dynamic-endpoint identity.
  2. It validates the SOCKS5 UDP header.
  3. If FRAG != 0, it ignores the datagram conservatively.
  4. It parses the requested remote destination from the header.
  5. It strips the SOCKS5 UDP header and forwards the payload upstream using the requested destination context.

When a reply comes back from the next node:

  1. The payload is wrapped in a SOCKS5 UDP response header.
  2. The response header source address identifies the remote endpoint associated with that reply.
  3. The wrapped datagram is sent downstream toward the dynamic UDP endpoint.

The client-facing association remains separate from outbound processing and uses the normal layer-4 callback contract. Association-owned outbound state is released when the control line closes.

Finish behavior​

The implementation follows Waterwall's close ordering:

  • local line state is destroyed before real Finish propagation
  • the control line closes its dynamic UDP endpoint and unregisters its UDP association before it can die
  • association-owned outbound state is released before close
  • final SOCKS5 failure replies, when needed before establishment, are sent through a close path that guards against re-entrant callbacks

Common mistakes​

  • Do not configure username, password, users, or accounts; those legacy keys are rejected.
  • Do not configure both auth-client-node-name and no-auth: true.
  • Do not enable UDP without ipv4 or udp-ipv4.
  • Do not use TcpListener -> Socks5Server with udp: true; that is a TCP-only entry and has no dynamic UDP provider.
  • Do not send SOCKS UDP datagrams to the configured static listener port; use the dynamic BND.PORT returned by UDP ASSOCIATE.
  • Do not manually place UserController immediately after an authenticated Socks5Server; it is inserted internally.
  • Do not expect SOCKS5 BIND or UDP fragmentation reassembly.
  • Do not allow only the TCP control port through a firewall/NAT when using UDP; the dynamically returned relay port must also be reachable.

Splice Support​

Socks5Server supports splice-backed TCP CONNECT payloads. Complete client deliveries are materialized with sbufEnsureOrdinary() until the TCP setup phase is established, before parser or nested-input admission. Later TCP payloads and necessary opaque ordering queues can retain their original buffers. Success replies still precede backend response bodies, and existing limits remain intact.

UDP client and remote lines request ordinary reads in both directions. A UDP-only service also requests ordinary reads on its TCP control line during Init. In a mixed CONNECT/UDP service, that control-line preference is set when an allowed UDP ASSOCIATE command is parsed. Preferences last for the exact line's lifetime and do not propagate between associated lines. UDP datagrams are still fully materialized before header decoding or framing because already-delivered or transformed input can remain splice-backed.

The internal UserController remains splice-capable; AuthenticationClient is a lookup dependency rather than a payload hop. Actual splice activation requires an eligible finalized chain and misc.splice.

Node Metadata​

The padding requirement lets UDP replies prepend SOCKS5 datagram headers while preserving Waterwall buffer-padding assumptions. If a reply buffer lacks enough left capacity, the tunnel allocates a replacement buffer and copies the payload before adding the header.

Source-backed metadata:

PropertyValue
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left262 bytes

Transport Est and the SOCKS reply are separate obligations. Init/Est reentry cannot overtake older request bytes. TCP Pause/Resume and transport Est propagate repeatedly without suppressing duplicates. Ready success replies and ordered application backlogs do not wait for Resume. A Pause received before next Init is replayed once that side is initialized; a Resume before replay cancels it. UDP association boundaries retain their existing signal mapping. Necessary request/reply ordering queues retain at most 1 MiB and 1,024 buffers per direction; ready direct payloads remain synchronous. Reply admission is published before callbacks so nested data cannot precede the reply.

After a CONNECT success reply has been handed off and next Init, startup Pause replay, and all request/reply ordering work have finished, the server destroys its local state and permanently bypasses itself on that TCP line. Published line credentials remain available. Later callbacks, including Finish, go directly to adjacent active nodes. UDP control, client, and remote lines remain active for association handling.

Control input advances in one synchronous parser scope. Nested deliveries follow older input; method/authentication state and identity are published before reply callbacks. Each negotiation message contains at most 513 metadata bytes. A complete CONNECT header followed by application bytes is accepted like separate deliveries; only genuinely retained application data consumes the shared ordering budget. Empty TCP input is ignored. After UDP ASSOCIATE, TCP control tails are discarded rather than interpreted as datagrams or new CONNECT requests.

An address with a zero-length domain is invalid as soon as its length byte is received. TCP requests receive the unsupported-address reply and close. Such UDP datagrams are discarded without closing the association or creating a backend. Truncated UDP addresses are also discarded within that datagram.

UDP backends are matched by the complete original address kind, exact address or domain bytes, and port within their client association. Hash collisions and later DNS/routing changes do not merge distinct destinations or change removal identity. Domain spelling and case are preserved.