cloudflare / quiche

Cloudflare / quiche

quiche is an implementation of the QUIC transport protocol and HTTP/3 as specified by the IETF. It provides a low level API for processing QUIC packets and handling connection state. The application is responsible for providing I/O (e.g. sockets handling) as well as an event loop with support for timers. For more information on how quiche came about and some insights into its design you can read a post on Cloudflare’s blog that goes into some more detail. quiche 是 IETF 所指定的 QUIC 传输协议和 HTTP/3 的实现。它提供了一个用于处理 QUIC 数据包和管理连接状态的底层 API。应用程序负责提供 I/O(例如套接字处理)以及支持定时器的事件循环。有关 quiche 的由来及其设计见解的更多信息,您可以阅读 Cloudflare 博客上的一篇文章,其中有更详细的介绍。

Who uses quiche? Cloudflare quiche powers Cloudflare edge network’s HTTP/3 support. The cloudflare-quic.com website can be used for testing and experimentation. Android Android’s DNS resolver uses quiche to implement DNS over HTTP/3. curl quiche can be integrated into curl to provide support for HTTP/3. 谁在使用 quiche?Cloudflare quiche 为 Cloudflare 边缘网络的 HTTP/3 支持提供动力。cloudflare-quic.com 网站可用于测试和实验。Android 的 DNS 解析器使用 quiche 来实现 DNS over HTTP/3。curl 可以集成 quiche 以提供对 HTTP/3 的支持。

Getting Started: Command-line apps. Before diving into the quiche API, here are a few examples on how to use the quiche tools provided as part of the quiche-apps crate. These are not suitable for production environments; see disclaimers and notes. After cloning the project according to the command mentioned in the building section, the client can be run as follows: 入门:命令行应用程序。在深入研究 quiche API 之前,这里有几个关于如何使用 quiche-apps crate 中提供的 quiche 工具的示例。这些工具不适用于生产环境;请参阅免责声明和注意事项。在按照构建部分提到的命令克隆项目后,客户端可以按如下方式运行:

$ cargo run —bin quiche-client — https://cloudflare-quic.com/ while the server can be run as follows: $ cargo run —bin quiche-server — —cert apps/src/bin/cert.crt —key apps/src/bin/cert.key (note that the certificate provided is self-signed and should not be used in production) Use the —help command-line flag to get a more detailed description of each tool’s options. 而服务器可以按如下方式运行:$ cargo run —bin quiche-server — —cert apps/src/bin/cert.crt —key apps/src/bin/cert.key(请注意,提供的证书是自签名的,不应在生产环境中使用)。使用 —help 命令行标志可以获取每个工具选项的更详细描述。

Configuring connections: The first step in establishing a QUIC connection using quiche is creating a Config object: 配置连接:使用 quiche 建立 QUIC 连接的第一步是创建一个 Config 对象:

let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
config.set_application_protos(&[b"example-proto"]);
// Additional configuration specific to application and use case...

The Config object controls important aspects of the QUIC connection such as QUIC version, ALPN IDs, flow control, congestion control, idle timeout and other properties or features. QUIC is a general-purpose transport protocol and there are several configuration properties where there is no reasonable default value. For example, the permitted number of concurrent streams of any particular type is dependent on the application running over QUIC, and other use-case specific concerns. quiche defaults several properties to zero, applications most likely need to set these to something else to satisfy their needs using the following: Config 对象控制 QUIC 连接的重要方面,例如 QUIC 版本、ALPN ID、流量控制、拥塞控制、空闲超时以及其他属性或功能。QUIC 是一种通用传输协议,有几个配置属性没有合理的默认值。例如,任何特定类型的并发流允许数量取决于在 QUIC 上运行的应用程序以及其他特定于用例的考量。quiche 将几个属性默认为零,应用程序很可能需要使用以下方法将它们设置为其他值以满足其需求:

set_initial_max_streams_bidi() set_initial_max_streams_uni() set_initial_max_data() set_initial_max_stream_data_bidi_local() set_initial_max_stream_data_bidi_remote() set_initial_max_stream_data_uni()

Config also holds TLS configuration. This can be changed by mutators on the an existing object, or by constructing a TLS context manually and creating a configuration using with_boring_ssl_ctx_builder(). A configuration object can be shared among multiple connections. Config 还包含 TLS 配置。这可以通过现有对象上的修改器进行更改,或者通过手动构建 TLS 上下文并使用 with_boring_ssl_ctx_builder() 创建配置来更改。一个配置对象可以在多个连接之间共享。

