项目简介
quiche 是 Rust 实现的 QUIC 传输协议与 HTTP/3 库,提供处理数据包和连接状态的底层 API。应用需自行负责 I/O、事件循环与定时器。它已用于 Cloudflare 边缘网络、Android DNS over HTTP/3 以及 curl 的 HTTP/3 支持。
README 预览
\n\n[](https://crates.io/crates/quiche)\n[](https://docs.rs/quiche)\n[](https://opensource.org/licenses/BSD-2-Clause)\n\n\n[quiche] is an implementation of the QUIC transport protocol and HTTP/3 as\nspecified by the [IETF]. It provides a low level API for processing QUIC packets\nand handling connection state. The application is responsible for providing I/O\n(e.g. sockets handling) as well as an event loop with support for timers.\n\nFor more information on how quiche came about and some insights into its design\nyou can read a [post] on Cloudflare's blog that goes into some more detail.\n\n[quiche]: https://docs.quic.tech/quiche/\n[ietf]: https://quicwg.org/\n[post]: https://blog.cloudflare.com/enjoy-a-slice-of-quic-and-rust/\n\nWho uses quiche?\n----------------\n\n### Cloudflare\n\nquiche powers Cloudflare edge network's [HTTP/3 support][cloudflare-http3]. The\n[cloudflare-quic.com](https://cloudflare-quic.com) website can be used for\ntesting and experimentation.\n\n### Android\n\nAndroid's DNS resolver uses quiche to [implement DNS over HTTP/3][android-http3].\n\n### curl\n\nquiche can be [integrated into curl][curl-http3] to provide support for HTTP/3.\n\n[cloudflare-http3]: https://blog.cloudflare.com/http3-the-past-present-and-future/\n[android-http3]: https://security.googleblog.com/2022/07/dns-over-http3-in-android.html\n[curl-http3]: https://github.com/curl/curl/blob/master/docs/HTTP3.md#quiche-version\n\nGetting Started\n---------------\n\n### Command-line apps\n\nBefore diving into the quiche API, here are a few examples on how to use the\nquiche tools provided as part of the [quiche-apps](apps/) crate. These are not\nsuitable for production environments; see [disclaimers and\nnotes](#disclaimers-and-notes).\n\nAfter cloning the project according to the command mentioned in the [building](#building) section, the client can be run as follows:\n\n```bash\n $ cargo run --bin quiche-client -- https://cloudflare-quic.com/\n```\n\nwhile the server can be run
常见问题 (5)
故障排除如何修复 quiche 在发送方并未超过 MAX_DATA 的情况下,仍以 FLOW_CONTROL_ERROR 关闭长时间运行的 QUIC 批量传输的问题?
这是由 quiche issue #2696 引起的。零长度的 non-FIN STREAM 帧会推进流的最大接收偏移量,并计入连接级流量控制,但 RecvBuf::write() 会丢弃该空帧而不推进流的高水位标记。当真实数据随后到达该偏移量时,相同的字节会被再次计费,最终使有效流量看起来超过了 MAX_DATA。该问题在 revision 0b1a89af 上复现,通常发生在长传输的后期,此时较大的流 ID/偏移量会生成一个恰好填满剩余数据包容量的空 STREAM 帧。修复接收端的方法是更新 RecvBuf::write() 中空 non-FIN 的提前返回路径,使其在返回前同步高水位标记:self.len = cmp::max(self.len, buf.max_off());。同时应用发送端修复:当流帧的 emit() 返回 (0, false) 时,不要编码 STREAM 帧;应将此情况视为受数据包大小限制而非受应用限制,这样 BBR2 样本就不会被标记为 app-limited。重新构建并重新部署 quiche,或升级到包含 issue #2696 的 PR 的发布版本。如果只能进行临时缓解且通信双方均可控,部署发送端更改可以防止生成这些冗余的空帧。
故障排除如何修复 quiche 零长度 STREAM FIN 被丢弃导致对端永远收不到 EOF 的问题?
这是 quiche 0.29.2 中的一个 bug:当流的所有字节都被确认后,SendBuf::is_complete() 就会认为该流已完成,即使携带 FIN 的 STREAM 帧从未被发送或确认。因此,在已被确认的偏移量处执行的零长度 stream_send(id, b"", true) 可能会被丢弃,而对端将永远无法得知流的最终大小。
请升级到包含修复 stream: track FIN acknowledgements separately 的版本。该修复将 FIN 的确认独立于已确认的字节范围进行跟踪:流会被保留,且空的 FIN 保持可刷新/可重传状态,直到位于 fin_off 处、FIN=true 的 STREAM 帧被确认为止。
临时解决方法:当还有最终数据需要发送时,将数据与 FIN 合并发送,而不是在之后再单独发送一个空的 FIN:
// 避免:
stream_send(id, b"response", false)?;
// ... 数据被确认之后 ...
stream_send(id, b"", true)?;
// 推荐:
stream_send(id, b"response", true)?;
对于真正的零长度流,必须升级或应用 FIN 跟踪补丁,因为不存在可以让 FIN 捎带的非空数据帧。
故障排除如何防止 quiche 超出 RFC 9000 规定的 3 倍抗放大限制?
不要将抗放大系数配置为大于 3 的值。quiche 默认使用符合 RFC 的值 3,因此使用默认配置的应用程序是安全的。如果你在 Rust 中调用 config.set_max_amplification_factor(v) 或在 C 中调用 quiche_config_set_max_amplification_factor(),请确保 v <= 3;使用 3 表示协议允许的最大值,或使用更小的值以施加更严格的限制。示例:config.set_max_amplification_factor(3);。诸如 4 的值会在地址验证之前故意允许超过 3 * received_bytes 的数据量,这违反了 RFC 9000 §8.1/RFC 9001 §9.3。如果维护必须强制合规的 fork,请在 setter 中拒绝或钳制输入,例如 self.max_amplification_factor = v.min(3);。
故障排除如何修复 quiche 在收到重复的 RETIRE_CONNECTION_ID 帧后以 PROTOCOL VIOLATION 关闭 QUIC 连接的问题?
将 quiche 升级到包含 issue #1833 修复的版本,或应用/cherry-pick 链接的 PR 并重新构建。如果是从 Git 构建,请使用包含重复 RETIRE_CONNECTION_ID 修复的 quiche 修订版本。
正确的行为是:
1. 首先在活跃 connection ID 列表中查找序列号。
2. 如果该序列号已被注销或未知,则将该帧作为有效重传予以忽略。
3. 仅在 ID 处于活跃状态时才注销该 ID。
4. 仅当注销一个活跃 ID 后不再剩余任何可用的 connection ID 时,才返回协议违规。
在 quiche/src/cid.rs 中,必须先确认所通告的序列号当前处于活跃状态,然后才能执行 inner.len() 可用性检查。RFC 9000 允许重复的 RETIRE_CONNECTION_ID 帧,因为该帧可能在数据包丢失后被重传。
临时解决方案:使用已打补丁的 quiche 构建版本,或确保对端有额外的活跃 connection ID 可用,从而使注销操作不会耗尽 connection ID 集合。
故障排除如何修复 tokio-quiche 的 listener 在 accept stream 被 drop 后未释放其 UDP socket 的问题?
此问题影响 tokio-quiche 0.20.0。路由器会检查 accept_sink.is_closed(),但空闲的监听器不会为流关闭注册 waker,因此它会一直阻塞在 UDP/连接事件的等待中,并继续持有 socket。请应用上游修复:在路由器的 poll/select 循环中保留并轮询 accept 通道的 Sender::closed() future;这样,当返回的 accept 流被丢弃时,即可唤醒空闲的监听器并使其正常关闭。请使用包含 PR fix(tokio-quiche): wake idle listener on stream drop 的发布版本,或基于该 PR 为 tokio-quiche 打补丁/构建已修复的提交。作为临时解决方案,接收一个 UDP 数据包可以唤醒阻塞的路由器,使其检测到关闭标志,但该补丁才是可靠的修复方法。