Xray-core xhttp H3传输 与 hysteria QUIC 代码分析

2026年4月8日

一、总体架构概览

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 连接建立

文件: dialer.go:102-343

关键代码路径(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 模式)

文件: dialer.go:498-583

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)要求:

  1. QUIC 握手:标准 QUIC(RFC 9000)+ TLS 1.3(RFC 8446)
  2. ALPN 协商:TLS ClientHello 中 ALPN 字段为 h3
  3. QUIC 连接:使用标准 QUIC 流和数据报
  4. HTTP/3 帧HEADERSDATASETTINGS 等标准帧类型
  5. 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-153config.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-51interConn.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 的 StreamDispatcherProxyStreamHijacker)是 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 的中间节点。

LeoKnox

Leo Leo Leo

文章评论