commit | 75c62c1fe97578173b74f16717a7fe9f2d34d5b0 | [log] [tgz] |
---|---|---|
author | Lucas Pardue <lucaspardue.24.7@gmail.com> | Fri Oct 09 18:32:05 2020 +0100 |
committer | Alessandro Ghedini <alessandro@ghedini.me> | Fri Oct 09 22:54:53 2020 +0100 |
tree | b3d3bc06a601c4164bc15211aacf7402ed1b1224 | |
parent | 48adbb96c9b3bbd3f7ab317a6d4fb71a5ab7d5e2 [diff] |
dgram: initial support for DATAGRAM extension With this change, we introduce support for the DATAGRAM frame extension specified in draft-ietf-quic-datagram-01 and draft-schinazi-quic-h3-datagram-04. Several API methods have been added to the quiche transport library: * `enable_dgram()` enables the optional QUIC extension for a connection and controls internal queue sizes (see below). * `dgram_max_writable_len()` returns the size of the largest DATAGRAM frame payload that can be sent on the connection. * `dgram_send()` buffers the provided payload data in quiche, a subsequent call to `send()` constructs a DATAGRAM frame and QUIC packet. * `dgram_purge_outgoing()` purges buffered payloads matching a predicate. * `dgram_recv()` extracts payload data buffered in quiche from a prior call to `recv()`. Internally, a send and receive queue are added to quiche for the purposes of buffering the payload of DATAGRAM frames. By their specification, these frames are not flow controlled by QUIC but because of quiche's API design we benefit some ability to enqueue/dequeue application data independent from the packetization and network loop. The size of queues is configurable in order for applications to tailor things to their needs. Several API methods have been added or updated to the quiche HTTP/3 library, which internally builds on the transport library: * `dgram_max_writable_len()` returns the size of the largest DATAGRAM frame payload that can be sent on the connection. * `dgram_send()` buffers the payload for a flow ID. * `poll()` processes received DATAGRAMs and generates a `quiche::h3::Event::Datagram` if there is something to read. * `dgram_recv()` extracts payload data buffered in quiche. Previously, quiche-client and quiche-server supported HTTP/0.9, HTTP/3 or both for any given QUIC connection. During TLS handshake, ALPN would select one of these and then the applications would create HttpConn objects to handle request/response. Http09Conn simply invokes quiche transport APIs directly. Http3Conn invokes quiche transport and H3 APIs. With DATAGRAM there are many more permutations. Rather than boil the ocean, this commit introduces support for two modes, with the following CLI parameters that control how datagrams are used: ``` --dgram-proto PROTO DATAGRAM application protocol to use. --dgram-count COUNT Number of DATAGRAMs to send. --dgram-data DATA Data to send for certain types of DATAGRAM application protocol. ``` Valid values for `dgram-proto` are `siduck` and `oneway`. `siduck` works according to the I-D: the endpoints advertise the ALPN siduck, siduck-00 and if negotiated, send dgram_count number of DATAGRAM frames. The client by default sends quack, which can be overridden by the dgram-data parameter. The server simply attempts to respond to every received DATAGRAM with ${value-ack}; it ignores dgram-count and dgram-data. The client counts sent quacks and received quack acks and reports this. No HTTP requests or responses are made in this mode. `oneway` layers on top of HTTP/3. Endpoints unilaterally send dgram-count amount of HTTP/3 DATAGRAMS to each other and no accounting is done. Clients send on flow ID 0, server on flow ID 1. By default the client sends quack, the server sends brrr. Meanwhile, the apps send requests/responses in accordance with all existing CLI parameters. Fixes #430, #573. Co-authored-by: Marco Mastropaolo <marco@mastropaolo.com> (@xanathar)
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.
A live QUIC server based on quiche is available at https://quic.tech:4433/
, and an HTTP/3 one at https://quic.tech:8443/
, that can be used for experimentation.
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 powers Cloudflare edge network's HTTP/3 support.
quiche can be integrated into curl to provide support for HTTP/3.
quiche can be integrated into NGINX using an unofficial patch to provide support for HTTP/3.
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.
The client can be run as follows:
$ cargo run --manifest-path=tools/apps/Cargo.toml --bin quiche-client -- https://quic.tech:8443/
while the server can be run as follows:
$ cargo run --manifest-path=tools/apps/Cargo.toml --bin quiche-server -- \ --cert tools/apps/src/bin/cert.crt \ --key tools/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.
The first step in establishing a QUIC connection using quiche is creating a configuration object:
let config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
This is shared among multiple connections and can be used to configure a QUIC endpoint.
On the client-side the connect()
utility function can be used to create a new connection, while accept()
is for servers:
// Client connection. let conn = quiche::connect(Some(&server_name), &scid, &mut config)?; // Server connection. let conn = quiche::accept(&scid, None, &mut config)?;
Using the connection's recv()
method the application can process incoming packets that belong to that connection from the network:
loop { let read = socket.recv(&mut buf).unwrap(); let read = match conn.recv(&mut buf[..read]) { Ok(v) => v, Err(e) => { // An error occurred, handle it. break; }, }; }
Outgoing packet are generated using the connection's send()
method instead:
loop { let write = 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(&out[..write]).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.
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:
// Timeout expired, handle it. conn.on_timeout(); // Send more packets as needed after timeout. loop { let write = 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(&out[..write]).unwrap(); }
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:
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 all the streams that have outstanding data to read.
The stream_recv()
method can then be used to retrieve the application data from the readable stream:
if conn.is_established() { // Iterate over readable streams. for stream_id in conn.readable() { // Stream is readable, read until there's no more data. while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) { println!("Got {} bytes on stream {}", read, stream_id); } } }
The quiche HTTP/3 module provides a high level API for sending and receiving HTTP requests and responses on top of the QUIC transport protocol.
Have a look at the examples/ directory for more complete examples on how to use the quiche API, including examples on how to use quiche in C/C++ applications (see below for more information).
quiche exposes a thin C API on top of the Rust API that can be used to more easily integrate quiche into C/C++ applications (as well as in other languages that allow calling C APIs via some form of FFI). The C API follows the same design of the Rust one, modulo the constraints imposed by the C language itself.
When running cargo build
, a static library called libquiche.a
will be built automatically alongside the Rust one. This is fully stand-alone and can be linked directly into C/C++ applications.
quiche requires Rust 1.39 or later to build. The latest stable Rust release can be installed using rustup.
Once the Rust build environment is setup, the quiche source code can be fetched using git:
$ git clone --recursive https://github.com/cloudflare/quiche
and then built using cargo:
$ cargo build --examples
cargo can also be used to run the testsuite:
$ cargo test
Note that BoringSSL, which is used to implement QUIC's cryptographic handshake based on TLS, needs to be built and linked to quiche. This is done automatically when building quiche using cargo, but requires the cmake
command to be available during the build process. On Windows you also need NASM. The official BoringSSL documentation has more details.
In alternative you can use your own custom build of BoringSSL by configuring the BoringSSL directory with the QUICHE_BSSL_PATH
environment variable:
$ QUICHE_BSSL_PATH="/path/to/boringssl" cargo build --examples
To build quiche for Android, you need the following:
ANDROID_NDK_HOME
environment variable to NDK path, e.g.$ export ANDROID_NDK_HOME=/usr/local/share/android-ndk
$ rustup target add aarch64-linux-android arm-linux-androideabi armv7-linux-androideabi i686-linux-android x86_64-linux-android
Note that the minimum API level is 21 for all target architectures.
Depending on the NDK version used, you can take one of the following procedures:
For NDK version 19 or higher (21 recommended), you can build in a simpler way using cargo-ndk. You need to install cargo-ndk first.
$ cargo install cargo-ndk
You can build the quiche library using the following procedure. Note that --target
and --android-platform
are mandatory.
$ cargo ndk --target aarch64-linux-android --android-platform 21 -- build
See build_android_ndk19.sh for more information.
Note that building with NDK version 18 appears to be broken.
If you need to use NDK version < 18 (gcc), you can build quiche in the following way.
To prepare the cross-compiling toolchain, run the following command:
$ tools/android/setup_android.sh
It will create a standalone toolchain for arm64/arm/x86 architectures under the $TOOLCHAIN_DIR/arch
directory. If you didn't set TOOLCHAIN_DIR
environment variable, the current directory will be used.
After it run successfully, run the following script to build libquiche:
$ tools/android/build_android.sh --features ndk-old-gcc
It will build binaries for aarch64, armv7 and i686. You can pass parameters to this script for cargo build. For example if you want to build a release binary with verbose logs, do the following:
$ tools/android/build_android.sh --features ndk-old-gcc --release -vv
To build quiche for iOS, you need the following:
$ xcode-select --install
$ rustup target add aarch64-apple-ios x86_64-apple-ios
cargo-lipo
:$ cargo install cargo-lipo
To build libquiche, run the following command:
$ cargo lipo
or
$ cargo lipo --release
iOS build is tested in Xcode 10.1 and Xcode 11.2.
In order to build the Docker images, simply run the following command:
$ make docker-build
You can find the quiche Docker images on the following Docker Hub repositories:
The latest
tag will be updated whenever quiche master branch updates.
cloudflare/quiche
Provides a server and client installed in /usr/local/bin.
cloudflare/quiche-qns
Provides the script to test quiche within the quic-interop-runner.
Copyright (C) 2018-2019, Cloudflare, Inc.
See COPYING for the license.