TrojanServer
TrojanServer is the server-side Trojan protocol tunnel for WaterWall. It parses a Trojan byte stream from the previous
node, authenticates the standard SHA224 password hash, extracts the requested TCP or UDP destination, and forwards
accepted traffic to its configured next node.
The node supports local password allowlists and AuthenticationClient backed users. It also supports optional fallback
for unauthenticated invalid probes.
This current node replaces the older two-node setup that used separate TrojanAuthServer and TrojanSocksServer nodes.
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
Normal Trojan server:
TcpListener -> TlsServer -> TrojanServer -> TcpUdpConnector
TCP-only outbound:
TcpListener -> TlsServer -> TrojanServer -> TcpConnector
With routing after authentication:
TcpListener -> TlsServer -> TrojanServer -> Router
TrojanServer does not terminate TLS by itself. For public Trojan deployments, place TlsServer before it. Without TLS,
the password hash and destination metadata are visible on the wire.
Minimal Local-Password Example
[
{
"name": "tls-in",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem"
},
"next": "trojan-server"
},
{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"password": "secret-password",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
]
Multiple Local Users
{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"users": [
"secret-password",
{
"username": "alice",
"password": "another-secret"
}
],
"connect": true,
"udp": true
},
"next": "outbound"
}
In local allowlist mode, TrojanServer stores the matched raw password on the line so Router can match password rules.
If the matching object has username, that username is also stored on the line for username-aware routing.
AuthenticationClient Mode
{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
}
In this mode, the configured AuthenticationClient is the authority. Store each user's plaintext Trojan password in the
user object's password field; AuthenticationClient prepares the SHA224 lookup data internally. The user object's
name is an account name, not the Trojan password itself.
When auth-client-node-name is configured, local password settings are rejected. TrojanServer internally inserts a
UserController before the configured outbound node, so do not place a manual UserController directly after it.
Fallback Example
{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"password": "secret-password",
"fallback-node-name": "fallback-service",
"fallback-intentional-delay-ms": 7,
"fallback-intentional-delay-jitter-ms": 1,
"connect": true,
"udp": true
},
"next": "outbound"
}
Fallback is for active-probing resistance. Invalid unauthenticated traffic can be handed to another configured node instead of being closed immediately.
Required Fields
Top-level fields:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "TrojanServer". |
settings | object | Non-empty Trojan server settings. |
next | string | Required. Accepted Trojan traffic is forwarded to this node. |
Choose exactly one authentication mode.
Local Authentication Settings
Use local authentication by omitting auth-client-node-name and configuring at least one raw password.
| Field | Type | Description |
|---|---|---|
password | string | One raw Trojan password. |
pass | string | Alias for password. Use either password or pass, not both. |
passwords | array of strings | Multiple raw Trojan passwords. |
users | array | Password strings or objects with password or pass. Objects can include username. |
clients | array | Alias for users. Use either users or clients, not both. |
Password strings must be non-empty. Duplicate local passwords are fatal configuration errors, even when the duplicate entries use different usernames.
Database Authentication Settings
| Field | Type | Description |
|---|---|---|
auth-client-node-name | string | Name of an existing AuthenticationClient node in the same config file. |
The referenced node must exist, must be an AuthenticationClient, and must not point back to the TrojanServer node
itself. In database mode, do not configure password, pass, passwords, users, or clients.
Optional Settings
| Field | Default | Description |
|---|---|---|
connect | true | Enables Trojan TCP CONNECT, command 0x01. |
udp | true | Enables Trojan UDP ASSOCIATE, command 0x03. |
verbose | false | Enables focused authentication diagnostics. |
fallback-node-name | not set | Fallback branch for unauthenticated invalid probes. Aliases: fallback-node, fallback. |
fallback-intentional-delay-ms | 7 | Delay for upstream payloads sent to fallback. Nonzero delay uses configured jitter. At 0, accepted input forwards synchronously, including completion under Pause; branch Init and reentry preserve FIFO. Must not be negative. |
fallback-intentional-delay-jitter-ms | 1 | Random jitter around delayed fallback payload scheduling. Ignored when delay is 0. Must not be negative. |
sweep-interval-ms | 1000 | Optional setting forwarded to the internally created UserController in database mode. |
At least one of connect or udp must be enabled.
Request Format
Accepted 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
body: TCP stream bytes or Trojan UDP packets
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
Trojan UDP packet format:
destination: ATYP + address + port
length: 2 bytes big-endian
separator: CRLF
payload: length bytes
Authentication Flow
Trojan clients send hex(SHA224(password)) as 56 ASCII hex bytes. TrojanServer decodes that value and authenticates it
against the selected mode.
In local mode, the server precomputes SHA224 for each configured raw password. In database mode, it uses the
AuthenticationClient SHA224 lookup API and records the returned user handle on the line after the request is parsed.
The complete 56-byte password hash must be present in the first upstream payload callback. If the first payload does not contain the full hash, the line is treated as an invalid unauthenticated probe. With fallback configured, those bytes go to fallback; otherwise the line is closed. Later request fields, including the CRLF, command, destination, and early body bytes, may still arrive across later payload callbacks after the complete password hash is present.
There is no unauthenticated Trojan mode.
TCP CONNECT Runtime Behavior
For command 0x01, the server:
- authenticates the password hash
- parses the requested destination
- writes that destination into
line->routing_context.dest_ctx - records local or database credentials on the line for downstream routing
- initializes the configured
nextnode - forwards any TCP body bytes that arrived in the same payload as the request header
- forwards downstream
Estonly after the selected outbound path establishes
The configured next node is usually TcpConnector, TcpUdpConnector, Router, or another node that understands the
destination context.
UDP ASSOCIATE Runtime Behavior
For command 0x03, the initial request address is only the association metadata. The real remote destination is carried
by each Trojan UDP packet.
For each unique UDP packet destination, TrojanServer creates or reuses one internal backend UDP line. That backend line
gets the packet destination in line->routing_context.dest_ctx, carries the authenticated user credentials, and forwards
the raw UDP payload to the configured next node.
Replies from backend UDP lines are wrapped back into Trojan UDP packet framing and sent downstream to the original Trojan client stream. One Trojan UDP association can therefore talk to multiple remote UDP endpoints.
Malformed UDP packet headers, zero-length domain names, missing ports or bad CRLF separators close the affected Trojan line. UDP bodies use the full 0–65,535-byte range of the two-byte length field. Backend replies are wrapped and forwarded synchronously without waiting for Est or Resume.
Fallback Behavior
Fallback is used only before successful password authentication. After authentication succeeds, malformed Trojan protocol data closes the line instead of being replayed to fallback.
Fallback may be selected for:
- invalid password prefix bytes
- a password hash split across the first upstream payload
- invalid password CRLF before authentication
- invalid password hex before authentication
- failed password lookup
- cached AuthenticationClient admission being unavailable (unloaded, stopping, rejected or terminal continuity failure)
When fallback is selected:
- the fallback branch receives
Initimmediately - saved initial bytes and later upstream payloads are forwarded to fallback
- normal live upstream fallback payloads observe
fallback-intentional-delay-msplus jitter when delay is nonzero - at delay
0, admitted input forwards synchronously even under Pause; Init and reentry preserve FIFO - the delayed fallback FIFO is bounded to 2 MiB and 1,024 buffers per line
- selected fallback relays every Pause/Resume, including repeats; due delayed output does not wait for Resume
- on upstream
Finish, an initialized branch receives one accepted FIFO batch synchronously before fallbackFinish, including during Pause; a branch still inside Init discards the still-local batch and closes - downstream fallback responses are not intentionally delayed
The remaining intentional delay is not kept alive after upstream close.
The default fallback delay is intentionally small. Tune delay and jitter against the public behavior the fallback is supposed to resemble; they are mitigations, not proof of timing indistinguishability.
Splice, buffering and backpressure
TrojanServer accepts ordinary buffers, private-pipe splice buffers and mixed input in authentication, CONNECT, ASSOCIATE and fallback. The initial parser consumes only the required header bytes: 68 for IPv4, 80 for IPv6, or 65 plus the domain length, at most 320. The first callback must still contain all 56 hash bytes. Available password CRLF bytes are checked before authentication; a hash accepted before a later CRLF arrives is authenticated only once. Invalid bytes arriving after authentication close the protected flow, without fallback. Credentials and database handles are recorded before protected branch Init.
TCP bodies remain opaque after the request. Initial parsing, branch Init and
reentrant input retain an authoritative FIFO so older accepted bytes cannot be
overtaken. Valid input forwards after onward Init without waiting for transport
Est or Resume. Once normal TCP branch Init has returned, retained input has drained
and any pre-Init Pause has been relayed, TrojanServer destroys and zeros its
line state, then permanently bypasses itself for that line. Later callbacks,
including Finish, follow the normal next/prev path directly. Authentication
credentials remain on line_t; bypass does not wait for Est. TCP and fallback
Est notifications forward promptly, including repeats and callbacks from inside Init.
Pause/Resume also forward every notification on initialized TCP and fallback paths.
Before branch selection, only the latest upstream pause state is retained for the
selected branch's Init; a Resume cancels that deferred Pause.
UDP associations and fallback keep TrojanServer active: they still need its framing, owned backend cleanup, branch selection or delayed replay.
Fallback replays the inspected resident prefix and untouched input tail exactly once before newer input. Init remains immediate, with the configured payload delay and jitter. A delayed batch is one buffer and one Payload callback; mixed or pipe-backed assembly uses exact movement, with complete ordinary fallback on pipe pressure. Due delayed batches do not wait for Resume. On source Finish, an initialized branch receives at most one final batch synchronously, even after Pause. A branch still inside Init discards the pending replay. No final callback reflects toward the finished side.
A fallback may generate a local response without outbound Est, including a
response immediately followed by Finish inside its Init or Payload callback.
Replies are forwarded synchronously even if Pause was received, so an admitted
response reaches the client before Finish. Source-Finish cleanup still absorbs
replies from the final fallback batch instead of reflecting them.
UDP reads only each 11-byte IPv4, 23-byte IPv6 or 8 + domain_length header, then
extracts exactly one body, including empty datagrams. Every backend is an owned
normal line on the client worker. It carries the authenticated credentials and
is initialized only after its association membership and destination are ready.
TrojanServer borrows the client line and destroys only its own backends.
UDP input pressure is association-wide: each paused backend contributes once to stopping new carrier-source work. Resume occurs only after all contributions clear. The currently admitted input still completes every complete datagram in FIFO order, including more than 1,024 tiny or empty datagrams. Nested input is serialized behind older bytes; only an incomplete suffix remains after the dispatch. Client reply Pause propagates to every initialized backend source, including newly created backends. Callback-driven close stops processing and releases local ownership.
Backend replies are framed and forwarded inline, before or after backend Est and under Pause, with no decoded-output queue or Est waiter. The first backend transport Est maps once to the client association; later backend Est does not repeat it. Receivers own finite in-flight headroom and their hard-limit close policy. If a selected backend closes during Init, permission notification or current delivery, the carrier association closes so an admitted datagram is not silently discarded. Backend-local errors outside decoding retain their local scope.
| Retained work | Fixed limit |
|---|---|
| Initial/UDP wire input, including active head and cached headers | 2,162,950 bytes (2 MiB plus a 65,798-byte maximum frame) |
| Temporary TCP Init/reentry ordering | 2 MiB and 1,024 buffers |
| Delayed fallback backlog and temporary replay ordering | 2 MiB and 1,024 buffers |
| Initial header cache | 320 actual bytes |
| Incoming UDP body | 65,535 bytes |
| Backend reply body | 65,535 bytes |
Initial assembly permits a partial request followed by a full 2 MiB delivery. Consumed headers are removed before applying the selected path's body budget. Input fragmentation and synchronously consumed output have no 1,024-record cap. Equality passes; retained-state overflow closes the affected association. Direct opaque paths have no queue merely to impose a byte limit. These logical limits are independent of RAM profile, buffer geometry and pipe capacity.
Both incoming datagrams and backend replies accept 0 through 65,535 payload bytes, matching the two-byte length field. Empty datagrams remain valid. The frame adds at most 263 header bytes outside the body. This framing range does not override the downstream UDP socket, network path or remote peer limits; ordinary IPv4 UDP, for example, permits at most 65,507 payload bytes without IP options. Each accepted body stays one datagram, even when the TCP carrier delivers its frame in several reads. Trojan does not truncate or split a body into separate UDP datagrams.
UDP prepend stays within the advertised 263-byte headroom and preserves resident prefix bytes as payload. Insufficient padding and private-pipe resource refusal use the shared exact-movement helpers and complete ordinary fallback. Source-owner shutdown releases dependent backends, queues and private bodies before chains and pools disappear; delayed fallback tasks hold only physical line lifetime.
Splice eligibility includes the internal UserController, connector helpers and all reachable fallback branches after chain finalization. The AuthenticationClient lookup dependency is not a payload hop. TlsServer and TlsClient still block splice for any chain containing them. Keep TLS in ordinary Trojan deployment guidance. Eligibility promises safe buffer handling, not universal zero-copy or measured speed improvements.
Finish Behavior
TrojanServer destroys its own line state before propagating real Finish callbacks.
The client stream line is created by the previous adapter and is not destroyed by TrojanServer. Internal UDP backend
lines are created by TrojanServer; it owns those lines and destroys them safely when the client stream closes or when a
backend UDP line closes.
Padding
TrojanServer may prepend the largest possible Trojan UDP reply header:
required_padding_left = 263
That covers ATYP + domain length + 255-byte domain + port + payload length + CRLF.
Unsupported Features
This implementation does not create or manage:
- TLS termination
- WebSocket, HTTP, gRPC, or other transport wrappers
- legacy
TrojanAuthServerplusTrojanSocksServercomposition - non-standard Trojan extensions
Use separate WaterWall nodes such as TlsServer, HttpServer, MuxServer, TcpUdpConnector, or Router when you need
those layers.
Common Mistakes
- Do not expose
TrojanServerdirectly on a public TCP listener withoutTlsServerunless you intentionally want plain Trojan on the wire. - Do not configure local passwords together with
auth-client-node-name. - Do not set the next node to a manual
UserController; database mode inserts its own internalUserController. - Do not use the old
TrojanAuthServerandTrojanSocksServernode names for new configurations. - Do not expect the initial UDP ASSOCIATE address to choose the final UDP target; each Trojan UDP packet carries its own target address.
Node Metadata
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 | 263 bytes |