HTTP/3 QUIC发送RESET_STREAM帧时Final Size字段设置咨询
问题场景
- 流发送方已连续发送总计10000字节的流数据
- 接收方返回ACK确认已收到0-8000字节范围的数据,发送方已收到该确认
- 发送方此时需要发送RESET_STREAM(流重置)帧,需确认帧中
final size(最终大小)字段的正确取值,候选值为10000、8000
规范判定结论
Final Size正确取值
根据RFC 9000第4.5节对Stream Final Size的定义:
流的最终大小为该流消耗的流量控制额度。若流上所有连续字节均被发送一次,最终大小即为已发送的字节总数;通用规则为,最终大小是流上已发送的最大偏移字节的偏移值加1,未发送任何字节时取值为0。
本场景中发送方已经发出的流数据总长度为10000字节,对应最大字节偏移为9999(偏移从0开始计数),因此Final Size的正确取值为10000,对应选项a。接收方的ACK确认进度不影响该字段的取值,Final Size是发送方侧对流发送总长度的权威声明,和对端实际接收进度无关。
发送RESET_STREAM后的流发送规则
咨询者提出的“发送RESET_STREAM帧后不得再在对应流上进行任何STREAM帧的新发与重传”的理解完全正确。根据RFC 9000第19.4节的强制规定:
端点发送RESET_STREAM帧后,需停止对应流上STREAM帧的发送与重传;RESET_STREAM的接收方可丢弃该流上已收到的所有数据。
Nginx QUIC实现的对应缺陷
目前Nginx的QUIC实现存在不符合上述规范的逻辑问题:发送RESET_STREAM帧后仍可能触发旧STREAM帧的重传。
问题根因在于:Nginx HTTP/3实现将所有待发送帧写入ctx->frames队列后,对应的qs流对象会被提前删除。重传逻辑遍历到待重传的STREAM帧时,无法通过流ID找到对应的流对象,原本用于拦截重置流帧重传的状态判断分支失效。
相关问题代码片段如下:
void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { case NGX_QUIC_FT_STREAM: qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); if (qs) { if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) { ngx_quic_free_frame(c, f); break; } } }
上述代码仅在能查到流对象qs时,才会判断流是否已进入重置状态、释放待重传的STREAM帧;如果流对象已被删除,判断逻辑直接跳过,待重传的STREAM帧会被正常发出,违反RFC规范要求。
内容的提问来源于stack exchange,提问作者mingkun bian

