Open-Source Wikis

/

OpenSSL

/

Features

/

QUIC

openssl/openssl

QUIC

Active contributors: Hugo Landau, Tomáš Kasal, Matt Caswell, Neil Horman, Tomas Mraz

Purpose

libssl ships an in-tree QUIC v1 (RFC 9000–9002) implementation. Both client and server are supported (server stack added in 3.5). The TLS handshake reuses the existing TLS 1.3 state machine in ssl/statem/ via a custom record method that delivers TLS messages as QUIC CRYPTO frames; the rest of the protocol — packet protection, congestion control, loss recovery, stream multiplexing, flow control, connection migration, retry, version negotiation — lives entirely in ssl/quic/.

OpenSSL's QUIC has a deliberately narrow interface to the application:

  • The application supplies a UDP socket (or a BIO_s_datagram / custom datagram BIO).
  • It uses SSL_* APIs to drive streams, handshake, and connection lifecycle.
  • Internal threads can optionally be enabled to drive the protocol while the application is idle.

Directory layout

ssl/quic/
├── quic_impl.c           168 KB  -- the SSL_* API surface for QUIC
├── quic_channel.c        140 KB  -- per-connection state machine (handshake -> idle -> closing)
├── quic_txp.c            111 KB  -- transmission processor (assembles outbound packets)
├── quic_ackm.c            59 KB  -- ACK manager: loss detection, RTT estimation, ack-eliciting tracking
├── quic_record_rx.c       50 KB  -- AEAD-protected packet receive/decrypt path
├── quic_rx_depack.c       50 KB  -- post-decrypt frame demultiplexer
├── quic_record_tx.c       30 KB  -- AEAD-protected packet transmit path
├── quic_record_shared.c, quic_record_util.c
├── quic_port.c            62 KB  -- the listening "port" object (multiplexes connections to one UDP socket)
├── quic_stream_map.c      26 KB  -- the per-connection stream table
├── quic_demux.c           14 KB  -- incoming-packet demultiplexer (matches DCID -> connection)
├── quic_lcidm.c           16 KB  -- Local Connection ID manager
├── quic_rcidm.c           20 KB  -- Remote Connection ID manager
├── quic_srtm.c            16 KB  -- Stateless Reset Token manager
├── quic_srt_gen.c
├── quic_fc.c              10 KB  -- per-stream + per-connection flow control
├── quic_cfq.c              9 KB  -- the "control-frame queue" of frames awaiting transmission
├── quic_fifd.c            10 KB  -- frame-in-flight database
├── quic_sf_list.c, quic_sstream.c, quic_rstream.c -- send/receive stream byte buffers
├── quic_wire.c, quic_wire_pkt.c -- wire-format encode/decode for frames and packets
├── quic_tls.c, quic_tls_api.c   -- the QUIC<->TLS bridge (the OSSL_RECORD_METHOD plumbing)
├── quic_thread_assist.c, quic_reactor.c, quic_reactor_wait_ctx.c -- the optional internal-thread reactor
├── quic_engine.c, quic_engine_local.h -- the top-level engine that owns ports
├── quic_obj.c, quic_obj_local.h       -- the SSL-object polymorphism (TLS-SSL, QUIC-CONN, QUIC-STREAM, QUIC-LISTENER, QUIC-DOMAIN)
├── quic_method.c, quic_local.h, quic_types.c
├── quic_statm.c          -- statistics
├── quic_trace.c, qlog.c, qlog_event_helpers.c, json_enc.c -- qlog tracing (RFC-style JSON event logging)
├── quic_tserver.c        -- a tiny test server harness
├── cc_newreno.c          -- the NewReno congestion controller
├── uint_set.c            -- a sparse-set helper
└── build.info

ssl/rio/
├── poll_builder.c, poll_immediate.c, poll_method.h, poll_builder.h
└── rio_notifier.c

ssl/rio/ ("Reactor I/O") is the asynchronous polling layer used by the QUIC engine to wait on multiple sockets/timers. It abstracts select/poll/epoll/kqueue so the QUIC reactor can be portable.

SSL object polymorphism

OpenSSL extended the SSL type to be a polymorphic handle. Today the same SSL * may be one of:

