Socks5Client
Socks5Client is a stream middle tunnel that speaks the client side of the SOCKS5 protocol. It connects to the next
transport as a SOCKS proxy connection, negotiates the SOCKS5 method, optionally authenticates with username/password,
and then sends either a CONNECT or UDP ASSOCIATE request for the configured final target.
The target configured on Socks5Client is the final destination that should appear inside the SOCKS5 request. The
address of the SOCKS proxy itself belongs to the next connector, usually TcpConnector for TCP-only use or
TcpUdpConnector when UDP relay traffic is needed.
Typical placement
TCP proxying:
TcpListener -> Socks5Client -> TcpConnector
TCP and UDP proxying through the same SOCKS server:
TcpUdpListener -> Socks5Client -> TcpUdpConnector
In the second topology, the combined connector is expected to reach the SOCKS proxy over TCP for the control connection
and UDP for the relay address returned by UDP ASSOCIATE.
Basic example
This example preserves the incoming destination from line->dest_ctx and asks the SOCKS proxy to connect to that same
destination.
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "tcp",
"user": "alice",
"pass": "secret",
"domain-strategy": "do-not-resolve-domains",
"verbose": false
},
"next": "proxy-transport"
}
proxy-transport should be the node that connects to the SOCKS5 server, for example a TcpConnector configured with
the proxy server address and port.
Dynamic protocol example
Use dest_context->protocol when the incoming line may already be marked as TCP or UDP and the SOCKS client should
follow that protocol.
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "dest_context->protocol"
},
"next": "proxy-transport"
}
If the incoming destination context is exactly TCP, Socks5Client sends CONNECT. If it is exactly UDP, it negotiates
UDP ASSOCIATE. If the protocol flags are missing, ambiguous, or not TCP/UDP, it logs a warning and falls back to TCP.
Fixed target example
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "example.com",
"port": 443,
"protocol": "tcp",
"domain-strategy": "resolve-domains-and-prefer-ipv4"
},
"next": "proxy-transport"
}
With local domain resolution enabled, the internal DomainResolver resolves example.com before the SOCKS request is
sent. If an IPv4 answer is available, the SOCKS request carries the IPv4 address. Otherwise it falls back according to
the selected strategy.
Required fields
| Field | Type | Description |
|---|---|---|
settings | object | Required and must not be empty. |
settings.address / settings.target-address / settings.target | string | Final SOCKS destination address. Use a literal IP, a domain name of 1 to 255 bytes, dest_context->address, or line->dest_ctx->address. Longer domain names are rejected during node creation. |
settings.port | number or string | Final SOCKS destination port. Use a number from 1 to 65535, dest_context->port, or line->dest_ctx->port. |
next | string | Required. Points to the transport that reaches the SOCKS proxy. |
Optional fields
| Field | Default | Description |
|---|---|---|
settings.protocol / settings.proto | tcp | SOCKS command source. Supports tcp, connect, udp, udp-associate, dest_context->protocol, and line->dest_ctx->protocol. |
settings.user / settings.username | unset | SOCKS5 username. Must be provided together with pass/password. |
settings.pass / settings.password | unset | SOCKS5 password. Must be provided together with user/username. |
settings.domain-strategy | do-not-resolve-domains | Controls whether domain targets are resolved locally before the SOCKS request is encoded. |
settings.verbose | false | Enables extra debug logging. |
Credentials must be non-empty and no longer than 255 bytes each. If only one side of the username/password pair is configured, node creation fails.
Domain strategy
Socks5Client prepares the configured target before the SOCKS protocol code runs and keeps the selected protocol for the
real client line.
When domain-strategy enables local resolution, the client creates an internal DomainResolver whose prepare hook
applies the configured target before DNS resolution. Resolution applies to fixed domain targets and to targets copied
from dest_context->address. Literal IP addresses are left unchanged.
Supported values:
| Value | Behavior |
|---|---|
do-not-resolve-domains | Keep domain names as SOCKS5 domain addresses. The SOCKS server side resolves them. |
resolve-domains-with-core-settings | Use the DNS preference configured in core.json. |
resolve-domains-and-accept-dns-returned-order | Resolve locally and use the first usable DNS result. |
resolve-domains-and-prefer-ipv4 | Prefer IPv4, fall back to IPv6. |
resolve-domains-and-prefer-ipv6 | Prefer IPv6, fall back to IPv4. |
resolve-domains-and-use-only-ipv4 | Use only IPv4 answers; close the line if none are available. |
resolve-domains-and-use-only-ipv6 | Use only IPv6 answers; close the line if none are available. |
Initialization direction
Application lines enter Socks5Client through upstream Init. Downstream Init is disabled for every line role,
including UDP application, control, and relay lines; receiving it logs a fatal diagnostic and terminates the process
through abortProgramNow(1). Downstream Est and payload callbacks remain supported for proxy replies and relay data.
TCP behavior
For TCP mode, the lifecycle is:
- Upstream
InitinitializesSocks5Clientline state and forwardsInitto the next transport. - The next transport establishes the proxy connection; its downstream
Estis forwarded immediately, before SOCKS negotiation. Socks5Clientsends the SOCKS5 method greeting once. Repeated transportEstnotifications continue downstream toward the application without repeating the greeting.- If credentials are configured and the server selects username/password auth, it sends the username/password request.
- It sends a SOCKS5
CONNECTcommand for the configured target. - A successful command reply opens the application-data gate; it does not emit another
Est. - The pending upstream FIFO drains without waiting for Resume; nested input follows older bytes.
TCP Pause/Resume notifications pass through immediately, including duplicates and notifications during negotiation. The greeting and ready application/reply output do not wait for Resume. A receive Pause reaches the next adapter even during negotiation, so its issuer must arrange Resume or close.
After successful TCP negotiation, all retained input/output has drained and the local source hold is released, the client destroys its line state and permanently bypasses itself. Later callbacks pass directly between neighbors. Already-running callbacks check the active mask before using state that a nested exchange could retire.
If upstream payload arrives before the SOCKS request succeeds, it is queued. Retained upstream payload is limited to 2 MiB and 1,024 buffers.
The 4,096-byte bound applies only to incomplete reply metadata; a completed reply may arrive with a larger application body. Nested reply processing is serialized, with response bytes kept in FIFO order. Necessary retained response input shares a 2 MiB / 1,024-buffer downstream budget and drains after older reply input, including through Pause; ready TCP forwarding stays synchronous. Empty proxy TCP input is ignored, and unused TCP tails on an established UDP control line are discarded. Ordering queues allocate backing storage on first use; UDP application and relay lines do not allocate handshake stream storage.
If the proxy rejects all methods, authentication fails, the command reply is invalid, or the command fails, the tunnel closes the line.
UDP behavior
For UDP mode, the application line represents the user-facing UDP flow, while Socks5Client creates two internal lines:
| Internal line | Purpose |
|---|---|
| UDP control line | A TCP line that connects to the SOCKS proxy and sends UDP ASSOCIATE. The request target is 0.0.0.0:0. |
| UDP relay line | A UDP line that connects to the relay address returned by the SOCKS proxy. |
The relay line uses the BND.ADDR and BND.PORT in the successful UDP ASSOCIATE reply. It does not reuse the TCP control port or overwrite that
negotiated endpoint with the next connector's static SOCKS-proxy address and
port. A server can therefore return a dedicated ephemeral relay port; firewall
and NAT policy must allow the returned endpoint, not merely the TCP control
port.
This negotiated authority survives a Router or sniffer clearing observed
application-protocol metadata. The outbound UDP adapter consumes it exactly
once, then clears the internal marker so it cannot affect later unrelated
routing.
The control transport Est maps once to the application line. Datagram delivery still waits for both the successful control association and UDP relay readiness, under the 2 MiB / 1,024-buffer pending limit.
When application payload goes upstream, Socks5Client prepends a SOCKS5 UDP request header containing the configured
final target and sends the datagram through the UDP relay line. When a relay datagram comes back downstream, it validates
and strips the SOCKS5 UDP header before forwarding only the payload toward the previous node.
SOCKS5 UDP fragmentation is not reassembled. Datagrams with FRAG != 0 are ignored conservatively.
Common mistakes
- Do not put the SOCKS proxy address in
settings.address; that field is the final target inside the SOCKS request. - Do not use UDP mode with a TCP-only next connector. UDP mode needs a next side that can create the TCP control line and the UDP relay line.
- Do not expect
domain-strategyto change literal IP targets. It only affects domain targets. - Do not omit
portwhen usingdest_context->address; address and port are selected independently.
Splice Support
Socks5Client supports splice-backed TCP payloads. During method, authentication
and command negotiation, complete proxy-reply deliveries are materialized with
sbufEnsureOrdinary() before parser or nested-input admission. After negotiation,
TCP payloads and necessary opaque FIFO entries can retain their original buffers.
The incomplete-handshake and application-backlog limits remain separate.
UDP application, TCP control and UDP relay lines request ordinary reads in both directions for their lifetimes. UDP target preparation sets the application preference before local DNS can retain input. Preferences are advisory and do not propagate between associated lines; every UDP datagram is still materialized before application retention, header decoding or framing. Ordinary input passes through the helper unchanged. TCP CONNECT lines do not request ordinary reads.
The internal DomainResolver remains splice-capable. Actual activation requires
an eligible finalized chain and misc.splice; a TLS or other blocking node still
disables splice for the entire payload chain.
Node Metadata
The padding requirement lets UDP mode prepend SOCKS5 datagram headers without breaking Waterwall buffer-padding assumptions. If an incoming buffer still lacks enough left capacity, the tunnel allocates a replacement buffer and copies the payload before adding the header.
Source-backed metadata:
| Property | Value |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 262 bytes |
Transport Est does not mean SOCKS negotiation has succeeded. Application payload can be accepted after Init;
the real protocol backlog is bounded at 2 MiB and 1,024 buffers. TCP greeting and ready application/reply output
do not wait for Resume. The first retained application input pauses its source; the source resumes when the
older FIFO drains. This local protocol hold is independent of transparently relayed transport pressure.
TCP receive Pause also reaches the next adapter during negotiation; its issuer must arrange Resume or close.
For UDP, control and relay write pressure combine; one Resume cannot clear the other's outstanding Pause.
UDP application backlog release and the owned control greeting continue to honor that boundary pressure,
and application receive pressure is mapped to the relay line after its Init.
UDP application, control and relay lines remain active for framing, association ownership and pressure mapping.