Convert Figma logo to code with AI

cloudflare logoquiche

🥧 Savoury implementation of the QUIC transport protocol and HTTP/3

9,400
709
9,400
240

Top Related Projects

Mirror of BoringSSL

4,046

Cross-platform, C implementation of the IETF QUIC protocol, exposed to C, C++, C# and Rust.

10,041

A QUIC implementation in pure Go

1,673

QUIC and HTTP/3 implementation in Python

1,150

ngtcp2 project is an effort to implement IETF QUIC protocol

Quick Overview

Quiche is an open-source implementation of the QUIC transport protocol and HTTP/3 written in Rust. It provides a low-level API for building QUIC and HTTP/3 applications, focusing on performance, security, and flexibility.

Pros

  • High performance and efficiency due to Rust implementation
  • Comprehensive support for QUIC and HTTP/3 protocols
  • Cross-platform compatibility
  • Active development and maintenance by Cloudflare

Cons

  • Steep learning curve for developers unfamiliar with Rust
  • Limited high-level abstractions compared to some other networking libraries
  • Documentation could be more extensive for beginners

Code Examples

  1. Creating a QUIC client connection:
let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
config.set_application_protos(b"\x05h3-29\x05h3-28\x05h3-27\x08http/0.9")?;

let mut conn = quiche::connect(None, &scid, local_addr, peer_addr, &mut config)?;
  1. Sending data on a QUIC stream:
let mut buf = [0; 65535];
let (write, send_info) = conn.send(&mut buf)?;
socket.send_to(&buf[..write], send_info.to)?;
  1. Handling incoming QUIC packets:
let (read, from) = socket.recv_from(&mut buf)?;
let recv_info = quiche::RecvInfo { from };
conn.recv(&mut buf[..read], recv_info)?;
  1. Creating an HTTP/3 client:
let mut http3_conn = quiche::h3::Connection::new(&mut conn)?;
let req = vec![
    quiche::h3::Header::new(b":method", b"GET"),
    quiche::h3::Header::new(b":scheme", b"https"),
    quiche::h3::Header::new(b":authority", b"example.com"),
    quiche::h3::Header::new(b":path", b"/"),
];
http3_conn.send_request(&mut conn, &req, true)?;

Getting Started

To use Quiche in your Rust project, add the following to your Cargo.toml:

[dependencies]
quiche = "0.17.0"

Then, in your Rust code, you can import and use Quiche:

use quiche;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
    // ... rest of your QUIC/HTTP3 implementation
    Ok(())
}

For more detailed usage instructions and examples, refer to the Quiche documentation and the project's GitHub repository.

Competitor Comparisons

Mirror of BoringSSL

Pros of BoringSSL

  • More mature and widely adopted, with extensive use in Google products
  • Broader cryptographic functionality beyond TLS/QUIC
  • Larger community and more frequent updates

Cons of BoringSSL

  • Larger codebase, potentially more complex to integrate
  • Less focused on QUIC-specific optimizations
  • May include unnecessary features for projects only needing QUIC support

Code Comparison

Quiche (QUIC-focused):

int quiche_conn_send(quiche_conn *conn, uint8_t *out, size_t out_len);

BoringSSL (general-purpose crypto):

int SSL_write(SSL *ssl, const void *buf, int num);

Summary

Quiche is a focused QUIC implementation, while BoringSSL is a comprehensive cryptographic library. Quiche offers simplicity and QUIC-specific optimizations, whereas BoringSSL provides a broader range of cryptographic functions with wider adoption. The choice between them depends on project requirements: Quiche for QUIC-centric applications, BoringSSL for more general cryptographic needs or projects already integrated with OpenSSL.

4,046

Cross-platform, C implementation of the IETF QUIC protocol, exposed to C, C++, C# and Rust.

Pros of msquic

  • More extensive documentation and examples
  • Broader platform support, including Windows and various Linux distributions
  • Active development with frequent updates and releases

Cons of msquic

  • Larger codebase, potentially more complex to understand and maintain
  • Slower release cycle compared to quiche
  • May have a steeper learning curve for newcomers

Code Comparison

msquic:

QUIC_STATUS
QUIC_API
MsQuicOpen(
    _Out_ const QUIC_API_TABLE** QuicAPI
    )
{
    return MsQuicOpenVersion(QUIC_API_VERSION_2, QuicAPI);
}

quiche:

pub fn connect(
    server_name: Option<&str>,
    src_conn_id: &[u8],
    dst_conn_id: &[u8],
    local: SocketAddr,
    peer: SocketAddr,
    config: &mut Config,
) -> Result<Connection>

The code snippets show different approaches to initializing QUIC connections. msquic uses a C-style API with function pointers, while quiche employs a more modern Rust-style approach with structured parameters.

Both projects aim to provide QUIC protocol implementations, but they cater to different ecosystems and programming paradigms. msquic offers broader platform support and extensive documentation, while quiche focuses on a more lightweight and Rust-centric approach.

10,041

A QUIC implementation in pure Go

Pros of quic-go

  • Written in Go, offering better cross-platform compatibility and easier integration with Go-based projects
  • More active community with frequent updates and contributions
  • Extensive documentation and examples for easier implementation

Cons of quic-go

  • Generally slower performance compared to Quiche's Rust implementation
  • Less optimized for high-performance scenarios and large-scale deployments
  • May have higher memory usage due to Go's garbage collection

Code Comparison

Quiche (Rust):