Connection setup: On the client-side the connect() utility function can be used to create a new connection, while accept() is for servers: 连接设置:在客户端,可以使用 connect() 实用函数来创建新连接,而 accept() 则用于服务器:

// Client connection.
let conn = quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?;
// Server connection.
let conn = quiche::accept(&scid, None, local, peer, &mut config)?;

Handling incoming packets: Using the connection’s recv() method the application can process incoming packets that belong to that connection from the network: 处理传入数据包:使用连接的 recv() 方法,应用程序可以处理来自网络且属于该连接的传入数据包:

let to = socket.local_addr().unwrap();
loop {
    let (read, from) = socket.recv_from(&mut buf).unwrap();
    let recv_info = quiche::RecvInfo { from, to };
    let read = match conn.recv(&mut buf[..read], recv_info) {
        Ok(v) => v,
        Err(e) => { // An error occurred, handle it. break; },
    };
}

Generating outgoing packets: Outgoing packet are generated using the connection’s send() method instead: 生成传出数据包:传出数据包则使用连接的 send() 方法生成:

loop {
    let (write, send_info) = match conn.send(&mut out) {
        Ok(v) => v,
        Err(quiche::Error::Done) => { // Done writing. break; },
        Err(e) => { // An error occurred, handle it. break; },
    };
    socket.send_to(&out[..write], &send_info.to).unwrap();
}

When packets are sent, the application is responsible for maintaining a timer to react to time-based connection events. The timer expiration can be obtained using the connection’s timeout() method. 当发送数据包时,应用程序负责维护一个定时器,以响应基于时间的连接事件。定时器到期时间可以使用连接的 timeout() 方法获取。

let timeout = conn.timeout();

The application is responsible for providing a timer implementation, which can be specific to the operating system or networking framework used. When a timer expires, the connection’s on_timeout() method should be called, after which additional packets might need to be sent on the network: 应用程序负责提供定时器实现,这可以针对所使用的操作系统或网络框架进行定制。当定时器到期时,应调用连接的 on_timeout() 方法,之后可能需要在网络上发送额外的数据包:

// Timeout expired, handle it.
conn.on_timeout();
// Send more packets as needed after timeout.
loop {
    let (write, send_info) = match conn.send(&mut out) {
        Ok(v) => v,
        Err(quiche::Error::Done) => { // Done writing. break; },
        Err(e) => { // An error occurred, handle it. break; },
    };
    socket.send_to(&out[..write], &send_info.to).unwrap();
}

Pacing: It is recommended that applications pace sending of outgoing packets to avoid creating packet bursts that could cause short-term congestion and losses in the network. quiche exposes pacing hints for outgoing packets through the [at] field of the [SendInfo] structure that is returned by the send() method. This field represents the time when a specific packet should be sent into the network. Applications can use these hints by artificially delaying the sending of packets through platform-specific mechanisms (such as the SO_TXTIME socket option on Linux), or custom methods (for example by using user-space timers). 步调控制(Pacing):建议应用程序对传出数据包的发送进行步调控制,以避免产生可能导致网络短期拥塞和丢包的数据包突发。quiche 通过 send() 方法返回的 [SendInfo] 结构中的 [at] 字段公开了传出数据包的步调提示。该字段表示特定数据包应发送到网络的时间。应用程序可以通过平台特定机制(例如 Linux 上的 SO_TXTIME 套接字选项)或自定义方法(例如使用用户空间定时器)人为延迟数据包发送来利用这些提示。

Sending and receiving stream data: After some back and forth, the connection will complete its handshake and will be ready for sending or receiving application data. Data can be sent on a stream by using the stream_send() method: 发送和接收流数据:经过几次往返后,连接将完成握手,并准备好发送或接收应用程序数据。可以使用 stream_send() 方法在流上发送数据:

if conn.is_established() {
    // Handshake completed, send some data on stream 0.
    conn.stream_send(0, b"hello", true)?;
}

The application can check whether there are any readable streams by using the connection’s readable() method, which returns an iterator over 应用程序可以使用连接的 readable() 方法检查是否有任何可读流,该方法返回一个迭代器……