Skip to main content

TrojanClient

TrojanClient is the client-side Trojan protocol tunnel for WaterWall. It writes the standard Trojan password hash, request command, and destination header, then forwards application traffic through the next stream-facing node.

The node supports Trojan TCP CONNECT and Trojan UDP UDP ASSOCIATE. It does not create TLS or open the remote server connection by itself.

Typical Placement​

TCP Trojan client:

TcpListener -> TrojanClient -> TlsClient -> TcpConnector

UDP Trojan client:

UdpListener -> TrojanClient -> TlsClient -> TcpConnector

Dynamic destination from a proxy or router:

Socks5Server -> TrojanClient -> TlsClient -> TcpConnector
SniffRouter -> TrojanClient -> TlsClient -> TcpConnector

TcpConnector normally connects to the Trojan server address and port. The real target service is encoded inside the Trojan request or inside each Trojan UDP packet.

TCP Example​

[
{
"name": "trojan-client",
"type": "TrojanClient",
"settings": {
"password": "secret-password",
"address": "example.com",
"port": 443,
"protocol": "tcp",
"domain-strategy": "do-not-resolve-domains",
"verbose": false
},
"next": "tls-client"
},
{
"name": "tls-client",
"type": "TlsClient",
"settings": {
"sni": "trojan.example.net",
"verify": true
},
"next": "server-out"
},
{
"name": "server-out",
"type": "TcpConnector",
"settings": {
"address": "trojan.example.net",
"port": 443,
"nodelay": true
}
}
]

This sends a Trojan request shaped as:

hex(SHA224("secret-password")) CRLF
command 01
destination example.com:443
CRLF

Dynamic-Destination Example​

{
"name": "trojan-client",
"type": "TrojanClient",
"settings": {
"sha224": "7e240de74fb1ed08fa08d38063f6a6a91462a815c15d3f6abf1d7e0b",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "dest_context->protocol"
},
"next": "tls-client"
}

Use sha224 when the config should contain the already-computed 56-character SHA224 hexadecimal digest instead of the raw Trojan password.

UDP Example​

{
"name": "trojan-client-udp",
"type": "TrojanClient",
"settings": {
"password": "secret-password",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "udp"
},
"next": "tls-client"
}

In UDP mode, the previous side remains UDP-facing. TrojanClient creates an internal TCP carrier line, sends a Trojan UDP ASSOCIATE request over that carrier, and wraps each local UDP payload into Trojan UDP packet framing.

Required Fields​

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "TrojanClient".
settingsobjectNon-empty Trojan client settings.
nextstringStream-facing node that carries Trojan bytes. Required in normal use.

Required settings fields:

FieldAccepted namesDescription
password materialpassword, pass, sha224, password-sha224, password_sha224Use either a raw password or a precomputed SHA224 hex digest, not both.
target addresstarget-address, address, targetFinal target host or IP, or "dest_context->address" / "line->dest_ctx->address".
target portportFinal target port, or "dest_context->port" / "line->dest_ctx->port". Constant ports must be in 1..65535.

password and pass are raw Trojan passwords. TrojanClient calculates SHA224(password) and sends the lowercase hex form. The sha224 aliases must already be exactly 56 hexadecimal characters; the node normalizes uppercase hex to lowercase.

Optional Settings​

FieldDefaultDescription
protocol"tcp"Selects Trojan command. Also accepts proto.
first-payload-timeout-ms400Integer milliseconds in 0..4,294,967,295; zero disables deliberate waiting.
verbosefalseEnables extra tunnel logging.
domain-strategy"do-not-resolve-domains"Controls whether domain targets are resolved locally before the Trojan request is written.

Supported protocol values:

ValueBehavior
"tcp" or "connect"Send Trojan command 0x01 for TCP CONNECT.
"udp" or "udp-associate"Send Trojan command 0x03 for UDP ASSOCIATE and carry UDP datagrams over an internal TCP carrier line.
"dest_context->protocol" or "line->dest_ctx->protocol"Use the incoming destination context protocol. Exact TCP selects 0x01; exact UDP selects 0x03.

If dest_context->protocol is selected but the incoming protocol is missing, ambiguous, or not TCP/UDP, the client logs a warning and falls back to TCP.

Domain Strategy​

domain-strategy controls the final target address encoded in the Trojan request and in UDP packet headers.

ValueBehavior
"do-not-resolve-domains"Keep domain targets as domains. The Trojan server side resolves the target.
"resolve-domains-and-accept-dns-returned-order"Resolve locally and use the first usable DNS result.
"resolve-domains-and-prefer-ipv4"Resolve locally, prefer IPv4, and fall back to IPv6.
"resolve-domains-and-prefer-ipv6"Resolve locally, prefer IPv6, and fall back to IPv4.
"resolve-domains-and-use-only-ipv4"Resolve locally and require an IPv4 result. If none exists, close the line.
"resolve-domains-and-use-only-ipv6"Resolve locally and require an IPv6 result. If none exists, close the line.
"resolve-domains-with-core-settings"Resolve locally using the dns settings from core.json.

When local resolution is enabled, TrojanClient creates an internal DomainResolver whose prepare hook applies the configured target before DNS resolution. Resolution applies when the final Trojan target address is a domain, including both fixed JSON targets and destination-context targets. Literal IP addresses are left unchanged.

Request Format​

Initial Trojan request:

password:     56 ASCII hex bytes, hex(SHA224(password))
separator: CRLF
command: 01 CONNECT or 03 UDP ASSOCIATE
destination: ATYP + address + port
separator: CRLF

Destination format:

ATYP 01: IPv4 address, 4 bytes, then 2-byte big-endian port
ATYP 03: domain length, domain bytes, then 2-byte big-endian port
ATYP 04: IPv6 address, 16 bytes, then 2-byte big-endian port