let mut config = Config::new(PROTOCOL_VERSION)?;
config.set_application_protos(b"\x0ahttp/1.1")?;
config.set_max_idle_timeout(5000);
config.set_max_udp_payload_size(1350);
config.set_initial_max_data(10_000_000);

quic-go (Go):

config := &quic.Config{
    MaxIdleTimeout: 5 * time.Second,
    MaxIncomingStreams: 100,
    KeepAlive: true,
}
session, err := quic.DialAddr("example.com:443", tlsConf, config)

Both implementations offer similar configuration options, but Quiche's Rust code tends to be more verbose while quic-go's Go code is more concise. Quiche provides finer-grained control over protocol-specific settings, while quic-go offers a more high-level API that abstracts some of the underlying complexities.

1,673

QUIC and HTTP/3 implementation in Python

Pros of aioquic

  • Written in pure Python, making it more accessible for Python developers
  • Integrates well with asyncio, allowing for efficient asynchronous programming
  • Includes both client and server implementations

Cons of aioquic

  • May have lower performance compared to Quiche's Rust implementation
  • Less battle-tested in large-scale production environments
  • Smaller community and fewer contributors

Code Comparison

aioquic (Python):

async def handle_connection(connection):
    while True:
        event = await connection.wait_event()
        if isinstance(event, StreamDataReceived):
            stream_id = event.stream_id
            data = event.data
            # Process received data

Quiche (Rust):

fn handle_connection(conn: &mut quiche::Connection) -> Result<()> {
    loop {
        match conn.poll() {
            Ok((stream_id, data)) => {
                // Process received data
            },
            Err(quiche::Error::Done) => break,
            Err(e) => return Err(e.into()),
        }
    }
    Ok(())
}

Both implementations provide similar functionality for handling QUIC connections, but aioquic leverages Python's async/await syntax, while Quiche uses Rust's pattern matching and error handling.

1,150

ngtcp2 project is an effort to implement IETF QUIC protocol

Pros of ngtcp2

  • More focused on QUIC protocol implementation, potentially offering better specialization
  • Extensive test suite, indicating robust quality assurance
  • Actively maintained with frequent updates and contributions

Cons of ngtcp2

  • Less comprehensive feature set compared to quiche
  • Steeper learning curve for integration into existing projects
  • Limited documentation for advanced use cases

Code Comparison

ngtcp2:

ngtcp2_conn *conn;
ngtcp2_conn_client_new(&conn, dcid, &params, &callbacks, &settings, NULL);
ngtcp2_conn_handshake(conn, buf, &pktlen, NULL, 0, timestamp);

quiche:

let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
config.set_application_protos(b"\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9")?;
let scid = quiche::ConnectionId::from_ref(&[0; quiche::MAX_CONN_ID_LEN]);
let mut conn = quiche::Connection::connect(None, &scid, local, peer, &mut config)?;

Both repositories implement QUIC protocol, but ngtcp2 focuses on a C implementation while quiche uses Rust. ngtcp2 provides a lower-level API, requiring more manual management, while quiche offers a higher-level abstraction with built-in configuration options. quiche's API appears more user-friendly for quick integration, whereas ngtcp2 might offer more fine-grained control at the cost of complexity.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

quiche

crates.io docs.rs license build

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.

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.

NGINX (unofficial)

quiche can be integrated into NGINX using an unofficial patch to provide support for 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.

After cloning the project according to the command mentioned in the building section, the client can be run as follows:

 $ 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.

Configuring connections

The first step in establishing a QUIC connection using quiche is creating a Config object:

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 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.

Connection setup

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, 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:

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:

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.

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, 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).

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:

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);
        }
    }
}

HTTP/3

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 [quiche/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).

Calling quiche from C/C++

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.

Note that in order to enable the FFI API, the ffi feature must be enabled (it is disabled by default), by passing --features ffi to cargo.

Building

quiche requires Rust 1.67 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

Alternatively you can use OpenSSL/quictls. To enable quiche to use this vendor the openssl feature can be added to the --feature list. Be aware that 0-RTT is not supported if this vendor is used.

Building for Android

Building quiche for Android (NDK version 19 or higher, 21 recommended), can be done using cargo-ndk (v2.0 or later).

First the Android NDK needs to be installed, either using Android Studio or directly, and the ANDROID_NDK_HOME environment variable needs to be set to the NDK installation path, e.g.:

 $ export ANDROID_NDK_HOME=/usr/local/share/android-ndk

Then the Rust toolchain for the Android architectures needed can be installed as follows:

 $ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android

Note that the minimum API level is 21 for all target architectures.

cargo-ndk (v2.0 or later) also needs to be installed:

 $ cargo install cargo-ndk

Finally the quiche library can be built using the following procedure. Note that the -t <architecture> and -p <NDK version> options are mandatory.

 $ cargo ndk -t arm64-v8a -p 21 -- build --features ffi

See build_android_ndk19.sh for more information.

Building for iOS

To build quiche for iOS, you need the following:

  • Install Xcode command-line tools. You can install them with Xcode or with the following command:
 $ xcode-select --install
  • Install the Rust toolchain for iOS architectures:
 $ rustup target add aarch64-apple-ios x86_64-apple-ios
  • Install cargo-lipo:
 $ cargo install cargo-lipo

To build libquiche, run the following command:

 $ cargo lipo --features ffi

or

 $ cargo lipo --features ffi --release

iOS build is tested in Xcode 10.1 and Xcode 11.2.

Building Docker images

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

Copyright (C) 2018-2019, Cloudflare, Inc.

See COPYING for the license.