Kind Created by What it is
TLS-SSL SSL_new(ctx) with a TLS method A TLS 1.x or DTLS connection.
QUIC-CONN SSL_new(ctx) with OSSL_QUIC_client_method() or OSSL_QUIC_server_method() A QUIC connection.
QUIC-STREAM SSL_new_stream(qconn, flags) A bidirectional or unidirectional QUIC stream within a connection.
QUIC-LISTENER SSL_new_listener(ctx) A QUIC listener bound to a UDP socket (server side).
QUIC-DOMAIN SSL_new_domain(ctx) A "domain" grouping multiple listeners under a single event loop.

quic_obj.c and quic_obj_local.h implement the dispatch. Most SSL_* calls work on any kind of object where they make sense (e.g. SSL_read, SSL_write); a smaller set requires a specific kind (SSL_set_blocking_mode, SSL_get_stream_id, SSL_accept_stream).

Lifecycle (client)

sequenceDiagram
    participant App
    participant QC as QUIC-CONN (quic_impl.c, quic_channel.c)
    participant TXP as quic_txp.c
    participant AKM as quic_ackm.c
    participant TLS as TLS state machine (statem_clnt.c)
    participant UDP as BIO_s_datagram
    App->>QC: SSL_new(ctx, OSSL_QUIC_client_method())
    App->>QC: SSL_set_initial_peer_addr(...) ; SSL_set1_host(...)
    App->>QC: SSL_connect()
    QC->>TLS: drive handshake; TLS messages are QUIC CRYPTO frames
    QC->>TXP: queue Initial / Handshake / 1-RTT packets
    TXP->>UDP: BIO_write_ex(datagram)
    UDP-->>QC: BIO_read_ex(datagram)
    QC->>AKM: register sent packets; handle ACK frames
    QC->>TLS: deliver received CRYPTO frames
    TLS-->>QC: keying material via OSSL_RECORD_METHOD callbacks
    QC->>QC: install Initial/Handshake/1-RTT keys
    QC-->>App: handshake complete
    App->>QC: SSL_new_stream / SSL_write / SSL_read

The same pattern holds server-side, with the listener accepting a connection that is delivered to the application via SSL_accept_connection.

QUIC-TLS bridge

ssl/quic/quic_tls.c and quic_tls_api.c implement a custom OSSL_RECORD_METHOD that the TLS state machine uses. Instead of TLS records, the method packages handshake messages into QUIC CRYPTO frames at the appropriate encryption level (Initial, Handshake, 1-RTT). The state machine itself doesn't know it's running over QUIC — it just calls into its record method. Keys are derived in TLS 1.3, exported via tls13_export_keying_material, and installed into quic_record_rx.c / quic_record_tx.c for packet protection.

The encryption levels:

QUIC encryption level Keys derived from Used for
Initial A salt + ConnectionID (RFC 9001 §5.2) First Initial packet (CRYPTO with ClientHello).
Handshake TLS 1.3 handshake_secret Encrypts CRYPTO with EE/Cert/CertVerify/Finished.
1-RTT TLS 1.3 application_secret Encrypts streams and most control frames.
0-RTT TLS 1.3 early_secret Optional; not yet enabled in OpenSSL's default config.

Packet processing pipeline

  1. quic_demux.c reads a UDP datagram via BIO_recv_ex. It parses the QUIC short/long header to extract the Destination Connection ID (DCID).
  2. quic_demux.c looks the DCID up in quic_lcidm.c to find the owning connection (or the listener for a new connection).
  3. quic_record_rx.c decrypts the packet using the key and PN cipher for the relevant encryption level.
  4. quic_rx_depack.c walks the now-plaintext payload and dispatches each frame: STREAM, ACK, MAX_DATA, MAX_STREAM_DATA, NEW_CONNECTION_ID, RETIRE_CONNECTION_ID, HANDSHAKE_DONE, CONNECTION_CLOSE, CRYPTO, NEW_TOKEN, PING, PADDING, RESET_STREAM, STOP_SENDING, PATH_CHALLENGE, PATH_RESPONSE, ….
  5. STREAM frames go to quic_rstream.c's receive buffer.
  6. CRYPTO frames go to the TLS bridge.
  7. ACK frames go to quic_ackm.c to update RTT and confirm in-flight packets.
  8. Stream control frames update flow-control state (quic_fc.c).

The send side mirrors this: quic_txp.c collects pending frames from various queues and the in-flight DB, builds packets at the right encryption level, and writes them via the configured datagram BIO.

Loss recovery and congestion control

ssl/quic/quic_ackm.c is the largest QUIC file outside quic_impl.c because RFC 9002 (loss detection / congestion control) is intricate:

  • Sent-packet history (quic_fifd.c).
  • RTT samples and smoothed RTT.
  • Loss thresholds (kPacketThreshold, kTimeThreshold).
  • ACK frequency hints.
  • ECN handling (basic).

