UDP 与 QUIC 传输层

本页目录

UDP 保留数据报边界,但不保证交付、顺序和去重。QUIC 借助 UDP 承载自己的加密包、确认与多流机制;它提供可靠流,也可通过扩展承载不可靠数据报。选择它们时,应先明确业务需要消息还是字节流、是否允许过期丢弃、怎样确认成功,而不是把 UDP 等同于“快”、QUIC 等同于“永不断线”。

查阅问题核心理解
UDP 一次发送多大UDP 长度、IP 长度和路径 MTU分别约束
QUIC 重传为何换包号包号标识一次发送,流偏移标识数据位置
丢包是否阻塞所有流流内有序,跨流没有 TCP 式统一字节缺口
0-RTT 是不是首次就零等待需要恢复状态,可能被拒绝且有重放风险
换 IP 后为何可能继续CID、路径验证与迁移规则共同作用

UDP:8 B 头部之外还有使用责任

UDP 头由源端口、目的端口、Length、Checksum 四个 16 bit 字段组成。Length 包含 8 B UDP 头;普通字段最大 65535,但在无选项 IPv4 中,还需受 IP 总长度限制,最大 UDP 数据是 65535−20−8=65507 B。这个协议上界远大于常见路径可直接承载的大小,并不建议应用发送这么大的数据报。RFC 768:UDP

在 MTU=1500、无选项/扩展头时,IPv4 UDP 数据最多 1472 B,IPv6 为 1452 B;隧道和 QUIC 自身开销还会继续扣除。若一个 UDP 数据报被 IP 分片,通常需要所有片重组成功才能交付这份数据报,丢一片就可能损失整份数据。

IPv4 UDP 的零校验和字段表示未提供该校验;若实际计算结果为零,应在线上发送全 1。IPv6 UDP 默认要求校验和,特定隧道例外和 jumbogram 有独立规则,不能把例外当作普通应用默认。校验覆盖伪首部与 UDP 消息,不提供发送者身份认证。

应用还要处理丢失、乱序、重复、大小探测和拥塞控制。UDP connect 可以设置本地默认对端和过滤等 socket 行为,并不执行 TCP 式远端握手;send 成功也不证明对端接收。实时业务常宁愿丢弃已经过时的数据,但仍应控制发送负载。RFC 8085:UDP 应用准则

QUIC 的四层对象

不要混淆 UDP 数据报、QUIC 包、帧和应用流 一个 UDP 数据报:8 B UDP 头之后是载荷 QUIC 包 A:自身头部与保护 CRYPTO 帧 握手字节 ACK 帧 确认包号 QUIC 包 B:自身头部与保护 帧集合 按包类型与加密级别允许的帧 图示合包层次,不规定每包必须包含哪些帧;短头包缺少独立 Length,合包时位于数据报末尾。
对象标识/边界功能
UDP 数据报UDP 长度路径上的一次数据报承载,可合装多个 QUIC 包
QUIC 包Packet Number 等加密保护、确认与丢失检测的发送单位
Frame帧类型和具体字段STREAM、ACK、CRYPTO、流控或路径验证等语义
StreamStream ID 与字节 Offset应用可使用的独立有序字节流

一条流可以跨很多包,一个包也可以携带多条流的帧。丢包后重新发送需要恢复的信息,可能装进另一包并重新分组,因此不是把旧包原封不动再发一次。

长头、短头与可见信息

QUIC v1 的 Initial、Handshake、0-RTT 使用相应长头,1-RTT 使用短头;Retry 和版本协商又有特殊结构,不能用一个固定字节表覆盖所有类型。长头携带 Version、连接 ID 长度等信息;短头的目的 CID 长度依赖连接上下文,不能仅凭随手抓到的孤立包就完整解析。RFC 9000:包格式

Packet Number 在线上只编码必要的低位,并由接收方结合上下文恢复。部分头字段受头保护,不能在解保护前把它们当作明文标志直接分析。v1 基线的 QUIC Bit 规则也存在后续协商扩展,因此需结合协商结果解释该位。RFC 9287:QUIC Bit 扩展

Spin Bit 是可选的测量信号,端点可以不提供,观察还会受丢包、重排和路径影响。它不是每个端点严格按一个定时器“每 RTT 翻一次”的通用时钟,也不揭示应用请求时延。

重传的是信息,还是原来的包?

