RealityServer
RealityServer is the server-side Reality v2 tunnel. It starts every incoming line as ordinary visitor traffic, forwards that traffic to a configured destination branch, passively extracts the real TLS handshake binding, and only then tests client-to-server records with fresh per-connection session keys. TLS 1.3 requires an authenticated request/ack/confirm handoff before the visitor branch closes and the same WaterWall line switches to protected next; TLS 1.2 retains immediate profile-aware authorization.
The server does not terminate TLS for visitors. It only sniffs enough TLS-like records to decide whether a peer is a Reality client or ordinary visitor traffic.
Typical Placement
TcpListener -> RealityServer -> protected-chain
|
+-> destination visitor branch
The next node receives authorized Reality traffic. The destination setting names the normal visitor branch, commonly a TcpConnector for the real visitor site.
Basic Example
{
"name": "reality-server",
"type": "RealityServer",
"settings": {
"destination": "visitor-site",
"password": "replace-with-a-strong-secret",
"algorithm": "chacha20-poly1305",
"sniffing-attempts": 8
},
"next": "protected-chain"
}
Visitor destination:
{
"name": "visitor-site",
"type": "TcpConnector",
"settings": {
"address": "www.example.com",
"port": 443
}
}
Required Fields
| Field | Type | Description |
|---|---|---|
type | string | Must be RealityServer. |
next | string | Node that receives authorized, decrypted Reality payload. |
settings | object | Must be a non-empty object. |
settings.destination | string | Node name for visitor traffic. The node must exist and must not be RealityServer itself or its authorized next node. |
settings.password | string | Shared Reality secret containing 1 through 32 UTF-8 bytes. Must match the client. |
Settings
| Field | Default | Description |
|---|---|---|
destination | required | Visitor branch node name. Handshake records, ordinary visitor TLS, invalid probes, and failed Reality candidates are forwarded here while the line is not authorized. |
password | required | Shared Reality secret containing 1 through 32 UTF-8 bytes. |
algorithm | chacha20-poly1305 | AEAD algorithm. Accepts chacha20-poly1305, chacha20poly1305, aes-gcm, aes-256-gcm, or aes256gcm. |
method | same as algorithm | Alias for algorithm. Used only when algorithm is absent. |
salt | waterwall-reality | Salt used in key derivation. An explicitly configured value must contain 1 through 32 UTF-8 bytes and match the client. |
kdf-iterations | 12000 | Integer key derivation rounds. Valid range is 1 through 1000000; fractional numbers are rejected. |
sniffing-attempts | 8 | Integer number of failed Reality candidates after the one-record TLS 1.3 pre-request cover allowance before permanently treating the line as visitor traffic. Valid range is 1 through 1024; TLS 1.2 has no allowance. |
sniffing-counter | same as sniffing-attempts | Legacy integer alias used only when sniffing-attempts is absent, with the same range. |
tls12-gcm-server-nonce-policy | auto | Downstream AES-GCM explicit-nonce policy: auto, sequence, counter, or random. |
aes-gcm requires AES-GCM support in the active crypto backend. If it is unavailable, tunnel creation fails.
Credential limits are measured in UTF-8 bytes, not characters, using the same byte length supplied to BLAKE2s. Optional defaults apply only when a key is absent; a present key with the wrong JSON type is a startup error. Primary fields win over aliases even when malformed: method cannot rescue an invalid algorithm, and sniffing-counter cannot rescue an invalid sniffing-attempts.
Visitor And Protected Branches
On upstream Init, RealityServer initializes its line state and immediately initializes the visitor destination branch. The protected next chain is not initialized yet.
While the line is pending:
- bounded streaming parsers observe fragmented ClientHello/ServerHello messages and, for TLS 1.2, each direction's protected-record epoch while bytes continue to the visitor branch
- TLS 1.3
supported_versionsis honored and HelloRetryRequest is ignored until the final ServerHello - malformed or unsupported handshake input switches the line to ordinary visitor behavior
- before both hello values are ready, application-data-looking records remain visitor traffic and are never tested with the root key
- after deriving v2 directional session keys, candidate records are duplicated and authenticated at the exact expected client-to-server sequence
- if authentication fails while pending, the original record is forwarded to
destination; there is no v1 trial decrypt - TLS 1.2 keeps its immediate authorization path; for TLS 1.3 only
HANDOFF_REQUESTat sequence0may begin handoff
After TLS 1.3 session keys are derived, the server grants one internal, non-resettable pre-request cover-record allowance for the genuine encrypted client Finished record. A failed candidate that uses this allowance is forwarded byte-for-byte without incrementing sniffing-attempts or advancing the Reality sequence. The allowance cannot authenticate a client, is discarded when HANDOFF_REQUEST authenticates, and is not used by TLS 1.2. Later failed candidates consume the configured budget normally.
After a TLS 1.3 request authenticates, the server finishes forwarding only the already-started destination TLS record. A streaming boundary tracker splits at the first safe record boundary, suppresses every later destination downstream byte, enters the ACK-waiting state, and sends HANDOFF_ACK at server-to-client sequence 0. Late genuine TLS protocol output from the client may still travel upstream to the destination before confirmation. Failed HANDOFF_CONFIRM trials preserve the original record and sequence but are bounded by record count and bytes. Confirmation at client-to-server sequence 1 closes the remaining destination direction, initializes protected next exactly once, and enables strict Authorized behavior. Application records cannot authorize TLS 1.3 directly.
After authorization:
| Direction | Behavior |
|---|---|
| Upstream payload | Must be valid Reality records. Records are authenticated, decrypted, and forwarded as cleartext to next. |
| Downstream payload | Cleartext from next is encrypted and sent back to the previous node as TLS-like application-data records. |
If upstream traffic is invalid after authorization, if a non-Reality TLS record arrives after authorization, or if Reality authentication fails after authorization, the server queues at most one authenticated fatal bad_record_mac when the wire remains writable and then closes both directions.
The server owns orderly Reality shutdown after authorization. A protected-side orderly Finish queues exactly one close_notify, then immediately finishes the wire side without waiting for a reply. RealityClient consumes that record without replying and closes its TCP side immediately, even if a later server FIN is withheld. Normal clients close with raw transport FIN and do not send close_notify. Received fatal alerts are not answered, and a raw wire Finish cannot be answered. Pending, Visitor, and both TLS 1.3 handoff phases close silently without synthetic Reality alerts because the client may still be interpreting downstream bytes as cover TLS.
Visitor Fallback Rules
The line becomes visitor traffic when:
- the pending bytes are not a plausible TLS record
- enough Reality candidate records fail authentication to reach
sniffing-attemptsafter the bounded TLS 1.3 pre-request allowance, if applicable
The public TLS record prefix is classified progressively without consuming it: an unsupported content type is decisive after byte one, an invalid legacy-version major after byte two, and a minor greater than 0x04 after byte three. A plausible one- through four-byte prefix remains Pending while the connection is open. If upstream Finish arrives in Pending mode, those exact buffered bytes are transferred to destination before the destination receives Finish.
After a line switches to visitor mode, upstream payload is forwarded to the visitor destination, and downstream payload from that branch is forwarded back to the previous node. The protected next chain is not initialized for that line.
Pending or Visitor failures never generate Reality alerts. A failed Pending candidate remains byte-for-byte owned by destination; any response is therefore the real cover endpoint's response rather than a Reality-specific oracle.
Record Format
Authorized Reality traffic uses the profile selected from the real cover handshake:
| Negotiated cover | Visible Reality body |
|---|---|
| TLS 1.3 | Reality-AEAD(payload + encrypted inner type 0x17) + 16-byte tag; no visible nonce/prefix; body length P + 17, maximum 16401. |
| TLS 1.2 ChaCha20-Poly1305 | Reality-AEAD(payload) + 16-byte tag; no visible nonce/prefix; body length P + 16, maximum 16400. |
| TLS 1.2 AES-GCM | 8-byte explicit nonce + Reality ciphertext + 16-byte tag; maximum 16408. |
| TLS 1.2 AES-CBC/SHA-1 | 16-byte random explicit IV + block-aligned opaque body; maximum 16432. |
CBC public lengths reproduce the suite's TLS MAC and minimum-padding formula using an authenticated inner payload length and zero filler; Reality does not implement CBC encryption.
The TLS-like header uses:
| Field | Value |
|---|---|
| Content type | 0x17 application data |
| Version | 0x0303 |
| Length | Selected profile's visible prefix plus authenticated opaque body |
Authenticated alerts use BoringSSL-compatible public shapes:
| Negotiated cover | Alert header and body |
|---|---|
| TLS 1.3 | Outer 0x17, version 0x0303, 19-byte body with encrypted inner alert type 0x15. |
| TLS 1.2 AES-GCM | Outer 0x15, version 0x0303, 26-byte body with the continued 8-byte explicit-nonce policy. |
| TLS 1.2 AES-CBC/SHA-1 | Outer 0x15, version 0x0303, 48-byte body with a fresh 16-byte IV. |
| TLS 1.2 ChaCha20-Poly1305 | Outer 0x15, version 0x0303, 18-byte body. |
The encrypted semantic values are 01 00 (close_notify) and 02 14 (fatal bad_record_mac). Alerts and data share one strict sequence per direction. Associated data binds record kind and negotiated TLS version as well as the profile and visible record image. These are Reality-AEAD camouflage records, not genuine TLS closure or TLS-key alert encryption.
TLS 1.3 HANDOFF_REQUEST, HANDOFF_ACK, and HANDOFF_CONFIRM use outer and inner application-data types. Their versioned plaintext binds the expected control code and includes authenticated CSPRNG padding. The TLS body length is sampled uniformly from 22..1172 bytes (3..1153 padding bytes), the range observed across the covered BoringSSL post-handshake paths. Controls are consumed internally, share the normal directional Reality sequence, and have no JSON-configurable size. Their visible timing and length distribution remain part of the wire-camouflage review.
The visible prefix is authenticated camouflage, not the Reality AEAD nonce. The nonce is derived from a per-direction secret IV and independent Reality replay sequence. Associated data also binds the record profile ID and visible-prefix length.
For downstream TLS 1.2 GCM, auto selects sequence when observed cover nonces match protected-record sequences, otherwise counter when at least two samples increment, and otherwise random. Explicit sequence continues the tracked TLS record sequence; counter increments the last observed nonce and requires a sample; random uses fresh CSPRNG bytes. auto cannot distinguish an arbitrary fixed-prefix counter from random output with one non-sequence sample, so operators who know the cover convention may set an explicit policy.
Each direction accepts exactly its next sequence number. Duplicate, deleted, reordered, reflected, and cross-connection records therefore fail. Authentication failure is fatal after authorization, and counters close before UINT64_MAX can be used.
Buffer And Lifecycle Notes
RealityServerhas per-line state for bounded hello parsing, the TLS binding, session ID, directional keys/IVs and counters, current mode, and branch-transition state.- It does not create or destroy the normal line. It destroys only its own line state before propagating finish callbacks.
- The node advertises
21bytes of required left padding:5bytes for the header and the worst-case16-byte CBC explicit IV. - Empty downstream payload buffers from the protected chain are recycled without sending a Reality record.
- Application plaintext fragments are automatically capped at the native TLS limit of
16384bytes. Larger WaterWall payload callbacks are split immediately;32768bytes becomes exactly two full records, with no cross-callback buffering. - During the authorized transition, downstream callbacks caused by closing the visitor branch are suppressed so they do not reflect back into the already-switching side.
- Final alerts are transferred before
Finish; terminal guards suppress synchronous Pause/Resume/Payload/Est reflection and abort further teardown if the line dies during the send or first finish.
Security Model And Limitations
The reviewed camouflage threat model is a non-terminating on-path observer. It may capture and reassemble the full bidirectional stream; inspect TLS record types, versions, lengths, directions, ordering, timing, visible nonces/IVs, and close behavior; manipulate bytes; and initiate probes while knowing the implementation. It is assumed not to know the Reality password or genuine TLS traffic secrets, not to terminate TLS, and not to control or collude with the configured cover destination. It also cannot alter a completed TLS handshake without failing TLS Finished verification.
Per-session Reality material depends on the fresh client and server randoms from the genuine TLS handshake. There is no independent Reality server challenge dedicated to session freshness. Cross-connection replay protection therefore assumes that the observer cannot predict or control both handshake randoms. A malicious or colluding cover destination that deliberately helps reproduce a previously observed handshake binding is outside this model; defending that stronger model would require an independently authenticated server challenge.
Reality camouflage reproduces selected public TLS record properties, including suite-specific record shapes, lengths, explicit nonces/IVs, controls, alerts, and close behavior. After takeover, however, the records contain Reality-AEAD ciphertext rather than genuine ciphertext under the cover connection's TLS traffic keys. Reality does not claim to survive TLS termination or to reproduce every Chrome timing distribution, write size, burst pattern, or higher-level application semantic. A TLS-terminating observer can test the real traffic keys against later records and is outside the reviewed camouflage boundary.
Operational Notes
destinationis for ordinary visitor traffic. It should normally be a branch that can serve or proxy the same visitor domain used byRealityClient.settings.sni.nextis for authorized WaterWall traffic only.password,salt,kdf-iterations, andalgorithmmust matchRealityClient.- The authenticated TLS 1.3 handoff replaces the earlier unpublished main-handshake cutover. Upgrade both peers together; TLS 1.2 layouts and counters remain unchanged, the secure path never falls back to an older layout or v1, and obsolete
max-frame-sizeremains a startup error. - The password-derived value is a root key for per-TLS-session directional material. Password rotation invalidates future sessions, and no server-side replay database is required.
- This node is a stream tunnel, not a packet tunnel.
Node Metadata
| Property | Value |
|---|---|
| Position | Middle stream tunnel with visitor branch |
Requires next | Yes |
Requires destination | Yes |
| Previous layer | Anything |
| Next layer | Anything |
| Per-line state | Yes |
| Creates lines | No |
| Destroys lines | No |
| Required left padding | 21 |