cc_newreno.c is the default congestion controller (a NewReno variant). The CC interface (include/internal/quic_cc.h) is pluggable so future CCAs (BBR, CUBIC) can drop in.

Stream model

Each connection has a stream map (quic_stream_map.c) keyed by stream id. Streams are bidirectional or unidirectional, client- or server-initiated. Each stream has:

  • A receive buffer (quic_rstream.c) reassembled from STREAM frames using the segmented-byte-buffer (quic_sf_list.c).
  • A send buffer (quic_sstream.c).
  • Flow control state.
  • SSL_* operations are implemented as forwards from the parent QUIC-CONN SSL * to the per-stream SSL *.

SSL_read / SSL_write on a QUIC-STREAM are stream byte-stream operations, not record-bounded.

Connection IDs and stateless reset

QUIC supports connection migration: a client may change its source address mid-connection without breaking the connection, because routing is by Destination Connection ID rather than 4-tuple. The local CIDs (quic_lcidm.c) and remote CIDs (quic_rcidm.c) are independent rotating sets. Stateless reset (quic_srtm.c, quic_srt_gen.c) lets a server that loses connection state respond with an unforgeable reset token rather than ignoring the packet.

qlog

ssl/quic/qlog.c and qlog_event_helpers.c emit qlog events (the IETF JSON tracing format for QUIC) to a file. Enable via SSL_QLOG_DIR=/tmp/qlog env var or programmatic API. quic_trace.c is an additional simpler text trace.

Threading

QUIC needs background work: timers fire even when the application isn't calling into it. Two modes:

  1. Application-driven: the application periodically calls SSL_handle_events(ssl) (or any SSL_* operation that drives the reactor). No hidden threads.
  2. Thread assisted: enable SSL_VALUE_QUIC_INTERNAL_THREADS_ENABLE and OpenSSL spawns a helper thread that runs the reactor. The application can do blocking I/O elsewhere.

The reactor itself (quic_reactor.c) runs over the RIO poll abstraction.

Listener / domain

Server-side, multiple connections share one UDP socket. The QUIC-LISTENER owns the socket and the QUIC-PORT (quic_port.c) demuxes incoming packets to existing connections or accepts new ones. A QUIC-DOMAIN aggregates multiple listeners under a single event loop, useful for servers that want to terminate on multiple addresses in one process.

Configuration

  • SSL_set_initial_peer_addr / SSL_set1_host — required client-side knobs.
  • SSL_set_blocking_mode — toggle blocking semantics.
  • SSL_set1_qlog_dir — qlog destination.
  • SSL_set_default_stream_mode — let SSL_read/SSL_write on the connection handle implicit default streams.

Many configuration knobs are exposed via the generic SSL_set/get_value_uint(ssl, class, id, &v) API. See include/openssl/quic.h and doc/man3/SSL_get_value_uint.pod.

Integration points

  • TLS state machine is reused unchanged. The bridge is the QUIC OSSL_RECORD_METHOD.
  • EVP provides every cryptographic primitive (AES-GCM/ChaCha20-Poly1305 for AEAD, HKDF for key derivation).
  • BIO is the application-supplied datagram socket (or BIO_s_dgram_pair for tests).
  • QUIC-GROUP / QUIC-SIGALG are not separate from TLS-GROUP / TLS-SIGALG — QUIC's TLS 1.3 sub-handshake reuses the TLS provider capabilities.

Entry points for modification

  • New congestion controller: implement OSSL_CC_METHOD (include/internal/quic_cc.h), register it; consider gating via SSL_set_value_uint.
  • New transport parameter: extend wire encode/decode in quic_wire.c, the channel state, and the TLS bridge that ferries it via the TLS extension.
  • New frame type: add a wire decoder/encoder in quic_wire.c, a dispatcher branch in quic_rx_depack.c, a producer in quic_txp.c.
  • Server-side knobs: quic_port.c and the listener glue in quic_impl.c.

Documentation

  • README-QUIC.md — top-level user guide.
  • doc/designs/quic-design/*.md — extensive design notes.
  • doc/man7/ossl-guide-quic-introduction.pod and the rest of the ossl-guide-quic-* series.
  • doc/man3/OSSL_QUIC_client_method.pod, SSL_new_stream.pod, SSL_new_listener.pod, SSL_handle_events.pod, …

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

QUIC – OpenSSL wiki | Factory