先只递送包11,流4已经有可交付数据,流0仍为空。再送包12,最后补送迟到的包10:包确认集合增加了,唯一流数据量却不再增加。这两个编号空间不能合并理解。

正在呈现知识画面
重传的是信息,还是原来的包?

同一流区间可以装进新编号的包。接收去重按流偏移,包的确认按包编号;一个流的缺口不阻止另一个流交付。

握手与加密级别

QUIC 使用 TLS 1.3 的握手消息和密钥协商,但不把 TLS record 原样套在传输流上;CRYPTO 帧按相应加密级别承载握手字节。Initial、Handshake、0-RTT、1-RTT 的保护与使用条件不同。Initial 密钥可由公开信息派生,所以 Initial 包保护不等于已经对网络观察者保密或完成服务器认证。RFC 9001:TLS 与 QUIC

首次握手常可在一次往返后开始相应应用通信,但 Retry、证书大小、丢包和调度会改变实测耗时。服务器在尚未验证客户端地址时受反放大限制,通常最多发送收到字节数的三倍;这可能限制一次性发完握手数据。

客户端承载 Initial 的 UDP 数据报需扩展到至少 1200 B,指的是 UDP 载荷⁠,不是加上 UDP/IP 后恰好 1200 B。无扩展头 IPv6 下对应至少 1248 B IP 包,仍需考虑真实路径。RFC 9000:数据报大小与地址验证

0-RTT:提前发送,不是提前获得全部保证

恢复连接时,客户端可依据保存的 TLS 恢复状态和允许沿用的传输参数发送 early data;首次访问不能凭空拥有这些条件。服务器可能拒绝 early data,TLS 的接受信号决定结果,QUIC 没有用于此目的的 REJECTED 帧。拒绝时,客户端按协议与应用接口处理原本的早期数据,不能把拒绝当成业务已经执行。RFC 9001:0-RTT

0-RTT 存在重放风险,应用必须判断操作语义和部署中的重放防护。HTTP 方法名不是充分证明:错误实现的 GET 也可能有副作用。AEAD 的密钥使用上限限制密码学风险,并不提供业务去重或限制攻击者的重放次数。

流编号与流控:限额是累计上限

Stream ID 的最低位表示发起端(0 客户端、1 服务端),次低位表示方向(0 双向、1 单向);余下高位是该类别的序号。因此各类 ID 每次加 4:

类别首批 ID
客户端发起双向0、4、8、12
服务端发起双向1、5、9、13
客户端发起单向2、6、10、14
服务端发起单向3、7、11、15

双向流的两个发送方向有各自字节偏移。MAX_STREAM_DATA 约束相应流方向的累计可发送范围,MAX_DATA 约束连接范围内相应流数据的累计使用,MAX_STREAMS 则限制可开启流数量;这些不是三个可以互换的“缓冲大小”。RFC 9000:流与流控

例如连接限额 10000 B,两条流已分别使用 4000 和 3000 B,连接还余 3000 B;若目标流自身只余 1000 B,则新增数据最多受这 1000 B 限制。重传已有字节不会因为再次发送而重复消耗新的流控额度。RESET_STREAM 的 final size 也参与相关记账,不能用不断重置绕过上限。

跨流独立交付:共享网络资源,仍有各自的有序缺口 Stream 0 Stream 4 0~999 已到 1000~1999 缺失 2000~2999 已到 0~999 已到 1000~1999 已到 2000~2999 已到 Stream 4 可连续交付;Stream 0 后半段等待补齐 若同一丢失包同时装了两条流的数据,两条流都会受影响;连接拥塞窗口与连接流控仍共享。

独立流消除了 TCP 单一有序字节流导致的跨流传输层等待,但同一流内部仍需补齐缺口。多个流共享拥塞控制、连接流控、CPU 与路径;HTTP/3 还可能有应用层依赖,所以“QUIC 完全没有队头阻塞”仍不准确。

重传为什么不再占一份流控额度?

把连接总量与目标流单独画出,才能看出瓶颈来自哪里。保持偏移不变反复重传,线速重复字节增长,流控余额却不变;这不是免费带宽,重传仍受拥塞控制等其他约束。

正在呈现知识画面
重传为什么不再占一份流控额度?

额度按各流已消耗的最大偏移累计,不按重复发出的线速字节累计。新数据同时受连接与流上限约束,重传已有区间不提高偏移。

ACK 与丢失检测:确认不等于读心

