一、总体架构概览
graph TD
subgraph Xray-core
A[xhttp/splithttp 传输层] -->|HTTP/3 模式| B[quic-go/http3.Transport]
A -->|HTTP/2 模式| C[golang.org/x/net/http2.Transport]
A -->|HTTP/1.1 模式| D[net/http.Transport]
B --> E[quic.DialEarly → QUIC连接]
F[hysteria 传输层] --> G[http3.Transport]
G --> H[quic.DialEarly → QUIC连接]
H -->|TCP流代理| I[QUIC Stream]
H -->|UDP代理| J[QUIC Datagram]
end
subgraph hysteria standalone
K[client.go connect] --> L[quic.DialEarly]
L --> M[HTTP POST 认证握手]
M -->|TCP| N[conn.OpenStream + WriteTCPRequest]
M -->|UDP| O[udpSessionManager + SendDatagram]
P[server.go NewServer] --> Q[quic.Listen]
Q --> R[h3sHandler.ServeHTTP 认证]
R -->|TCP| S[ProxyStreamHijacker → handleTCPRequest]
R -->|UDP| T[udpSessionManager.Run]
end
二、Xray-core:xhttp (splithttp) 的 H3/QUIC 传输
文件路径
/Xray-core/transport/internet/splithttp/
2.1 HTTP 版本决策逻辑
文件: dialer.go:83-100
func decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {
if realityConfig != nil { return "2" } // Reality 强制 h2
if tlsConfig == nil { return "1.1" } // 无 TLS → h1
if len(tlsConfig.NextProtocol) != 1 { return "2" }
if tlsConfig.NextProtocol[0] == "h3" { return "3" } // ALPN h3 → HTTP/3
return "2"
}
触发 H3 的条件:TLS NextProtocol 列表中只有一个元素 "h3"(ALPN 协商)。
2.2 客户端 H3/QUIC 连接建立
关键代码路径(createHTTPClient 函数,当 httpVersion == "3" 时):
1. dest.Network = net.Network_UDP // 切换到 UDP 网络
2. 读取 QuicParams 配置(窗口大小、超时、MTU 等)
3. 构建 quic.Config{
InitialStreamReceiveWindow, MaxStreamReceiveWindow,
InitialConnectionReceiveWindow, MaxConnectionReceiveWindow,
MaxIdleTimeout, KeepAlivePeriod, MaxIncomingStreams,
DisablePathMTUDiscovery
}
4. 构建 http3.Transport{
QUICConfig: quicConfig,
TLSClientConfig: gotlsConfig,
Dial: func(...) (*quic.Conn, error) {
// 支持 UdpHop(端口跳变)
// 支持 UdpmaskManager(UDP 流量混淆)
quicConn = quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
// 设置拥塞控制
switch quicParams.Congestion {
case "force-brutal": congestion.UseBrutal(quicConn, ...)
case "reno": // 默认 QUIC NewReno
default: congestion.UseBBR(quicConn, ...)
}
return quicConn, nil
}
}
5. 返回 DefaultDialerClient{client: &http.Client{Transport: transport}}
UdpHop 支持 (端口随机跳变):
当 quicParams.UdpHop.Ports 非空时,随机选一个初始端口,并创建 udphop.UDPHopPacketConn 包装底层 UDP 连接,定期自动切换端口。
2.3 数据上传流程(packet-up 模式)
conn.Write(data)
↓
uploadWriter.Write() // 将数据切分为 ≤ maxUploadSize 的块写入管道
↓
pipe.Writer.WriteMultiBuffer()
↓
[后台 goroutine 读取管道]
uploadPipeReader.ReadMultiBuffer()
↓
buf.SplitSize(remainder, maxUploadSize) // 按大小切分
↓
httpClient.PostPacket(ctx, url, sessionId, seqStr, chunk)
// HTTP POST /path/{sessionId}/{seq}
// body = 数据块
每次 POST 携带序号 seq,服务端按序重组。最小发送间隔 scMinPostsIntervalMs 可配置。
2.4 数据下载流程(stream-down 模式)
httpClient.OpenStream(ctx, url, sessionId, nil, false)
// HTTP GET /path/{sessionId}
// 响应为 SSE (text/event-stream),长连接持续读取
↓
conn.reader = HTTP Response Body (流式读取)
2.5 服务端 H3/QUIC 监听
文件: hub.go:449-599
ListenXH()
↓
isH3 = (TLS NextProtos == ["h3"])
↓ 是H3
ListenSystemPacket(UDP地址) // 监听 UDP 端口
↓ 可选
UdpmaskManager.WrapPacketConnServer() // UDP 混淆包装
↓
quic.ListenEarly(Conn, tlsConfig, quicConfig) // QUIC 早期侦听
↓
http3.Server{Handler: requestHandler}
↓ [goroutine 循环]
h3listener.Accept() // 接受 QUIC 连接
↓
congestion 设置(BBR/Brutal/Reno)
↓
h3server.ServeQUICConn(conn) // 在此连接上跑 HTTP/3
2.6 服务端请求处理
文件: hub.go:94-410
requestHandler.ServeHTTP()
├─ HTTP GET (无seq) → stream-down / stream-one
│ ├─ 设置 X-Accel-Buffering: no, Cache-Control: no-store
│ ├─ Content-Type: text/event-stream (SSE)
│ └─ 创建 splitConn,addConn() 注入 Xray 连接池
│
└─ HTTP POST / GET(有seq) → 数据上传 (packet-up / stream-up)
├─ 读取 sessionId 和 seq
├─ 解析 payload(支持 header/cookie/body 三种位置)
└─ uploadQueue.Push(Packet{Payload, Seq})
↓
splitConn.reader = uploadQueue // 按序读取数据
HTTP/3 特殊处理 (hub.go:177-182):
if request.ProtoMajor == 3 {
remoteAddr = &net.UDPAddr{...} // H3 连接的远端地址用 UDP
}
2.7 xhttp H3 模式的传输模式对比
| 模式 | 上行 | 下行 | 适用场景 |
|---|---|---|---|
packet-up |
多个 POST,带序号 | GET 长连接 | 默认模式,任意 CDN |
stream-up |
单次 POST,无限流 | GET 长连接 | 支持 HTTP 流的场景 |
stream-one |
GET body 双向 | GET body | Reality 场景 |
stream-down |
独立 POST 上传通道 | 独立服务器下载通道 | 上下行分离 |
三、Xray-core:hysteria 传输层
文件路径
/Xray-core/transport/internet/hysteria/
3.1 客户端连接建立(dialer.go)
关键路径:
Dial(ctx, dest, streamSettings)
↓
client.dial()
├─ internet.DialSystem() → raw UDP 连接
├─ [可选] udphop.NewUDPHopPacketConn() // 端口跳变
├─ [可选] udpmaskManager.WrapPacketConnClient() // UDP 混淆
├─ quic.Config{ EnableDatagrams: true, MaxDatagramFrameSize, DisablePathManager: true }
├─ http3.Transport{Dial: quic.DialEarly}
├─ HTTP POST 认证请求:
│ Header: Hy-Auth:
<token>
│ Hy-CCRx: <brutal_down 速率>
│ Hy-Padding: <随机填充>
├─ 检查响应: 233 = auth OK
├─ 解析服务端 Hy-CCRx 响应(服务端 Tx 速率)
└─ 设置拥塞控制:
brutal: UseBrutal(quicConn, min(clientUp, serverDown))
bbr: UseBBR(quicConn, profile)
reno: 内置 NewReno(quic-go 默认)
TCP 代理流:
c.tcp()
→ conn.OpenStream() // 打开新 QUIC Stream
→ 封装为 interConn{ stream } // 注入 Xray 连接处理
UDP 代理(通过 QUIC Datagram):
c.udp()
→ udpSessionManagerClient.udp() // 分配 udpConn,ID=uint32
→ InterUdpConn.Write()
→ quic.Conn.SendDatagram([4字节ID | 数据])
// 收包:
udpSessionManagerClient.run()
→ conn.ReceiveDatagram()
→ 解析前4字节 ID → feed() → 对应 udpConn.ch <-
3.2 服务端监听(hub.go)
Listen()
├─ ListenSystemPacket() → UDP PacketConn
├─ [可选] UdpmaskManager 包装
├─ quic.Config{ EnableDatagrams, MaxDatagramFrameSize, DisablePathManager }
├─ quic.Listen(pktConn, tlsConfig, quicConfig)
└─ keepAccepting() → goroutine
└─ listener.Accept() → handleClient(conn)
└─ httpHandler + http3.Server.ServeQUICConn(conn)
认证流程:
httpHandler.ServeHTTP() [POST 到 URLHost/URLPath]
├─ 验证 Hy-Auth token(validator 或 config.Auth)
├─ 读取客户端 Hy-CCRx(客户端 Rx 速率)
├─ 设置拥塞控制
└─ [UDP 启用时] 启动 udpSessionManagerServer.run()
TCP 流分发:
httpHandler.StreamDispatcher() [FrameTypeTCPRequest = 0x401]
→ 读取 QUIC 流帧
→ addConn(interConn{stream}) // 注入 Xray 连接处理
四、hysteria 独立项目(core/)
文件路径
/hysteria/core/
4.1 协议设计(internal/protocol/)
TCP 请求格式
0x401 (QUIC varint) ← 帧类型标识
AddrLen (QUIC varint)
Address (raw bytes)
PaddingLen (QUIC varint)
Padding (随机填充,防流量分析)
UDP 消息格式(通过 QUIC Datagram)
SessionID uint32 BE ← 标识 UDP 会话
PacketID uint16 BE ← 分片序号
FragID uint8 ← 当前分片索引
FragCount uint8 ← 总分片数
AddrLen QUIC varint
Address bytes
Data...
最大 Datagram 帧大小:1200 字节(MaxDatagramFrameSize)
认证协议(通过 HTTP/3 header)
- 客户端发
Hysteria-Auth,Hysteria-CC-RX(宣告自己的最大接收速率) - 服务端回
Hysteria-UDP,Hysteria-CC-RX(宣告服务端 TX 速率,或返回 "auto")
4.2 客户端完整流程
NewClient(config)
└─ clientImpl.connect()
├─ config.ConnFactory.New(ServerAddr) // 创建 UDP PacketConn
├─ quic.Config{ EnableDatagrams, MaxDatagramFrameSize, DisablePathManager }
├─ http3.Transport{ Dial: quic.DialEarly(pktConn, ServerAddr) }
├─ HTTP POST 认证
├─ 解析 authResp:
│ RxAuto=true → UseConfigured(conn, congType, bbrProfile)
│ RxAuto=false → actualTx=min(serverRx, clientTx) → UseBrutal(conn, actualTx)
└─ 若 UDPEnabled → newUDPSessionManager(&udpIOImpl{conn})
client.TCP(addr)
└─ openStream() → QStream
└─ WriteTCPRequest(stream, addr) // 写入 TCP 请求帧
└─ [FastOpen=false] ReadTCPResponse(stream) // 等响应
└─ 返回 tcpConn{Orig: stream}
client.UDP()
└─ udpSM.NewUDP() // 分配会话
└─ 返回 HyUDPConn(Send/Receive)
HyUDPConn.Send(data, addr)
└─ UDPMessage.Serialize(buf) // 序列化含分片的 UDP 消息
└─ quic.Conn.SendDatagram(buf)
HyUDPConn.Receive()
└─ ReceiveMessage() → Conn.ReceiveDatagram()
└─ ParseUDPMessage(msg) // 解析与重组
4.3 服务端完整流程
NewServer(config)
└─ quic.Listen(config.Conn, tlsConfig, quicConfig)
└─ serverImpl{ listener }
serverImpl.Serve()
└─ listener.Accept() → goroutine: handleClient(conn)
└─ h3sHandler + http3.Server{
Handler: h3sHandler,
StreamDispatcher: h3sHandler.ProxyStreamHijacker
}
└─ h3s.ServeQUICConn(conn)
h3sHandler.ServeHTTP() [认证]
└─ POST 到 URLHost/URLPath
├─ 验证 Auth
├─ 协商带宽/拥塞控制
└─ 若 UDP enabled → 启动 udpSessionManager.Run()
h3sHandler.ProxyStreamHijacker() [TCP 代理]
└─ 检测帧类型 = 0x401 (FrameTypeTCPRequest)
└─ 读消耗帧类型 varint
└─ QStream 包装
└─ handleTCPRequest(qStream):
├─ ReadTCPRequest(stream) → 得到目标地址
├─ [可选] RequestHook.TCP()
├─ config.Outbound.TCP(reqAddr) → 建立目标连接
├─ WriteTCPResponse(stream, ok, msg)
└─ copyTwoWay(stream, tConn) // 双向流量转发
udpSessionManager.Run() [UDP 代理]
└─ [goroutine] ReceiveMessage() → ParseUDPMessage()
└─ 按 SessionID 路由到对应 session
└─ session.feed() → 转发到目标 UDP
└─ 返回数据 → SendMessage() → SendDatagram()
五、两个项目的对比
| 特性 | Xray hysteria | hysteria 原版 |
|---|---|---|
| 底层传输 | quic-go (apernet fork) | quic-go (apernet fork) |
| 认证方式 | HTTP POST + Hy-Auth header |
HTTP POST + Hysteria-Auth header |
| TCP 代理 | QUIC Stream + 自定义帧 0x401 | QUIC Stream + 自定义帧 0x401 |
| UDP 代理 | QUIC Datagram,4字节 SessionID | QUIC Datagram,UDPMessage 结构体 |
| 拥塞控制 | BBR/Brutal/Reno 可配 | BBR/Brutal 可配 |
| UdpHop | ✅ 支持端口跳变 | ❌ 不内置 |
| 流量混淆 | ✅ UdpmaskManager | ❌ 不内置 |
| TCP 分层 | QUIC → HTTP/3 → StreamDispatcher → 自定义帧 | QUIC → HTTP/3 → StreamDispatcher → 自定义帧 |
六、xhttp H3 与 hysteria 的 QUIC 参数对比
| 参数 | xhttp H3 | hysteria |
|---|---|---|
EnableDatagrams |
❌ 不启用 | ✅ 启用(UDP 必须) |
MaxDatagramFrameSize |
N/A | 1200 字节 |
DisablePathManager |
❌ | ✅ |
MaxIncomingStreams |
-1 (无限) | 1024 |
KeepAlivePeriod |
QuicgoH3KeepAlivePeriod |
可配 |
InitialStreamReceiveWindow |
可配 / 默认quic-go值 | 8 MB (8388608) |
InitialConnReceiveWindow |
可配 / 默认quic-go值 | 20 MB (8388608×5/2) |
七、关键文件索引
Xray-core
| 文件 | 说明 |
|---|---|
| splithttp/dialer.go | xhttp 客户端:H3/H2/H1 transport 创建,UdpHop,xmux 管理,上传逻辑 |
| splithttp/hub.go | xhttp 服务端:H3 QUIC 侦听,HTTP 请求路由,会话管理 |
| splithttp/mux.go | Xmux 连接复用管理 |
| hysteria/dialer.go | hysteria 客户端:QUIC 握手,认证,TCP/UDP 会话 |
| hysteria/hub.go | hysteria 服务端:QUIC 侦听,HTTP handler,StreamDispatcher |
| hysteria/congestion/utils.go | BBR/Brutal 拥塞控制切换 |
hysteria standalone
| 文件 | 说明 |
|---|---|
| core/client/client.go | 客户端主逻辑:connect,TCP/UDP 代理接口 |
| core/server/server.go | 服务端主逻辑:QUIC 侦听,认证,TCP/UDP 分发 |
| core/internal/protocol/proxy.go | 协议序列化:TCP 请求/响应帧,UDP 消息格式 |
| core/internal/protocol/http.go | HTTP header 认证协议 |
| core/internal/congestion/ | BBR/Brutal 拥塞控制实现 |
xhttp H3 与 hysteria:是否为标准 HTTP/3?
结论先行
| 协议 | QUIC 握手 | TLS 1.3 | ALPN h3 | HTTP/3 帧 | 标准性评价 |
|---|---|---|---|---|---|
| xhttp H3 | ✅ 标准 | ✅ 标准 | ✅ 标准 | ✅ 标准 | 几乎完全标准的 HTTP/3,应用层逻辑在标准 HTTP 语义之上 |
| hysteria | ✅ 标准 | ✅ 标准 | ✅ 标准 | ⚠️ 部分偏离 | 底层标准,但 TCP 流通过私有帧类型劫持 HTTP/3 流 |
一、什么是"标准 HTTP/3"
标准 HTTP/3(RFC 9114)要求:
- QUIC 握手:标准 QUIC(RFC 9000)+ TLS 1.3(RFC 8446)
- ALPN 协商:TLS ClientHello 中 ALPN 字段为
h3 - QUIC 连接:使用标准 QUIC 流和数据报
- HTTP/3 帧:
HEADERS、DATA、SETTINGS等标准帧类型 - QPACK 头压缩:标准 HTTP 头字段编码
二、xhttp(splithttp)H3 模式
2.1 握手过程 —— 完全标准
Client Server
| |
|── UDP 包 (QUIC Initial) ──────────────→ |
| TLS ClientHello |
| ALPN: ["h3"] |
| SNI: 目标域名 |
| |
|← QUIC Handshake (TLS 1.3) ─────────── |
| TLS ServerHello + Certificate |
| ALPN: h3 确认 |
| |
|── QUIC Handshake Complete ────────────→ |
| 拥塞控制激活(BBR/Reno) |
代码依据(dialer.go:193-302):
transport = &http3.Transport{
QUICConfig: quicConfig, // 标准 QUIC 配置
TLSClientConfig: gotlsConfig, // 标准 TLS 1.3
Dial: func(...) (*quic.Conn, error) {
quicConn = quic.DialEarly(...) // 标准 0-RTT QUIC 握手
congestion.UseBBR(quicConn) // 握手后切换拥塞控制
return quicConn, nil
},
}
服务端(hub.go:516):
l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, quicConfig) // 标准 QUIC
l.h3server = &http3.Server{Handler: handler} // 标准 HTTP/3 服务器
2.2 流量传输过程 —— 标准 HTTP/3 语义
xhttp 完全在标准 HTTP/3 请求/响应框架内工作:
下行通道(stream-down)
GET /path/{sessionId} HTTP/3
Host: example.com
Content-Type: text/event-stream ← 标准 SSE
Cache-Control: no-store
HTTP/3 200 OK ← 标准响应
[DATA frames 持续流式推送] ← 标准 HTTP/3 DATA 帧
上行通道(packet-up)
POST /path/{sessionId}/{seq} HTTP/3 ← 标准 HTTP POST
Host: example.com
Content-Length: N
[body = 数据块]
HTTP/3 200 OK
上行通道(stream-up/stream-one)
POST /path/{sessionId} HTTP/3 ← 标准 HTTP POST
Content-Type: application/grpc ← 标准 Content-Type
[body = 无限流] ← 标准 HTTP 请求体流
HTTP/3 200 OK
[body = 无限流] ← 标准 HTTP 响应体流
关键判断:xhttp H3 中每一帧都是标准的 HEADERS + DATA 帧。没有任何私有帧类型被注入。数据序列号(seq)、sessionId 等元信息通过普通 URL 路径/查询参数/HTTP 头/Cookie 传递,这些都是完全标准的 HTTP 语义。
2.3 与标准 HTTP/3 的唯一差异
- 用途:xhttp 把 HTTP/3 的请求/响应对当作流量隧道来用,不是正常的 Web 请求
- 行为模式:持续长连接 GET(SSE)+ 多个短 POST 并行,是非常规的请求模式,但仍在 HTTP/3 标准语义范围内
- 对 CDN/代理的兼容性:因为完全标准,中间 CDN 可以正常处理(这正是 xhttp 的设计目标)
三、hysteria 传输层
3.1 握手过程 —— 完全标准
Client Server
| |
|── UDP 包 (QUIC Initial) ──────────────→ |
| TLS ClientHello |
| ALPN: ["h3"] | ← 标准 h3
| |
|← QUIC Handshake (TLS 1.3) ─────────── |
| |
|── HTTP/3 POST /auth ──────────────────→ | ← 标准 HTTP/3
| Host: hysteria | ← 特殊值,非真实域名
| Hysteria-Auth:
<token> | ← 私有 HTTP 头
| Hysteria-CC-RX: <速率> | ← 私有 HTTP 头
| Hysteria-Padding: <随机填充> | ← 私有 HTTP 头
| |
|← HTTP/3 233 ────────────────────────── | ← 非标准状态码!
| Hysteria-UDP: true | ← 私有 HTTP 头
| Hysteria-CC-RX: <服务端速率> |
| |
|── 拥塞控制切换(Brutal/BBR)─────────→ |
代码依据(client/client.go:93-153,config.go:URLHost = "hysteria"):
req := &http.Request{
Method: http.MethodPost,
URL: &url.URL{
Scheme: "https",
Host: protocol.URLHost, // = "hysteria"(非真实域名)
Path: protocol.URLPath, // = "/auth"
},
Header: make(http.Header),
}
protocol.AuthRequestToHeader(req.Header, protocol.AuthRequest{
Auth: c.config.Auth,
Rx: c.config.BandwidthConfig.MaxRx,
})
resp, err := rt.RoundTrip(req)
// resp.StatusCode == 233 ← HTTP 标准状态码范围是 100-599,233 不是标准状态码!
状态码 233 的问题:标准 HTTP 只定义了 2xx 系列(200-299 有含义),233 虽然在技术上在 2xx 范围内,但它是 hysteria 私有的,没有 HTTP 标准定义。
3.2 TCP 流量传输 —— 关键偏离点:私有帧类型劫持
这是 hysteria 与标准 HTTP/3 最大的差异:
Client Server
| |
|── QUIC Stream (新流) ────────────→ |
| [首字节写入] |
| quicvarint(0x401) | ← FrameTypeTCPRequest = 0x401
| + 实际代理数据... |
| |
conn.go:34-51 的 interConn.Write():
func (i *interConn) Write(b []byte) (int, error) {
if i.client { // 仅首次写入时
buf := make([]byte, 0, quicvarint.Len(FrameTypeTCPRequest)+len(b))
buf = quicvarint.Append(buf, FrameTypeTCPRequest) // 写入 0x401
buf = append(buf, b...) // 后跟代理数据
_, err := i.stream.Write(buf)
i.client = false // 之后的写入不再加前缀
return len(b), nil
}
return i.stream.Write(b)
}
服务端通过 StreamDispatcher(HTTP/3 stream hijacking 机制)拦截:
func (h *httpHandler) StreamDispatcher(ft http3.FrameType, stream *quic.Stream, err error) (bool, error) {
switch ft {
case FrameTypeTCPRequest: // 0x401
// 劫持此流,不走正常 HTTP/3 处理
h.addConn(&interConn{stream: stream, ...})
return true, nil // true = 已处理,不再传给 HTTP handler
}
return false, nil
}
这里的机制说明:
- HTTP/3 规范(RFC 9114 §7.2.8)允许扩展帧类型(frame type),值
>= 0x21且不是已知类型的可被忽略或拒绝 - 但
0x401并非注册的标准扩展,而是 hysteria 私有定义 - quic-go/http3 的
StreamDispatcher(ProxyStreamHijacker)是 quic-go 的扩展 API,不是标准 HTTP/3 部分 - 结果:TCP 流实际上绕过了 HTTP/3 的请求/响应语义,直接在 QUIC Stream 上传输原始代理数据
3.3 UDP 流量传输 —— QUIC Datagram,非 HTTP/3 层
Client → Server:
QUIC DATAGRAM frame
[SessionID uint32 BE][... 代理数据 ...] ← 直接在 QUIC 层,不在 HTTP/3 层
Server → Client:
QUIC DATAGRAM frame
[SessionID uint32 BE][... 响应数据 ...]
代码(conn.go:128-136):
func (i *InterUdpConn) Write(p []byte) (int, error) {
binary.BigEndian.PutUint32(p, i.id) // 覆盖前4字节为SessionID
i.conn.SendDatagram(p) // 直接发QUIC Datagram
}
评价:QUIC Datagram(RFC 9221)是标准的,但它是 QUIC 层特性,HTTP/3 本身不使用 Datagram。hysteria 在 HTTP/3 连接上同时使用了:
- 标准 HTTP/3(用于认证握手)
- 私有 QUIC Stream 帧(用于 TCP 代理)
- 标准 QUIC Datagram(用于 UDP 代理)
四、完整对比:层次分析
┌─────────────────────────────────────────────────────────────────┐
│ xhttp H3 │
├──────────────┬──────────────────────────────────────────────────┤
│ 应用层 │ GET /sess/ ←→ SSE 流(标准 HTTP/3) │
│ │ POST /sess/seq ←→ 200 OK(标准 HTTP/3) │
├──────────────┼──────────────────────────────────────────────────┤
│ HTTP/3 层 │ HEADERS 帧 + DATA 帧(完全标准) │
├──────────────┼──────────────────────────────────────────────────┤
│ QUIC 层 │ 标准 QUIC 流和连接(RFC 9000) │
├──────────────┼──────────────────────────────────────────────────┤
│ TLS 层 │ 标准 TLS 1.3,ALPN h3 │
├──────────────┼──────────────────────────────────────────────────┤
│ 传输层 │ 标准 UDP │
└──────────────┴──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ hysteria │
├──────────────┬──────────────────────────────────────────────────┤
│ 应用层 │ TCP代理:私有帧(0x401)+原始数据 在 QUIC Stream │
│ │ UDP代理:[ID|数据] QUIC Datagram(非HTTP层) │
│ │ 认证:HTTP/3 POST(标准 HTTP 但私有 host/headers) │
├──────────────┼──────────────────────────────────────────────────┤
│ HTTP/3 层 │ 只用于认证握手;TCP代理绕过HTTP/3;UDP在QUIC层 │
├──────────────┼──────────────────────────────────────────────────┤
│ QUIC 层 │ 标准 QUIC(RFC 9000),EnableDatagrams=true │
├──────────────┼──────────────────────────────────────────────────┤
│ TLS 层 │ 标准 TLS 1.3,ALPN h3 │
├──────────────┼──────────────────────────────────────────────────┤
│ 传输层 │ 标准 UDP │
└──────────────┴──────────────────────────────────────────────────┘
五、一句话总结
| 协议 | 本质 |
|---|---|
| xhttp H3 | 真正的 HTTP/3 应用:用标准 HTTP/3 的 GET 长流 + 多 POST 实现数据隧道,中间节点(CDN/WAF)完全认为这是正常 HTTP/3 流量 |
| hysteria | 伪装成 HTTP/3 的 QUIC 代理:QUIC+TLS+ALPN=h3 完全标准以通过握手,但 TCP 代理抛弃了 HTTP/3 语义转而使用私有 QUIC Stream 帧,只是"借用"了 HTTP/3 的握手外壳 |
为什么 hysteria 要这样设计?
标准 HTTP/3 的 HEADERS+DATA 帧开销较大,每个 TCP 连接都需要一个完整的 HTTP 请求/响应,不适合高频小包。hysteria 通过私有帧类型直接复用 QUIC Stream,去掉 HTTP 头部开销,配合 Brutal 拥塞控制追求极限吞吐量。代价是无法通过标准 HTTP/3-aware 的中间节点。
文章评论