|
| 1 | +RFC: The Anytls Protocol Version 2 |
| 2 | +Category: Informational |
| 3 | +Date: April 2026 |
| 4 | + |
| 5 | +# The Anytls Protocol Version 2 |
| 6 | + |
| 7 | +## Abstract |
| 8 | + |
| 9 | +This document describes the Anytls protocol, a secure, multiplexed proxy protocol that operates over Transport Layer Security (TLS). The protocol is designed to provide robust obfuscation against traffic analysis through dynamic padding schemes, multiplex multiple logical streams over a single session, and protect against active probing by falling back to standard protocols. |
| 10 | + |
| 11 | +## 1. Introduction |
| 12 | + |
| 13 | +Anytls is a proxy protocol that establishes a secure session over a standard TLS connection. It consists of an initial authentication phase followed by a session layer that multiplexes multiple logical streams. To counter advanced Deep Packet Inspection (DPI) heuristics, Anytls utilizes a dynamic `paddingScheme` to alter traffic fingerprints actively. |
| 14 | + |
| 15 | +## 2. Conventions and Terminology |
| 16 | + |
| 17 | +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. |
| 18 | + |
| 19 | +- **Session**: A single established TLS connection running the Anytls session layer. |
| 20 | +- **Stream**: A multiplexed logical channel within a Session used to proxy a specific connection. |
| 21 | +- **Client**: The software initiating the Anytls connection. |
| 22 | +- **Server**: The Anytls endpoint receiving the connection and performing the proxying. |
| 23 | + |
| 24 | +## 3. Protocol Architecture |
| 25 | + |
| 26 | +The overall protocol stack is layered as follows: |
| 27 | + |
| 28 | +```text |
| 29 | ++-------------------------------------------------+ |
| 30 | +| User TCP/UDP Proxy | |
| 31 | ++-------------------------------------------------+ |
| 32 | +| Anytls Stream Layer | |
| 33 | ++-------------------------------------------------+ |
| 34 | +| Anytls Session Layer | |
| 35 | ++-------------------------------------------------+ |
| 36 | +| Transport Layer Security (TLS) | |
| 37 | ++-------------------------------------------------+ |
| 38 | +| Transmission Control Protocol (TCP) | |
| 39 | ++-------------------------------------------------+ |
| 40 | +``` |
| 41 | + |
| 42 | +## 4. Authentication Phase |
| 43 | + |
| 44 | +Immediately after the TLS handshake completes, the Client MUST send an authentication request. |
| 45 | + |
| 46 | +### 4.1. Client Authentication Request |
| 47 | + |
| 48 | +The authentication payload is structured as follows: |
| 49 | + |
| 50 | +```text |
| 51 | + 0 1 2 3 |
| 52 | + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 53 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 54 | +| | |
| 55 | ++ + |
| 56 | +| | |
| 57 | ++ + |
| 58 | +| | |
| 59 | ++ + |
| 60 | +| | |
| 61 | ++ + |
| 62 | +| | |
| 63 | ++ + |
| 64 | +| | |
| 65 | ++ + |
| 66 | +| | |
| 67 | ++ + |
| 68 | +| sha256(password) (32 Bytes) | |
| 69 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 70 | +| padding0 length | padding0 (Variable) ... | |
| 71 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 72 | +``` |
| 73 | + |
| 74 | +- **sha256(password)**: 32 bytes. The SHA-256 hash of the pre-shared protocol password. |
| 75 | +- **padding0 length**: 2 bytes (Big-Endian uint16). The length of the subsequent padding. |
| 76 | +- **padding0**: Variable length. Random padding data. |
| 77 | + |
| 78 | +*Note: The overhead for the authentication portion is 34 bytes (excluding the variable padding).* |
| 79 | + |
| 80 | +### 4.2. Server Authentication Response |
| 81 | + |
| 82 | +The Server MUST read the first packet and verify the authentication request (including fully reading `padding0`). |
| 83 | +- If authentication is successful, the Server enters the Session loop. |
| 84 | +- If authentication fails, the Server MUST immediately close the connection OR fallback to a standard HTTP/L7 service to defend against active probing. |
| 85 | + |
| 86 | +## 5. Session Layer |
| 87 | + |
| 88 | +Once authenticated, both endpoints enter a session event loop. |
| 89 | + |
| 90 | +### 5.1. Frame Format |
| 91 | + |
| 92 | +The Session layer communicates using the following frame format: |
| 93 | + |
| 94 | +```text |
| 95 | + 0 1 2 3 |
| 96 | + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 97 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 98 | +| command | streamId | |
| 99 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 100 | +| | data length | | |
| 101 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 102 | +| data (Variable) | |
| 103 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 104 | +``` |
| 105 | + |
| 106 | +- **command**: 1 byte (uint8). Identifies the frame type. |
| 107 | +- **streamId**: 4 bytes (Big-Endian uint32). Identifies the logical stream. |
| 108 | +- **data length**: 2 bytes (Big-Endian uint16). The length of the payload. |
| 109 | +- **data**: Variable length payload. |
| 110 | + |
| 111 | +### 5.2. Commands |
| 112 | + |
| 113 | +The following commands are defined. Unless explicitly specified below, commands MUST NOT carry a data payload. |
| 114 | + |
| 115 | +| Command Name | Value | Introduced | Direction | Description | |
| 116 | +|---|---|---|---|---| |
| 117 | +| `cmdWaste` | 0 | v1 | Both | Padding. Data MUST be read and silently discarded. | |
| 118 | +| `cmdSYN` | 1 | v1 | Client -> Server | Open a new Stream. | |
| 119 | +| `cmdPSH` | 2 | v1 | Both | Push data to a Stream. | |
| 120 | +| `cmdFIN` | 3 | v1 | Both | Close a Stream (EOF). | |
| 121 | +| `cmdSettings` | 4 | v1 | Client -> Server | Client Settings negotiation. | |
| 122 | +| `cmdAlert` | 5 | v1 | Server -> Client | Fatal error alert from Server. | |
| 123 | +| `cmdUpdatePaddingScheme` | 6 | v1 | Server -> Client | Dynamic padding scheme update. | |
| 124 | +| `cmdSYNACK` | 7 | v2 | Server -> Client | Stream open acknowledgment. | |
| 125 | +| `cmdHeartRequest` | 8 | v2 | Both | Keep-alive request. | |
| 126 | +| `cmdHeartResponse` | 9 | v2 | Both | Keep-alive response. | |
| 127 | +| `cmdServerSettings` | 10 | v2 | Server -> Client | Server Settings negotiation. | |
| 128 | + |
| 129 | +#### 5.2.1. cmdSettings (4) |
| 130 | +The Client MUST send a `cmdSettings` frame immediately upon opening a new Session. If a Server receives a `cmdSYN` before `cmdSettings`, it MUST reject the Session. |
| 131 | + |
| 132 | +The payload is a newline (`\n`) separated list of key-value pairs separated by `=`. Encoded in UTF-8. |
| 133 | +Example: |
| 134 | +```text |
| 135 | +v=2 |
| 136 | +client=anytls/0.0.1 |
| 137 | +padding-md5=(lowercase hex encoded md5 of current paddingScheme) |
| 138 | +``` |
| 139 | + |
| 140 | +#### 5.2.2. cmdServerSettings (10) |
| 141 | +If the Client reports version `v >= 2`, the Server MUST reply with `cmdServerSettings` immediately after receiving `cmdSettings`. |
| 142 | +Example: |
| 143 | +```text |
| 144 | +v=2 |
| 145 | +``` |
| 146 | + |
| 147 | +#### 5.2.3. cmdAlert (5) |
| 148 | +The payload contains a warning text string sent by the Server. The Client MUST read and log this message, after which both endpoints MUST close the Session. A Server MAY send this to reject outdated or non-compliant Clients. |
| 149 | + |
| 150 | +#### 5.2.4. cmdUpdatePaddingScheme (6) |
| 151 | +If the Server detects that the Client's `padding-md5` (from `cmdSettings`) differs from the Server's current scheme, the Server MUST send this command. The payload format is described in Section 6. |
| 152 | + |
| 153 | +#### 5.2.5. cmdHeartRequest (8) and cmdHeartResponse (9) |
| 154 | +When an endpoint receives a `cmdHeartRequest`, it MUST respond with a `cmdHeartResponse`. These are used to detect and recover from stuck tunnels. |
| 155 | + |
| 156 | +### 5.3. Stream Lifecycle Commands |
| 157 | + |
| 158 | +#### 5.3.1. cmdSYN (1) |
| 159 | +Notifies the Server to open a new Stream. The Client MUST generate a monotonically increasing `streamId` within the Session. |
| 160 | + |
| 161 | +#### 5.3.2. cmdSYNACK (7) |
| 162 | +For Client version `v >= 2`, the Server SHOULD send a `cmdSYNACK` with the corresponding `streamId` after the outbound proxy TCP handshake completes. |
| 163 | +- A payload-less `cmdSYNACK` indicates a successful proxy stream connection. |
| 164 | +- If data is present, it represents an error message. The Client MUST close the corresponding Stream upon receiving an error payload. |
| 165 | + |
| 166 | +#### 5.3.3. cmdPSH (2) |
| 167 | +The data payload carries the actual proxied traffic for the Stream. |
| 168 | + |
| 169 | +#### 5.3.4. cmdFIN (3) |
| 170 | +Notifies the peer to close the specified Stream. |
| 171 | +- If the Session is healthy, the receiving endpoint closes the local Stream but DOES NOT need to reply with its own `cmdFIN`. |
| 172 | +- If the Session is already closing, `cmdFIN` does not need to be sent. |
| 173 | + |
| 174 | +## 6. Dynamic Padding Scheme |
| 175 | + |
| 176 | +The Padding Scheme dictates how packets are fragmented and padded to obfuscate traffic patterns. |
| 177 | + |
| 178 | +### 6.1. Padding Scheme Format |
| 179 | + |
| 180 | +The scheme is sent as a newline-separated string. Example: |
| 181 | +```text |
| 182 | +stop=8 |
| 183 | +0=30-30 |
| 184 | +1=100-400 |
| 185 | +2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 |
| 186 | +3=9-9,500-1000 |
| 187 | +``` |
| 188 | + |
| 189 | +### 6.2. Scheme Directives |
| 190 | + |
| 191 | +- `stop`: E.g., `stop=8` dictates that only the first 8 packets (indexes 0 to 7) are subjected to the padding scheme. |
| 192 | +- `0=X-Y`: Rules for the 0th packet (`padding0` during Authentication). This cannot be fragmented. The Client sends padding between X and Y bytes. |
| 193 | +- `1` and above: Rules for Session phase packets. |
| 194 | + |
| 195 | +**Packet Counting:** |
| 196 | +The packet index corresponds to the number of times `Write()` is called on the underlying TLS connection. |
| 197 | +- **Packet 1** typically includes: `cmdSettings` + first Stream's `cmdSYN` + `cmdPSH` (containing proxy target address). |
| 198 | +- **Packet 2** typically contains the first chunk of user proxy data (e.g., TLS ClientHello of the proxied connection). |
| 199 | + |
| 200 | +**Fragmentation and Padding Logic:** |
| 201 | +If a packet is ruled by `A-B,C-D`: |
| 202 | +1. The user data is fragmented. The first chunk's size is randomly chosen between A and B. The second between C and D, etc. (Size refers to TLS PlainText length, excluding TLS encryption overhead). |
| 203 | +2. If user data remains after all specified fragments, the remainder is sent natively. |
| 204 | +3. If user data is exhausted before all fragments are fulfilled, the endpoint MUST send `cmdWaste` filled with padding (preferably 0s) to fulfill the size requirement. |
| 205 | +4. **Check Symbol (`c`)**: If a `,c,` separator is present and the user data is exhausted, the implementation MUST return from the Write operation immediately and MUST NOT send subsequent padding packets defined after the `c`. |
| 206 | + |
| 207 | +### 6.3. Scheme Lifecycle |
| 208 | +- A Client MUST store the `paddingScheme` specific to the Server it connects to. |
| 209 | +- A Client MUST use the default `paddingScheme` on its first connection. |
| 210 | +- Upon receiving a `cmdUpdatePaddingScheme`, the Client MUST use the newly provided scheme for all subsequent Sessions created with that Server. This ensures that any discovered fingerprints only affect a minimal subset of initial connections before the scheme is dynamically rolled. |
| 211 | + |
| 212 | +## 7. Connection Multiplexing |
| 213 | + |
| 214 | +Clients MUST implement session multiplexing using a connection pool. The architecture is: |
| 215 | +`TCP Proxy -> Stream -> Session -> TLS -> TCP` |
| 216 | + |
| 217 | +### 7.1. Multiplexing Strategy |
| 218 | +- Before creating a new Session, the Client MUST check the pool for "idle" Sessions. |
| 219 | +- If available, the Client MUST pick the Session with the highest sequence number (`Seq`) to open the new Stream. |
| 220 | +- If no idle Sessions exist, a new Session is created. The `Seq` MUST monotonically increase within the Client instance. |
| 221 | +- When a Stream closes normally (and the Session has no errors), the Session is returned to the "idle session pool" and its idle start time is updated to `now`. |
| 222 | +- The Client SHOULD periodically (e.g., every 30s) reap Sessions that have been idle for a designated duration (e.g., 60s). |
| 223 | +- Servers MAY also periodically reap Sessions that lack uplink/downlink activity for extended periods. |
| 224 | + |
| 225 | +## 8. Proxy Protocol Interaction |
| 226 | + |
| 227 | +### 8.1. TCP Proxy |
| 228 | +After opening a Stream (`cmdSYN`), the Client MUST send the target destination address in [RFC 1928 (SOCKS5) Address](https://tools.ietf.org/html/rfc1928#section-5) format inside a `cmdPSH`. Following this, bidirectional proxying commences. |
| 229 | + |
| 230 | +### 8.2. UDP Proxy |
| 231 | +UDP proxying relies on the `sing-box udp-over-tcp v2` protocol. The Client acts as if it is making a TCP proxy request to the special domain `sp.v2.udp-over-tcp.arpa`. |
| 232 | + |
| 233 | +## 9. Protocol Parameters |
| 234 | + |
| 235 | +The Anytls protocol assumes TLS configuration is handled externally. Specific Anytls parameters include: |
| 236 | + |
| 237 | +### 9.1. Client Parameters |
| 238 | +- `password` (String, REQUIRED): Protocol authentication password. |
| 239 | +- `idleSessionCheckInterval` (Duration, OPTIONAL): Interval for checking the idle pool. |
| 240 | +- `idleSessionTimeout` (Duration, OPTIONAL): Max duration a Session can remain idle before closure. |
| 241 | +- `minIdleSession` (Integer, OPTIONAL): The number of fresh idle sessions to maintain as warm reserves. |
| 242 | + |
| 243 | +### 9.2. Server Parameters |
| 244 | +- `paddingScheme` (String, OPTIONAL): The master padding scheme to enforce on Clients. |
| 245 | + |
| 246 | +## 10. Version History |
| 247 | + |
| 248 | +- **v1**: Initial implementation. |
| 249 | +- **v2 (v0.0.8 - April 2025)**: Added `cmdSYNACK` to report outbound connection state and handle stuck tunnels. Added `cmdHeartRequest`/`cmdHeartResponse` keep-alives. Added `cmdServerSettings` for negotiation. |
| 250 | +- **v2 (v0.0.10 - September 2025)**: Clarified `cmdFIN` semantics regarding Session and Stream closure. |
0 commit comments