Initial、Handshake、Application Data 是三个 Packet Number Space,其中 0-RTT 和 1-RTT 共用应用数据空间。包号在各自空间中递增且不复用;把流字节重新装包发送时使用新包号。ACK 报告已经收到的包号区间,并不直接列出一份已被物理证实丢失的包清单。

若包 10 未确认而较大包已确认,它可能丢失,也可能只是乱序。RFC 9002 用包阈值和时间阈值等推断:推荐包阈值为 3;时间参考 9/8×max(latest_rtt,smoothed_rtt) 并考虑时钟粒度等条件,使新样本与平滑估计共同约束判定。判断还需要已确认更晚发送的包等上下文。RFC 9002:丢失检测

PTO 到期会安排探测,不直接宣布所有未确认包都已丢失。以应用数据空间的简化估算为例,SRTT=40 ms、RTTVAR=5 ms、max_ack_delay=25 ms、粒度 1 ms,则 PTO 基础值为 40+max(20,1)+25=85 ms;握手空间不照搬同样 ACK delay,连续 PTO 还有退避。

拥塞控制决定发送负载,恢复机制决定补哪些信息。RFC 9002 给出一个基于 NewReno 的示例拥塞算法,QUIC 并不要求所有实现必须使用它;CUBIC、BBR 等可在符合协议约束下由实现采用。

CID 与迁移:新路径仍要验证

接收端可用自己发放的 Connection ID 将来自新地址的包关联到连接。CID 不是永恒不变的一对编号;端点可以按规则发放、使用和退役多个 CID,短头也不同时携带源/目的两份 CID。零长度 CID 等部署选择会限制迁移能力。

客户端换网后,新路径上的受保护包仍要经过连接认证与路径验证;PATH_CHALLENGE 的随机数据由 PATH_RESPONSE 回显,帮助确认回程可达。NAT 端口重绑定与主动迁移有不同背景,禁止主动迁移的参数也不应简单理解为拒绝一切地址变化。RFC 9000:迁移与验证

路径可能有新的 RTT、容量和 MTU,相关控制状态需要按规则处理,业务可能暂停或失败。只有 CID 能匹配并不足以承诺“应用完全看不到中断”;新路径不支持 UDP、密钥状态丢失或验证超时都可能使连接无法延续。

可靠 STREAM 与不可靠 DATAGRAM

QUIC DATAGRAM 扩展保留应用数据报边界,不对丢失数据进行 STREAM 式重传,也不使用 STREAM 流控;它仍受连接拥塞控制以及协商大小等限制。收到承载它的包的 ACK 也不等于应用已经处理该消息。实时数据可据此选择过期丢弃语义,但不能认为带 QUIC 加密就必定可靠交付。RFC 9221

HTTP/3、DoQ 等为 QUIC 定义各自应用映射。实际选型还需看 UDP 可达性、实现成熟度、可观测性与资源消耗;用户态实现方便演进,也不自动保证比内核 TCP 更省 CPU。

本地复算:编号、流控和探测预算

from math import isclose

def stream_id(index,server=False,unidirectional=False):
    return (index<<2) | int(server) | (int(unidirectional)<<1)
assert [stream_id(i) for i in range(4)] == [0,4,8,12]
assert [stream_id(i,True,True) for i in range(4)] == [3,7,11,15]
assert 65535-20-8 == 65507
assert 1200+8+40 == 1248
assert min(10000-4000-3000,1000) == 1000
assert 1200*3 == 3600
srtt,latest,rttvar,ack_delay,granularity = .040,.050,.005,.025,.001
loss_delay = max(granularity,9/8*max(srtt,latest))
pto = srtt+max(4*rttvar,granularity)+ack_delay
assert isclose(loss_delay,.05625)
assert isclose(pto,.085)
# 包 10 的内容重传进包 14:流偏移不变,发送包号不同。
sent = {10:(0,1000,500),14:(0,1000,500)}
assert sent[10] == sent[14] and 10 != 14
print('UDP/Initial 大小、流 ID、额度、阈值与 PTO 算例通过')

这些断言验证局部字段和预算,不实现完整包保护、握手或丢失检测状态机;未启动真实 QUIC 连接或网络迁移。

继续查阅

  • TCP:字节序号、窗口与恢复的对照。
  • 端口与 NAT 穿透:映射、过滤和路径建立。
  • HTTP/3:QUIC 之上的 HTTP 流与应用依赖。