For UDP mode, the initial UDP ASSOCIATE request uses 0.0.0.0:0 as the associate address. The real remote destination is carried by each subsequent Trojan UDP packet.

Trojan UDP packet format:

destination:  ATYP + address + port
length: 2 bytes big-endian
separator: CRLF
payload: length bytes

First payload and transport establishment​

Init prepares the destination and starts the next node without emitting a request. The first eligible application payload may arrive before transport Est, during its callback, or after Pause. It is sent immediately with the complete request in one ordinary buffer, including one UDP frame header when applicable. Ordinary input is reused in place when its headroom fits all headers. Otherwise a best-fit ordinary buffer is allocated; any resident prefix and private-pipe body are copied in order. Empty TCP input does not trigger a request. Empty Trojan UDP datagrams are valid.

Direct TCP transport Est reaches the live application promptly, including repeated notifications, independently of protocol data and Pause. UDP carrier establishment maps once to its application. If the request remains unsent, its first-payload deadline starts at the first Est. settings.first-payload-timeout-ms defaults to 400, accepts integers from 0 to 4,294,967,295, and rejects other types and ranges. Zero disables deliberate waiting. Paused time counts. On direct TCP, expiry sends one header-only request even after Pause; all Pause/Resume notifications pass through, including duplicates, without retaining consumer-pause state. On an owned UDP carrier, the client is the exact-line head and retains producer control: an idle header due during carrier Pause waits for Resume. An eligible payload arriving first cancels the timer and sends the combined output. Duplicate Est/Resume cannot repeat the request or reset its deadline. Finish cancels the timer. One ordinary output does not promise one syscall, TCP packet, or TLS record.

Direct TCP and per-datagram upstream forwarding do not wait for Est or Resume and do not keep application output queues. Trojan has no success response; downstream TCP is opaque from Init. Once the complete initial TCP request is ready, the client destroys its setup state, cancels the timer and permanently bypasses itself before handing off that buffer. All later callbacks follow the same line directly through the neighbouring tunnels. Reentrant Est continuations check that the client is still active before accessing its state. UDP application and carrier lines remain active for framing, signal mapping and owned-carrier cleanup.

UDP application lines remain borrowed UDP-facing lines. The client creates one dependent, owned TCP carrier on the application's worker and maps Est, Pause/Resume and Finish to that association. The request uses 0.0.0.0:0; each frame encodes its destination, length and CRLF. The UDP decoder finishes every complete datagram in an admitted input even when an earlier delivery causes Pause. Nested input joins the guarded parser FIFO behind older input; only incomplete framing/order state remains. Finish, malformed input or receiver refusal stops delivery and closes the affected association. Only this node's owned carrier is destroyed here; the application owner drains it during shutdown.

Splice and retention​

The first request plus payload is ordinary; the request itself is at most 320 bytes. Later opaque TCP preserves its original ordinary or splice representation. UDP sends prepend at most 263 bytes within the advertised 331-byte padding budget. Header parsing and exact body movement preserve boundaries and use complete ordinary fallback when padding or pipe capacity requires it. Receive extraction preserves full onward padding.

Retained parser inputInclusive logical limit
UDP wire input, including active head and cached header2,162,950 bytes (2 MiB plus one 65,798-byte maximum frame)
UDP body65,535 bytes; empty bodies are valid

Equality passes; overflow or parser queue refusal closes the association. There is no retained decoded-output queue or 1,024-record limit on a synchronous batch. Fragment counts do not consume output slots. These are logical protocol limits, independent of buffer-pool size, RAM profile, and pipe capacity. Oversized local datagrams are dropped; malformed received frames close the association. Received source addresses are validated without changing the application destination.

The two-byte body length supports 0 through 65,535 payload bytes in either direction; the address, length and CRLF add up to 263 bytes outside that body. This is a Trojan framing limit, not a guarantee that a UDP socket or remote peer accepts every size. For example, ordinary IPv4 UDP permits at most 65,507 payload bytes without IP options, and a path or peer may impose a lower limit. Each accepted payload remains one datagram: TCP may split the encoded frame into reads, but Trojan never truncates the payload or turns it into several application datagrams.

Splice requires misc.splice, a supported build and support from every node in the final chain, including internal helpers. DomainResolver supports splice. TlsClient still blocks splice for its entire chain. Keep TLS in ordinary deployment configurations; splice capability does not guarantee zero-copy or a measured speed increase.

Finish Behavior​

TrojanClient destroys its own line state before propagating real Finish callbacks. It does not call lineDestroy() on normal external lines.

In UDP mode, it owns the internal TCP carrier line it created. If the app line or carrier line closes, it closes the paired side and destroys the owned carrier line safely.

Padding​

TrojanClient reserves enough padding for its 68-byte UDP association request and the largest 263-byte datagram header, combined on the first payload:

required_padding_left = 331

The datagram header covers ATYP + domain length + 255-byte domain + port + payload length + CRLF. The same budget also covers the largest 320-byte TCP request.

Unsupported Features​

This implementation does not create or manage:

  • TLS
  • WebSocket, HTTP, gRPC, or other transport wrappers
  • server-side fallback
  • non-standard Trojan extensions

Use separate WaterWall nodes such as TlsClient, HttpClient, MuxClient, or TcpConnector when you need those layers.

Common Mistakes​

  • Do not expect TrojanClient to connect to the Trojan server by itself; put TlsClient and a connector after it.
  • Do not omit TlsClient for public Trojan deployments unless you intentionally want plain Trojan on the wire.
  • Do not confuse the Trojan server address with the final target address.
  • Do not use UDP mode with a next node that is not stream-facing.

Node Metadata​

Source-backed metadata:

PropertyValue
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left331 bytes