参考资料
- https://v2.hysteria.network/zh/docs/developers/Protocol/
Hysteria 2 协议规范
Hysteria 是基于 QUIC 的 TCP 与 UDP 代理,旨在提供速度、安全性与抗审查能力。本文档描述 Hysteria 自 2.0.0 版本起使用的协议,内部有时称为“v4“协议。下文将其称为“该协议“或“Hysteria 协议“。
规范性语言
本文档中的关键词“MUST“、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL“的含义应按 RFC 2119 解释:https://tools.ietf.org/html/rfc2119
底层协议与报文格式
Hysteria 协议 MUST 构建在标准 QUIC 传输协议(RFC 9000)之上,并使用不可靠数据报扩展(RFC 9221)。
所有多字节数字均使用大端字节序。
所有变长整数(“varints”)按 QUIC(RFC 9000)定义进行编码/解码。
认证与 HTTP/3 伪装
Hysteria 协议的一个关键特性是:对于没有正确认证凭据的第三方(无论是中间人还是主动探测者),Hysteria 代理服务器的行为就像标准的 HTTP/3 Web 服务器。此外,客户端与服务器之间的加密流量看起来与正常 HTTP/3 流量无法区分。
因此,Hysteria 服务器 MUST 实现一个 HTTP/3 服务器(RFC 9114),并像标准 Web 服务器一样处理 HTTP 请求。为防止主动探测者发现 Hysteria 服务器的常见响应模式,实现方 SHOULD 建议用户要么托管真实内容,要么将其设置为其他站点的反向代理。
真正的 Hysteria 客户端在连接后 MUST 向服务器发送以下 HTTP/3 请求:
:method: POST
:path: /auth
:host: hysteria
Hysteria-Auth: [string]
Hysteria-CC-RX: [uint]
Hysteria-Padding: [string]
Hysteria-Auth:认证凭据。
Hysteria-CC-RX:客户端最大接收速率(字节/秒)。值为 0 表示未知。
Hysteria-Padding:可变长度的随机填充字符串。
Hysteria 服务器 MUST 识别该特殊请求,并且不会尝试提供内容或转发到上游站点,而是 MUST 使用所提供的信息对客户端进行认证。若认证成功,服务器 MUST 发送以下响应(HTTP 状态码 233):
:status: 233 HyOK
Hysteria-UDP: [true/false]
Hysteria-CC-RX: [uint/"auto"]
Hysteria-Padding: [string]
Hysteria-UDP:服务器是否支持 UDP 中继。
Hysteria-CC-RX:服务器最大接收速率(字节/秒)。值为 0 表示无限制;“auto” 表示服务器拒绝提供该值,并要求客户端使用拥塞控制自行确定速率。
Hysteria-Padding:可变长度的随机填充字符串。
关于如何使用 Hysteria-CC-RX 的取值,详见“拥塞控制”章节。
Hysteria-Padding 为可选项,仅用于混淆请求/响应模式,双方 SHOULD 忽略它。
如果认证失败,服务器 MUST 要么表现得像不理解该请求的标准 Web 服务器,要么在其作为反向代理时,将请求转发到上游站点并将响应返回给客户端。
客户端 MUST 检查状态码以判断认证是否成功。若状态码不是 233,客户端 MUST 认为认证失败并断开与服务器的连接。
客户端通过认证之后(且仅在之后),服务器 MUST 将该 QUIC 连接视为 Hysteria 代理连接,并 MUST 按下一节所述开始处理来自客户端的代理请求。
代理请求
TCP
对于每个 TCP 连接,客户端 MUST 创建一个新的 QUIC 双向流,并发送以下 TCPRequest 消息:
[varint] 0x401 (TCPRequest ID)
[varint] Address length
[bytes] Address string (host:port)
[varint] Padding length
[bytes] Random padding
服务器 MUST 以 TCPResponse 消息回应:
[uint8] Status (0x00 = OK, 0x01 = Error)
[varint] Message length
[bytes] Message string
[varint] Padding length
[bytes] Random padding
如果状态为 OK,服务器 MUST 随后在客户端与指定的 TCP 地址之间转发数据,直到任一方关闭连接。如果状态为 Error,服务器 MUST 关闭该 QUIC 流。
UDP
UDP 数据包 MUST 按以下 UDPMessage 格式封装,并通过 QUIC 的不可靠数据报发送(客户端到服务器和服务器到客户端均如此):
[uint32] Session ID
[uint16] Packet ID
[uint8] Fragment ID
[uint8] Fragment count
[varint] Address length
[bytes] Address string (host:port)
[bytes] Payload
客户端 MUST 为每个 UDP 会话使用唯一的 Session ID。服务器 SHOULD 为每个 Session ID 分配唯一的 UDP 端口,除非它有其他机制区分来自不同会话的数据包(例如对称 NAT、不同的出站 IP 地址等)。
该协议未提供显式关闭 UDP 会话的方法。客户端可以无限期地保留并复用 Session ID,但服务器 SHOULD 在一段时间不活动或基于其他条件后释放并重新分配与该 Session ID 关联的端口。如果客户端向服务器已不再识别的 Session ID 发送 UDP 数据包,服务器 MUST 将其视为新会话并分配新的端口。
如果服务器不支持 UDP 中继,它 SHOULD 静默丢弃从客户端接收到的所有 UDP 消息。
分片
由于 QUIC 不可靠数据报通道的限制,任何超过 QUIC 最大数据报大小的 UDP 数据包 MUST 要么分片,要么被丢弃。
对于分片的数据包,每个分片 MUST 携带相同的唯一 Packet ID。Fragment ID 从 0 开始,表示在总 Fragment Count 中的索引。服务器与客户端 MUST 等待该分片数据包的所有分片到齐后再处理;如果丢失一个或多个分片,整个数据包 MUST 被丢弃。
对于未分片的数据包,Fragment Count MUST 设为 1。在这种情况下,Packet ID 和 Fragment ID 的值无关紧要。
拥塞控制
Hysteria 的一项独特能力是允许在客户端设置 tx/rx(上传/下载)速率。认证过程中,客户端通过 Hysteria-CC-RX 头向服务器发送其 rx 速率。服务器可据此确定向客户端的发送速率,并通过同一头返回其 rx 速率,反之亦然。
有三种特殊情况:
- 如果客户端发送 0,表示它不知道自身的 rx 速率。服务器 MUST 使用拥塞控制算法(如 BBR、Cubic)来调整其发送速率。
- 如果服务器返回 0,表示没有带宽限制。客户端 MAY 以任意速率发送。
- 如果服务器返回 “auto”,表示它选择不指定速率。客户端 MUST 使用拥塞控制算法来调整其发送速率。
“Salamander” 混淆
Hysteria 协议支持一个可选的混淆层,代号为 “Salamander”。
“Salamander” 将所有 QUIC 数据包封装为以下格式:
[8 bytes] Salt
[bytes] Payload
对于每个 QUIC 数据包,混淆器 MUST 计算将随机生成的 8 字节盐值追加到用户提供的预共享密钥后的 BLAKE2b-256 哈希。
hash = BLAKE2b-256(key + salt)
随后使用该哈希按以下算法对负载进行混淆:
for i in range(0, len(payload)):
payload[i] ^= hash[i % 32]
去混淆器 MUST 使用相同的算法计算带盐哈希并还原负载。任何无效的数据包 MUST 被丢弃。