10 KiB
协议说明
客户端
认证
本协议基于 TLS 协议,TLS 握手完成后客户端立即发送认证请求:
| sha256(password) | padding0 length | padding0 |
|---|---|---|
| 32 Bytes | Big-Endian uint16 | 可变长度 |
认证成功服务器会进入会话循环,认证失败服务器会关闭连接(或 fallback 到 http 服务)。
会话
认证完成后,客户端&服务器在 TLS 协议之上开启会话层事件循环,会话层 frame 格式如下:
| command | streamId | data length | data |
|---|---|---|---|
| uint8 | Big-Endian uint32 | Big-Endian uint16 | 可变长度 |
客户端每次开启新会话必须立即发送 cmdSettings。
command
// Since version 1
cmdWaste = 0 // Paddings
cmdSYN = 1 // stream open
cmdPSH = 2 // data push
cmdFIN = 3 // stream close, a.k.a EOF mark
cmdSettings = 4 // Settings(客户端向服务器发送)
cmdAlert = 5 // Alert(服务器向客户端发送)
cmdUpdatePaddingScheme = 6 // update padding scheme(服务器向客户端发送)
// Since version 2
cmdSYNACK = 7 // Server reports to the client that the stream has been opened
cmdHeartRequest = 8 // Keep alive command
cmdHeartResponse = 9 // Keep alive command
cmdServerSettings = 10 // Settings (Server send to client)
对于不同类型的 command,除非下方说明有提到,否则该类型 command 不应也不能携带 data。
cmdWaste
任意一方收到 cmdWaste 后都应将其 data 完整读出并无声丢弃。
cmdHeartRequest
任意一方收到 cmdHeartRequest 后,应向对方发送 cmdHeartResponse
cmdSYN
客户端通知服务器打开一条新的 Stream。客户端应为每个 Stream 生成在 Session 内单调递增的 streamId。
cmdSYNACK
若客户端上报的版本 v >= 2,服务器收到 cmdSYN 后应在代理出站连接 TCP 握手完成后,发送带有对应 streamId 的 cmdSYNACK 回包。
如果您的服务器软件架构不支持回报出站连接状态,也可以在收到 cmdSYN 后直接发送 cmdSYNACK。
cmdSYNACK 若不带有 data,则表示代理 stream 握手成功。若带有 data,则 data 代表错误信息。客户端收到错误信息后必须关闭对应 stream。
cmdPSH
本命令的 data 承载 Stream 的传输数据。
cmdFIN
通知对方关闭对应 streamId 的 Stream。
cmdSettings
其 data 目前为:
v=2
client=anytls/0.0.1
padding-md5=(md5)
采用 UTF-8 编码,key 与 value 之间用
=连接,两者均为 string 类型。不同项目之间用\n分割。
v是客户端实现的协议版本号 (目前为2)client是客户端软件名称与版本号(第三方实现请填写真实的软件名称与版本号,伪装没有任何意义)padding-md5是客户端当前paddingScheme的 md5 (小写 hex 编码)
cmdServerSettings
其 data 目前为:
v=2
v是服务器实现的协议版本号 (目前为2)
cmdAlert
其 data 为服务器发送的警告文本信息,客户端需要将其读出并打印到日志,然后双方关闭会话。
cmdUpdatePaddingScheme
当服务器收到客户端的 padding-md5 不同于服务器时,会发送 cmdUpdatePaddingScheme 向客户端请求更新,其 data 目前格式如下:
Default Padding Schme
stop=8
0=30-30
1=100-400
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
3=9-9,500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000
- 客户端应在 Client 对象存储
paddingScheme,即服务器下发的paddingScheme只作用于连接到该服务器的 Client - 客户端第一次会话连接使用默认的
paddingScheme,如果收到cmdUpdatePaddingScheme后续新建会话则必须使用服务器下发的paddingScheme
有了这个设计,当默认 paddingScheme 产生的流量特征被 GFW 列入黑名单时,理论上每个客户端启动时只需要发送少量数据(理想情况下只有第一个连接的 pkt 0~2),在收到服务器的首个
cmdUpdatePaddingScheme后就能更新为服务器指定的特征。因此,理论上可以被 GFW 捕获的已知特征的连接的比例将非常低。
paddingScheme 具体含义与实现
stop
stop 表示在第几个包停止处理 padding 比如: stop=8 代表只处理第 0~7 个包。
padding0
padding0 也就是第 0 个包,处于认证部分,不支持分包。客户端应将该长度的 padding 与 sha265(password) 一并发送。
提示:认证部分的开销为 34 字节。
padding1 开始
- padding1 开始处于会话部分,采用策略分包和/或填充:如果分包发送完之后,用户数据仍然有剩余,则直接发送剩余数据。如果分包发送完之前,用户数据已发送完毕,则发送
cmdWaste携带数据(建议用 0)做填充。 - 策略示例:上述 paddingScheme 将包
2将分成 5 个尺寸在 400-500 / 500-1000 的包发送(这里的尺寸指 TLS PlainText 的尺寸,不计算 TLS 加密等开销)。 - 策略中的
c是检查符号,含义:若上一个分包发送完毕后,用户数据已无剩余,则直接对本次 Write TLS 返回,不再发送后续的填充包。 - 包计数器以 Write TLS 的次数为准,包
1应该包括:cmdSettings和首个 Stream 的cmdSYN + cmdPSH(代理目标地址) - 包
2应该是代理自用户的第一个数据包,比如 TLS ClientHello。 - 假如在 stop 之前的某个包的发送策略没有被 PaddingScheme 定义,那么直接发送该包。
参考处理逻辑在 func (s *Session) writeConn()
复用
客户端必须实现会话层复用功能。 总体架构为:
TCP Proxy -> Stream -> Session -> TLS -> TCP
复用的具体逻辑:
创建新的会话层之前必须检查是否有“空闲”的会话,如果有则取 Seq 最大的会话,在该 Session 上开启 Stream 承载用户代理请求。
如果没有空闲的会话,则创建新的会话,Session 的序号 Seq 在一个 Client 内应单调递增。
Stream 在代理中继完毕被关闭时,如果对应 Session 的事件循环未遇到错误,则将 Session 放入“空闲会话池”,并且设置 Session 的空闲起始时间为 now。
定期(如 30s)检查会话池,关闭并删除持续空闲超过一定时间(如 60s)的会话。
以上复用策略高度概括:优先复用最新的会话,优先清理最老的会话。
代理
对于 TCP,每个 Stream 打开后,客户端向服务器发送 SocksAddr 格式表示代理请求的目标地址,然后开始双向代理中继。
对于 UDP,现在使用 sing-box 的 udp-over-tcp 2 协议,相当于代理请求 TCP sp.v2.udp-over-tcp.arpa。
服务器
认证
服务器基于 TLS Server 运行,对于每个 Accpted TLS Connection 认证的方式为:
读出第一个数据包,校验认证请求(包括完整读出 padding0),如果符合,则开始会话循环。如果不符合,则直接关闭连接或 "fallback" 到任意 "合法" L7 应用。
会话
会话层格式和命令见客户端。
对于一个新 Session,如果服务器在收到客户端的 cmdSettings 之前收到 cmdSYN,必须拒绝此次会话。
服务器有权拒绝未正确实现本协议(包括但不限于 cmdUpdatePaddingScheme 和连接复用)、版本过旧(有已知问题)的客户端连接。
当服务器拒绝这类客户端时,必须发送 cmdAlert 说明原因,然后关闭 Session。
当客户端上报的版本 v >= 2,服务器收到 cmdSettings 后应立即发送 cmdServerSettings。
代理
代理中继完毕后,服务器关闭 Stream 但不要关闭 Session。
服务器可以定期清理长期无上下行的 Session。
对于目标地址为 sp.v2.udp-over-tcp.arpa 的请求,则应该使用 sing-box udp-over-tcp 协议处理。
协议参数
anytls 协议参数不包括 TLS 的参数。应该在另外的配置分区中指定 TLS 参数。
客户端
password必选,string 类型,协议认证的密码。idleSessionCheckInterval可选,time.Duration 类型,检查空闲会话的间隔时间。idleSessionTimeout可选,time.Duration 类型,在检查中,关闭空闲时间超过此时长的会话。minIdleSession可选,int 类型,在检查中,至少保留前 n 个空闲会话不关闭,即为后续代理保留一定数量的“预备会话”。
服务器
paddingScheme可选,string 类型,填充方案。
更新记录
协议版本 2
anytls-gov0.0.7+
sing-anytlsv0.0.7+ (sing-box1.12.0-alpha.21+ )
mihomoPrerelease-Alpha 2025.3.27+
本次协议更新主要是为了应对隧道连接卡住的问题,实现更好的超时处理。
仅当您的服务器和客户端都支持版本 2 时,才应该启用以下特性。否则,两端都将按照版本 1 运行。
- 可以使用 cmdSYNACK 回报服务器出站连接状态,同时检测并恢复卡住的隧道连接
- 可以使用主动心跳包 (cmdHeartRequest cmdHeartResponse) 检测并恢复卡住的隧道连接
- 服务器可以向客户端发送协商信息 (cmdServerSettings)
版本协商原理:
- v2 服务器 + v1 客户端:由于客户端发送的版本为 1,服务器直接禁用版本 2 特性。
- v1 服务器 + v2 客户端:由于客户端发送的版本为 2,服务器不认识,也不会向客户端发送 cmdServerSettings。客户端没有收到 cmdServerSettings 提示的版本,默认版本为 1,则不启用版本 2 特性。
cmdSYNACK
当隧道连接意外断开且客户端未收到 RST 时,协议版本 1 的行为在极端情况下可能会导致很长的超时(取决于系统设置)。
由于在版本 2 客户端打开 stream 时可以期待来自服务器的回复,如果长时间未收到回复,则代表可能网络出现问题,客户端可以提前关闭卡住的连接。