切换深浅色
第29章 Hysteria2、TUIC与QUIC代理
29.1 把代理语义放在QUIC之上
QUIC已经提供加密会话、可靠流、多路复用、拥塞控制及可选不可靠数据报。代理协议仍需解决另一组问题:谁有权使用连接、要访问哪个目标、一条流对应哪条目标TCP连接、UDP数据报属于哪个会话,以及错误如何通知客户端。Hysteria2与TUIC在这层给出不同安排,不能把它们都简化成“用UDP发送加密代理数据”。
本章固定Hysteria官方实现apernet/hysteria提交ca8fbd874fd6ca82413948de6dc5924405e103e1(2026-10-04)。其core模块使用Go1.26.0,并固定apernet/quic-go依赖v0.63.1-0.20261004180939-a10df75c260c。协议文档将2.0.0起这代协议另称内部v4,不能据此误认为本章写Hysteria1的旧格式。
TUIC规范固定于tuic-protocol/tuic提交8e118f242f24a17a9f487dc344cc50d7e63e557e(2025-03-13),SPEC明确版本0x05。该仓库说明自己不提供官方实现,本章据其规范讲字段与流程,不把任一第三方实现的默认拥塞算法冒称TUIC协议规定。版本0x05也不能与SOCKS5或QUIC版本1的编号混淆。
两者常见的数据路径都是:
text
应用TCP → 本地入口 → QUIC可靠双向流 → 远端代理 → 目标TCP
应用UDP → 本地入口 → QUIC数据报或指定流模式 → 远端代理 → 目标UDP应用原有TCP连接在本地代理结束,远端另建目标TCP;客户端到代理这一段可改用QUIC流。网站TLS仍能通过这种转发保持端到端,因为它需要的是可靠、有序字节通道,不要求中间每一段都以同一个TCP连接运输。这里不是把原始TCP段再原封不动装进UDP,因此不能不加区分地称作TCP-over-TCP。
同一QUIC连接可同时承载多个目标流。一个流缺失字节通常不阻塞其他流中已齐的数据,但共享连接拥塞控制、发送容量和连接级接收额度。目标A下载很大时,目标B仍可能因竞争这些共享资源而延迟;多路复用消除的是某些交付依赖,不是所有资源竞争。
QUIC DATAGRAM则不承诺重传和有序交付。它仍受连接拥塞与报文大小约束,且受QUIC加密保护。把UDP应用放入DATAGRAM通常更接近原本的不可靠数据报语义;把它放入可靠流,则会把丢失补齐和流内等待引入这一段。协议或配置选择不同,性能与失败行为也会不同。
TLS服务器认证、代理账号认证和网站认证仍然分层。QUIC握手证明的端点不自然等于已获授权的代理用户,HTTP式认证或Exporter令牌又不替浏览器验证网站证书。至于某次协商是否使用混合后量子组,须查实际QUIC/TLS实现与握手,不能由Hysteria2或TUIC名称直接推出。
最后,基础承载需要可工作的UDP路径。网络允许普通TCP HTTPS,不代表允许这些QUIC连接;流量混淆也不能在完全没有UDP可达性的路径上凭空建立运输。中转可以改变到达路径,代价和信任也需另计。后续将按认证、TCP、UDP、拥塞及会话状态逐项分析,而不是给协议名称做先验速度排名。
版本与源码见固定Hysteria core/go.mod、代理格式与TUIC SPEC。QUIC流与数据报保证以第12章的RFC 9000、9002、9221为基础。
29.2 Hysteria2:HTTP/3认证不代表所有代理数据都是HTTP
固定Hysteria客户端先建立QUIC安全连接,并通过HTTP/3发送认证请求。它使用POST、目标authority为hysteria、路径/auth;认证字符串和接收速率放进请求字段。下面是解码后的逻辑字段片段,不是实际QPACK字节,也省略真实随机Padding:
text
:method: POST
:scheme: https
:authority: hysteria
:path: /auth
Hysteria-Auth: public-teaching-value
Hysteria-CC-RX: 12500000
Hysteria-Padding: (本例省略随机字符串)authority中的hysteria是协议识别值,不是客户端必须向DNS查询的服务器域名。实际拨号端点、TLS验证名称及HTTP请求authority属于不同对象;固定客户端自定义拨号到配置ServerAddr,再形成这个请求。源码中的Go URL.Host会由HTTP/3实现编码为相应authority,不能把文档里的Host说明直接改成不存在的通用:host伪字段。
Hysteria-Auth由认证后端解释,可能对应口令或用户/口令等配置方案;它不是QUIC TLS自己的证书认证。字符串在QUIC应用保护下发送,服务器终结后可读。客户端若未正确验证服务器,就不能仅靠“有QUIC加密”证明认证材料已交给预期端点。
客户端Hysteria-CC-RX表示它能接受的最大接收速率,单位是字节/秒;12500000对应十进制100 Mbit/s,而不是100 MiB/s。0表示客户端未知,要求采用带宽探测方向的处理。它描述客户端下行能力,影响服务器向客户端发送,而不是把客户端上行也自动设成同一值。
服务端识别特定方法、Host和路径,调用配置Authenticator;认证通过后,将该QUIC连接标记为已认证,返回状态233以及Hysteria-UDP、Hysteria-CC-RX和Padding。233是本协议约定的成功值,不能改成普通200后仍假定固定客户端接受。UDP字段说明是否开放UDP代理;服务端接收速率字段影响客户端上行,数值0和特殊字符串auto又有不同含义:前者表示无给定服务端接收上限,后者要求客户端采用配置的带宽探测型拥塞控制路径。
| 字段位置 | 表达的接收方 | 首先约束的发送方向 |
|---|---|---|
| 请求Hysteria-CC-RX | 客户端 | 服务端→客户端 |
| 响应Hysteria-CC-RX | 服务端 | 客户端→服务端 |
固定客户端等待认证HTTP响应并检查233,再向上交付可用客户端状态。不能因为底层调用DialEarly,就断言所有新连接已经无需等待认证或必然使用TLS0-RTT。认证属于连接,后续复用它打开新代理流,不用每条目标TCP流再发送完整HTTP认证。
未通过认证或不是这个特殊请求时,服务端进入MasqHandler,提供普通内容或转发配置的网站。它不是对所有错误统一返回代理特征明显的状态码。这个设计目标是让未认证访问获得网站行为;实际指纹、时间、流量分布及异常路径能否被区分,不能由这一个分支自动证明。
认证成功后,TCP代理并不是普通HTTP CONNECT。固定服务器的ProxyStreamHijacker/Dispatcher识别自定义类型0x401,接管相应QUIC双向流,之后按代理格式处理。HTTP/3认证交换和自定义代理流共享同一QUIC连接的认证状态。一般HTTP反向代理若重新建立一条到源站的QUIC连接,不会自动复制这份状态及自定义流处理。
可选混淆又位于另一层。例如固定Salamander在QUIC数据报外加8字节Salt,以BLAKE2b-256(预共享混淆材料||Salt)形成循环异或材料。它没有替代QUIC的AEAD与服务器认证,也不是第二套完整TLS握手;开启这类外部变换后,线上包不再是普通HTTP/3端点可直接处理的标准QUIC包。不能同时承诺未经适配的HTTP/3入口仍照常接受。
认证字段、233及方向语义见固定protocol/http.go,客户端等待及算法选择见client.go,连接认证和流接管见server.go,混淆实例见salamander.go。
29.3 Hysteria2的可靠TCP流与不可靠UDP消息
认证后的每条目标TCP连接使用一个新的QUIC双向流。流开头写TCPRequest类型0x401,再写地址字符串及Padding;之后才是目标字节。多字节固定整数用大端,变长整数采用QUIC格式,最高两位指示1、2、4或8字节编码长度。
| TCPRequest字段 | 编码 |
|---|---|
| 类型 | QUIC varint,值0x401 |
| 地址长度 | QUIC varint |
| 地址 | host:port字符串,IPv6需相应括号表示 |
| Padding长度 | QUIC varint |
| Padding | 所述字节数 |
这里的端口是地址字符串的一部分,不再单独发送SOCKS式2字节端口。例如example.com:443占15字节,ASCII端口443对应34 34 33三个字节。0x401在最短QUIC varint中编码为44 01,不能直接发送04 01而期待相同解释,后者最高两位会被当作1字节整数。
零Padding的最小构造为:
text
44 01 0f 65 78 61 6d 70 6c 65 2e 63 6f 6d 3a 34 34 33 00共19字节。这只是格式构造;固定WriteTCPRequest会生成64到511字节Padding,长度64也需2字节varint,因此选择64时头为84字节,而非19或83。没有把默认随机Padding改成零的实测请求。
服务器返回TCPResponse:状态1字节(00成功、01错误)、消息长度varint、消息、Padding长度varint、Padding。成功后同一双向流两边直接转发字节。客户端默认等待响应再交付TCP连接;FastOpen选项则先交付,允许Write推进,把响应检查推迟到第一次Read。它没有删除响应,更不是TCP Fast Open或TLS0-RTT。
普通路径在目标拨号成功后回应;若启用RequestHook,固定服务端可以先回成功,让客户端送来供嗅探的数据,再调用钩子及连接目标。因此即使收到00状态,也要结合所选服务端路径判断它证明了哪一步,不能无条件把它当成网站TCP已经成立。
UDP不为每个目标创建可靠双向流,而通过QUIC DATAGRAM传递UDPMessage:
| 顺序 | 字段 | 长度 |
|---|---|---|
| 1 | Session ID | 4字节大端 |
| 2 | Packet ID | 2字节大端 |
| 3 | Fragment ID | 1字节 |
| 4 | Fragment Count | 1字节 |
| 5 | 地址长度 | QUIC varint |
| 6 | host:port地址 | 指定字节数 |
| 7 | UDP负载 | 数据报中其余字节 |
例如Session1、Packet2、未分片、example.com:443和hello,UDPMessage头为00 00 00 01 00 02 00 01 0f,再接15字节地址及5字节负载,总共29字节。它还要被QUIC DATAGRAM帧与QUIC包封装,29不是完整IP包长度。
Session ID让服务器把数据关联到相应UDP出站状态。它不是Shadowsocks 2022的密钥派生Salt,也不表示每个会话都有另一套TLS秘密。Packet ID主要用于分片重组:未分片时Fragment Count为1,协议不需要依赖Packet ID提供逐包防重放窗口,不能按字段名字套用2022的窗口设计。
当UDPMessage超过当前可发送DATAGRAM大小时,客户端可以分片,每片保留会话、同一Packet ID及目标信息,按Fragment ID标号;接收端等所有片齐后才还原应用数据报。丢一片会使整个原包不能交付,QUIC DATAGRAM不会自动替这一片重传。若每片独立丢失概率为p,一个分成n片的原包至少缺一片的概率为1−(1−p)^n;这个理想模型说明分片扩大损失机会,不代表实际丢包一定独立。
固定实现的Defragger一次只维护一个Packet ID。尚未收齐时另一个分片包进入,会清掉原重组状态;它不是任意数量并发分片包的重组表。客户端还有有限接收通道,满时主动丢消息。因此“不可靠”不仅包括网络丢失,也包括本地缓冲和实现重组策略带来的丢弃,不能只看链路抓包统计推断应用收包数。
本章不把源码常量MaxDatagramFrameSize=1200当作所有路径MTU。真实QUIC包还有头部、Tag及帧开销,DATAGRAM额度由连接和路径约束。固定客户端SendBuf使用4096字节缓冲等实现限制,也应与协议抽象的数据报能力分别理解。
TCP/UDP结构及编码见固定protocol/proxy.go,等待与FastOpen见client.go,提前Hook响应见server.go,重组和缓冲见frag.go及client/udp.go。
29.4 带宽参数、Brutal与共享瓶颈
同一种Hysteria2报文格式,可以在不同拥塞控制下运行。客户端与服务端通过接收速率信息影响各自发送策略,但没有在协议里保证“配置多少就能达到多少”。可用瓶颈容量、竞争流、RTT、丢包与CPU仍决定实际交付。
固定客户端若收到auto,使用所配置的拥塞算法;否则结合服务端接收信息与自身发送参数。得到正的目标速率时使用Brutal,没有可用正目标时转向配置的探测型控制。服务端在认证后也按客户端接收信息、自己的配置及IgnoreClientBandwidth处理。utils.go在所列配置路径支持BBR和Reno;不能再把“Hysteria2始终只用Brutal”当成所有连接的事实。
Brutal的基本思路是以用户给出的目标速率为主要输入,通过pacer控制发送时机,再根据近期确认/丢失统计补偿损失。所读实现以字节/秒保存bps,发送速率回调约为bps/ackRate;拥塞窗口按约2×bps×平滑RTT/ackRate计算,并设置必要下限。
例如目标速率10,000,000字节/秒,统计ackRate为0.9时,pacer输入约11,111,111字节/秒。这是控制器计算条件,不是已测得的有效下载速度;其中还包含运输层开销和重传等实际因素。若RTT为80 ms,按所列窗口公式得到约1,777,778字节。该量是允许的在途控制尺度,不是把每包扩大到这个大小。
固定统计使用5个按秒槽位,样本少于50时ackRate回到1;计算的确认比例下限钳在0.8,禁用损失补偿时也使用1。因此损失很高时,它不会按1/(1−p)无界增加发送。比如观察比例0.6,计算仍以0.8为下限,不能用10MB/s÷0.6描述这个版本的实际公式。
这套方法对非拥塞型随机丢失可能较少因每次损失大幅收缩,但它不能凭空区分所有无线错误、设备丢弃与队列拥塞。目标速率高于可用瓶颈时,额外发送可能增加排队与丢包;旁边其他流也可能被挤压。更高的带宽配置并不单调改善网页响应或语音延迟,尤其共享瓶颈已经拥塞时。
BBR则尝试根据交付速率和RTT等状态估计路径,Reno主要通过拥塞窗口反馈调整。二者的具体版本、参数与实现都会改变行为。这里比较的是控制依据,不能直接用算法名字给所有高丢包网络排名;也不能把BBR的模型估计称为天然知道真实物理带宽。
接收窗口又是另一项限制。即使发送控制器允许很高速率,流或连接级接收额度不足,仍会等待窗口更新。反过来,把窗口调大只增加可缓冲和在途空间,不会让共享瓶颈容量提高。以100 Mbit/s、80 ms为条件,约1MB带宽时延积的推导仍适用,但每段实际窗口和消费速度要分别核对。
不可靠DATAGRAM也要参与QUIC拥塞管理。它不重传某个丢失数据报,不意味着可以绕过发送节奏或随意超出路径容量;可靠流与数据报在同一连接上还会争用发送资源。语音包可能因本地排队失去时效,甚至没有发生网络丢包,应用体验已经恶化。
因此判断一种方案是否合适,应先区分要保留的应用语义、UDP可达性、拥塞型或非拥塞型损失、可接受排队和目标速率来源。带宽参数是控制输入,吞吐是结果,低延迟又是另一个结果。没有同条件测量,本书不给出“高丢包必选某协议”的无条件结论。
分支与单位见固定client.go和server.go;公式、5秒槽、50样本与0.8下限见brutal.go,配置算法选择见utils.go。
29.5 TUIC v5:连接绑定令牌与五种命令
TUIC v5每条命令以版本05、类型1字节开头,后面按类型解释。它没有先发一份HTTP认证请求的要求;典型QUIC映射中,客户端打开单向流发送Authenticate,同时可建立其他代理任务,服务器在认证完成前暂停这些任务的后续处理。
| TYPE | 命令 | 主要后续字段 |
|---|---|---|
| 00 | Authenticate | UUID16、Token32 |
| 01 | Connect | Address |
| 02 | Packet | 关联、包号、分片及地址等 |
| 03 | Dissociate | 关联ID2 |
| 04 | Heartbeat | 无命令特有字段 |
认证命令总长50字节:05 00、UUID原始16字节、Token32。Token不是把口令固定做一次SHA256,也不是Trojan式每次相同的摘要。规范要求使用当前TLS连接的Keying Material Exporter,标签取客户端UUID,context取口令原始字节,输出32字节。
为了确定“UUID作标签”究竟用什么字节,本书另外核对Mihomo固定提交88dcbf7f1614a67c3b36b848ee3592dfa92ada36的TUIC v5实现。GenToken把UUID原始16字节转换为不改内容的字符串作为label,口令原始字节作为context,调用当前TLS状态的ExportKeyingMaterial(...,32)。它没有使用带连字符的36字符UUID文本,也没有把口令先Hex编码。
TLS Exporter把应用输出绑定到这次TLS秘密与上下文。沿第16章TLS1.3记号,可写为:
text
Token = TLS-Exporter(label=UUID原始16字节,
context=口令原始字节, length=32)
TLS-Exporter(label, context, n)
= HKDF-Expand-Label(Derive-Secret(Secret, label, 空消息),
"exporter", Hash(context), n)Secret取相应正常Exporter秘密;这里不是让口令代替TLS握手秘密,也不是把Export接口理解成导出网站或代理的原始流量密钥。不同连接拥有不同秘密时,相同UUID和口令得到的Token也随连接改变,上一连接的Token不能直接作为下一连接的正确值。
客户端与服务器各自持有当前TLS状态,服务器按UUID找到配置口令后复算并比较Token。它一方面依赖TLS服务器认证确保客户端连对端点,另一方面验证客户端具备匹配的账号材料。合法证书仍不等于任意客户端已授权;UUID能找到用户也不等于Token已通过。
Mihomo所列客户端异步发送认证,服务端完成Token比较后通过认证通道唤醒等待者,失败则可关闭连接。它与规范允许认证和代理头并行的设计对应,但不代表未认证负载可以先被任意转发。缓冲、等待超时与资源限制仍是实现要处理的部分。
规范所说0-RTT转发,应首先理解为代理命令不额外等待专用成功响应,而不是每次新建QUIC都不需要握手。普通TLS Exporter与早期Exporter还是不同接口;未明确改用早期Exporter并处理重放,就不能把普通Token公式搬到TLS0-RTT阶段。所列实现调用正常ExportKeyingMaterial,本节没有声称它能在任何早期状态成功输出。
Connect命令之后,客户端可开始发送目标字节;规范没有为任何命令定义统一响应。目标不可达时可关闭流等,具体错误映射由实现决定,不能编造一个TUIC标准成功码。与Hysteria2明确的TCPResponse相比,这是交互设计上的区别,不是服务器已经保证连通。
TUIC v5字段与并行规则见固定SPEC.md,具体Exporter字节及认证路径见Mihomov5/protocol.go、client.go及server.go。TLS Exporter定义以RFC 9846第7.5节为准,未改用早期Exporter。
29.6 TUIC:目标流、UDP关联与两种运输方式
Connect命令在新的QUIC双向流开头发送05 01和目标Address,之后同一流承载目标TCP字节。Address使用TUIC自己的类型值:域名00、IPv401、IPv602;域名先有1字节长度,端口在地址之后占2字节大端。不要使用SOCKS的域名03或VLESS的域名02。
example.com:443的完整Connect头为17字节:
text
05 01 00 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb服务器解析目标并等待连接认证,通过后建立目标TCP并转发。规范没有Connect成功回复,所以客户端可以立即写应用数据;写入QUIC成功只是进入该运输路径,不代表目标已建连。失败如何映射为流关闭或连接错误属于实现,而非一份本书可任意指定的标准状态头。
UDP用Packet命令。去掉最初版本与类型后,其字段如下:
| 字段 | 长度 | 作用 |
|---|---|---|
| ASSOC_ID | 2大端 | 这条QUIC连接内的UDP关联 |
| PKT_ID | 2大端 | 同一原始UDP包的分片关联 |
| FRAG_TOTAL | 1 | 分片总数 |
| FRAG_ID | 1 | 当前片编号 |
| SIZE | 2大端 | 本片负载字节数 |
| ADDR | 可变 | 目标或回程来源,后续片可为None |
| DATA | SIZE | 本片内容 |
算长度时,版本/类型2加这组固定字段8,共10字节,再加地址和负载。关联1、包2、总片数1、片0、example.com:443及hello的构造为30字节:
text
05 02 00 01 00 02 01 00 00 05
00 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb
68 65 6c 6c 6fSIZE是5,不是整个30;地址域名部分15字节也不计入SIZE。这里不用QUIC varint编码这些uint16字段,不能因为底层是QUIC就把所有整数改成varint。
较大的UDP包可以拆成多个Packet。首片带真实Address,非首片用None类型ff表示地址省略。固定Mihomo的ReadAddress/WriteTo遇到None立即返回,只占1字节,不再读写端口。其分片发送函数把后续片设为None,重组后从首片恢复地址。
用hello人为拆成he与llo两片,总片数2,首片SIZE2、次片SIZE3:首片10+15+2=27字节,次片10+1+3=14字节,总共41。这个微小构造只说明字段与重组,不是建议将5字节数据实际分片。真实分片大小取决于DATAGRAM可用额度及实现限制;多一个分片会增加头部和丢失风险。
TUIC规范允许客户端通过两种方式发送Packet:native模式使用QUIC DATAGRAM,quic模式使用QUIC单向流。前者不会重传丢失的数据报,后者由QUIC流可靠交付。固定Mihomo流模式为相应发送打开单向流,原始UDP包仍需在远端恢复后再经UDP发出。因此“可靠模式”只描述这一段,不保证目标网络上的UDP端到端可靠。
不同单向流可以减少不同UDP包间的流内顺序耦合,但共享QUIC连接的拥塞与流额度;大量短流会增加状态和调度。DATAGRAM省去可靠重传等待,却可能丢弃迟到或超额消息。两种方式不是同一个包简单换个配置标签而毫无代价。
服务器按每条QUIC连接内的ASSOC_ID关联UDP Socket。同一关联可向不同目标发送并接收回程,这是规范所说Full Cone兼容方向;它不意味着客户端所处物理NAT必然变成全锥型,也不取消服务器的访问策略。关闭关联可经单向流发送Dissociate:05 03及2字节关联号,共4字节。Heartbeat只有05 04,通过DATAGRAM周期发送,帮助维持连接活跃,但丢失心跳或实际路径失效仍可能导致关闭。
PKT_ID不应被误称为密码Nonce;QUIC自身负责包保护与密钥状态,它在代理层主要服务分片识别。关联与包号复用、重组过期、缓冲上限及错误处理要看具体实现。所列Mihomo重组器使用按包号管理的缓存,而Hysteria固定Defragger一次只存一个包号,不能把二者局部策略互相套用。
规范见固定TUIC SPEC;实际字段写读与None特例见Mihomov5/protocol.go,分片与运输见frag.go及packet.go。字节数按实际写出字段计算,没有依赖可能只用于分配估计的长度辅助函数。
29.7 同一组应用连接,怎样映射成两种协议
设本地应用同时建立两个TCP目标A、B,并使用一个UDP会话U。先假定客户端到代理的QUIC连接已经通过TLS端点验证;我们只比较代理认证与任务组织,不把已完成的QUIC握手从总成本中抹去。
Hysteria2客户端先在HTTP/3认证请求上携带凭据和接收能力,取得233后,A、B分别打开新的双向流,发送0x401请求。每条流有自己的TCPResponse;默认调用者等待该响应,FastOpen则可以先写入、首次Read再处理响应。U的消息通过DATAGRAM,Session ID将其关联到远端UDP状态。
TUIC v5先发单向认证流,同时允许A、B的Connect头并行发送。服务端若先见到Connect,就暂停后续任务直到认证完成;没有专用Connect成功响应。U可以选择native DATAGRAM或quic单向流,ASSOC_ID关联远端UDP Socket;关闭关联还可发送Dissociate。
| 对同一需求的处理 | Hysteria2固定路径 | TUIC v5规范及所列实现 |
|---|---|---|
| 代理连接认证 | HTTP/3 POST与233 | 单向Authenticate,Exporter绑定Token |
| A、B的目标表达 | host:port字符串 | 类型、地址及2字节端口 |
| 每条目标TCP确认 | 有TCPResponse,FastOpen可延后读取 | 无标准响应 |
| U的数据报路径 | QUIC DATAGRAM | DATAGRAM或单向流 |
| U的关联编号 | 32位Session ID | 16位ASSOC_ID |
| U的显式关闭命令 | 基础消息无相应命令,依空闲等策略 | Dissociate |
| 拥塞算法 | 依配置与认证速率选择 | 规范不指定算法,查实现 |
如果A的大块下载发生丢包,QUIC通常只等待补齐A流里缺失的区间,B中已齐的数据可以交付。可A和B共享同一QUIC连接的拥塞控制;若丢包降低发送额度,B后续尚未发出的数据仍可能变慢。U的DATAGRAM也争用同一发送资源,不会因为没有重传就自动获得优先级。
若U使用可靠单向流,某一包的内容可由QUIC重传补齐;不同包是否互相等待还取决于流映射。不能把它概括为所有UDP包永远只在同一条有序流上,也不能把使用多个流理解成没有状态成本。Mihomo的TUIC发送路径为这种选择提供了具体例子,但其他实现仍须核对。
再假设本地网络地址变化。QUIC具有连接迁移相关机制,代理层的会话ID也可帮助保持关联,但本章固定软件可能关闭某些路径管理能力,外层混淆、NAT与服务端策略还会影响结果。QUIC标准具备能力与某配置已实现无缝迁移是不同结论;不能只看协议名就保证所有目标连接不断。
认证方式也影响凭据可见性。Hysteria认证终点读取认证字符串;TUIC验证的是当前TLS Exporter生成的Token,并据UUID找口令。两者都需信任TLS终结端点,也都不能自动隐藏目标地址。TUIC的连接绑定Token不能直接挪到另一连接,但这不构成流量关联匿名性;Hysteria的HTTP式认证也不表示所有后续代理流能经任意HTTP中间节点处理。
这些差异足以解释两种实现为什么在同一网络上也可能表现不同,却不足以直接决定性能排名。下一节将把协议层减少的等待、拥塞策略及实际瓶颈分开,避免把一个目标流的快慢归因于全部协议设计。
对应机制分别见固定Hysteria client、server及TUIC SPEC;具体可靠UDP流映射以Mihomo v5/packet.go为例。场景为控制流推导,未实测连接迁移或吞吐。
29.8 如何理解一个“更快”的结果
协议比较最常见的错误,是把不同条件下的结果归结为协议名字。一方在已有QUIC连接中打开新流,另一方从DNS、TCP和TLS重新开始;即使前者首字节明显更早,也不能据此证明其每个编码字段或加密算法更高效。比较对象实际上包含连接寿命和复用策略。
冷启动、已认证连接中新建目标、单条长流和多条并发短流,关键路径不同。冷启动可能主要等待握手与认证;新目标还要等待远端DNS和目标连接;长流更可能受持续吞吐或CPU限制;短流并发则受流额度、调度、排队和共享拥塞影响。一个数字不能代表这四种工作负载。
吞吐也要说明计数位置。网络接口每秒发出更多字节,可能只是增加了Padding、QUIC头、分片或重传;应用每秒收到完整有效负载才接近goodput。UDP若只统计发送端Write成功,忽略接收丢包和过期,就可能把大量未交付数据算成“更快”。可靠流与不可靠DATAGRAM之间尤其不能混用这两种统计。
假设某流可用接收窗口为256 KiB,RTT为100 ms,且其他条件理想,仅由窗口循环估算的速率上界约为256×1024×8/0.1,即20.97 Mbit/s。此时换一个请求头省几字节,并不能突破这个依赖;增大单流窗口后若又碰到连接窗口,仍需按共享额度判断。这个算术不是某软件默认窗口或实测速度。
高丢包结果还要解释损失来源。随机无线错误可能与拥塞无关,队列溢出却说明路径已有竞争;同样1%平均丢包,孤立损失与集中长突发对实时数据和重组的影响不同。把配置速率设得很高而更积极发送,可能提高单次大文件goodput,也可能让旁边短流排队更久。只报一个最大下载值会丢掉后一部分代价。
分片也能改变有效交付。独立1%片损失下,10片原包全部到达概率约0.99^10,原包失败概率约9.56%;同样平均片损失并不能直接按1%估计应用整包丢失。若片损失有关联,该模型又不适用。Hysteria所列单包重组策略与TUIC所列缓存策略,还可能产生不同的本地丢弃结果。
CPU路径须与网络机制一起看。QUIC通常在用户态处理包保护、ACK、定时器和流调度,能方便替换拥塞算法,也可能受每包处理和内核收发开销限制。某路径支持批量收发、GSO等能力,不代表所有平台或虚拟化环境都走到相同快路径。TCP上的代理若能利用内核复制优化,其成本结构又不同。
算法与协议应分别命名。同一Hysteria版本可依速率与配置使用Brutal或其他控制;TUIC规范不固定BBR。将“某TUIC实现的BBR参数”与“另一Hysteria实现的Brutal目标速率”比较,得到的是整个组合表现,不能从中隔离出代理头差异。TLS组、证书校验、0-RTT接受和可选混淆也会改变条件。
实际结果若没有交代版本、流量方向、统计层、已复用状态、负载和网络约束,只能说明当时那组应用观察,不能支持普遍选型结论。本书的字节计数和控制公式帮助解释瓶颈,没有据此编造吞吐、CPU占用或延迟百分位。
流窗口和拥塞保证以第12章的QUIC标准为基础;实际算法与流行为见固定Hysteria拥塞选择、Brutal实现及TUIC映射规范。数值均是标明假设的分析例。
29.9 运输层成功,不代表代理任务成功
QUIC负责把受保护的流字节或数据报送到一个已经建立安全关系的端点;代理协议负责把这些内容映射为目标连接。QUIC握手完成后,用户仍可能未通过Hysteria认证或TUIC Token检查;账号通过后,目标DNS、访问策略和网站TCP仍可失败。把这些阶段都叫“节点连接成功”,会丢掉故障位置。
QUIC的ACK确认运输包,不确认远端网站已经读取对应应用内容。可靠流的发送完成也可能只表示字节已排入本地发送路径;即使已被对端QUIC确认,远端代理仍可能尚未写完目标Socket。DATAGRAM更没有应用交付确认。判断一条HTTP请求是否执行,还需目标协议自己的响应及业务语义。
流FIN表达发送方向的字节结束,RESET_STREAM等则表达中断。它们都不能代替网站TLS对握手、记录和关闭的检查;若网站响应被截断,代理流干净结束也未必足以证明HTTP消息完整。相反,关闭整个QUIC连接会影响共享它的多个目标流和UDP关联,故单流失败与连接失败应分别分析。
代理会话状态也不能由QUIC连接ID自动复制。连接迁移试图让同一QUIC安全连接适应路径变化,不等于新服务器进程天然取得旧的认证用户、目标Socket、UDP关联及重组缓存。若服务重启或负载均衡切换到了没有这些状态的实例,通常需要按实现重新建立任务。地址看似没变、Connection ID还在,都不是后端状态连续的充分证据。
Hysteria的Session ID、TUIC的ASSOC_ID、QUIC的Connection ID与QUIC流ID因此各有命名空间和用途。前两者关联代理UDP任务,后两者服务运输连接和流。Hysteria Packet ID与TUIC PKT_ID又主要参与分片,不是逐包公开的TLS Nonce。遇到一个id字段,必须先确定它归属哪层、在哪个范围内唯一、何时可复用以及谁保存映射。
安全边界同样可据这些层次定位。服务器验证名称正确且所选TLS交换满足要求,才能主张对应的安全连接;账号认证决定可使用哪些代理能力;远端服务器仍能看到目标地址。普通HTTP、DNS或其他未加端到端加密的负载在代理处可见,网站TLS则把应用秘密继续保留在浏览器与网站。
若启用QUIC早期数据,还须区分恢复票据、正常与早期Exporter,以及应用是否接受重放。TUIC的连接绑定Token不应未经证明就称为消除所有早期数据重放;Hysteria的HTTP认证也不能保证其Body或后续命令自动具备业务幂等性。底层支持早发只是能力,真正使用需有对应状态和接受规则。
选择拥塞算法、混淆外观或增加Padding不会改变这些职责。Brutal的目标速率不是代理授权,HTTP/3外观不是任意CDN的承载协议,Full Cone关联也不是匿名性。将每个功能放回实际终结点和数据路径,才能判断它解决了哪一个问题,以及剩余问题应交给哪一层。
本章所列实现和构造案例解释了两种协议的机制,没有进行生产互通、连接迁移、吞吐或不可识别性验证。后面的工程比较将据已明确的字段、握手依赖和状态约束作条件性选择,保留具体实现、路径和目标应用的差异。
代理层规则见固定Hysteria协议实现与TUIC规范,可靠流、ACK与连接迁移边界见RFC 9000,不可靠数据报见RFC 9221。