Skip to content

HTTP/3 (QUIC) support for net.http #27675

Description

@quaesitor-scientiam

Summary

Add HTTP/3 support to net.http, built on a new QUIC transport implementation
(vlib/net/quic/). This tracks a large, multi-phase effort matching the existing
HTTP/1.1 + HTTP/2 quality bar (connection pooling, conformance testing) already
in the repo, targeting Linux, Windows, and macOS.

Why a new TLS 1.3 handshake instead of using vendored mbedTLS

QUIC (RFC 9001) needs per-encryption-level secret export (Initial/Handshake/
1-RTT) and a way to carry TLS handshake bytes over QUIC CRYPTO frames instead of
a normal TLS record layer/socket.

The vendored mbedTLS (3.6.6) has no QUIC support at all — confirmed by
reading the vendored source (only the generic mbedtls_ssl_set_export_keys_cb
exists; there is no record-layer bypass and no way to carry the
quic_transport_parameters extension). This isn't specific to our vendored
version: it's a long-standing, unresolved upstream limitation
(Mbed-TLS/mbedtls#6178,
#4731). For comparison,
ngtcp2 (the reference QUIC implementation) supports OpenSSL, LibreSSL,
GnuTLS, BoringSSL, AWS-LC, picotls, and wolfSSL as TLS backends — mbedTLS is
absent from that list for exactly this reason.

Patching mbedTLS ourselves to add the missing hooks was estimated at
~1150–2200 LOC inside the library's most actively-maintained, most complex
files (ssl_tls13_client.c/ssl_tls13_server.c), with real ongoing rebase risk
against every upstream mbedTLS release.

Decision: implement a QUIC-scoped TLS 1.3 handshake (RFC 8446) from scratch in
pure V
, reusing already-existing, already-tested V crypto (X25519 ECDH,
Ed25519, HKDF, AES-GCM, SHA-256), and delegating only X.509 parsing/chain
validation to mbedTLS's already-bound C functions — the same thing HTTP/1.1 and
HTTP/2 already do today, with no mbedTLS source patch required. This lands at
comparable total LOC (~1500–3000) to patching mbedTLS, but as fully-owned V code
with no upstream-rebase risk.

Scope for v1 (open to discussion)

  • Client first. Server support is designed for (a role field on the core
    connection struct) but out of v1's committed scope — a client can be
    validated immediately against real-world QUIC servers/reference
    implementations; a server can't be meaningfully tested without a working
    client first.
  • Congestion control: NewReno (RFC 9002 Appendix B's own reference
    pseudocode) rather than CUBIC — directly derivable from spec text.
  • 0-RTT deferred — anti-replay, session resumption, and a 4th encryption
    level's key derivation aren't needed for a correct 1-RTT-only handshake.
  • Single-threaded, caller-driven event loop per QUIC connection (library
    owns state; caller calls poll()/process_timeouts(), mirroring how
    quiche/ngtcp2 expose themselves as libraries) — fits V's lack of a native
    async runtime and QUIC's one-UDP-socket-many-connections-by-CID model.

Phase plan

  1. Cross-platform build risk — confirm OpenSSL (needed for new P-256 ECDH +
    RSA-PSS bindings) and standalone mbedTLS X.509 usage build/work identically
    on Linux/Windows/macOS before any protocol code lands. (Windows CI already
    builds+tests crypto.ecdsa against OpenSSL via windows_ci_gcc.yml
    confirmed, no new CI needed there.)
  2. Primitives — QUIC varint (RFC 9000 §16, distinct from existing LEB128),
    packet number encode/decode (§17.1 + Appendix A), long/short header
    parsing, new P-256 ECDH binding, new RSA-PSS module (no RSA exists in V
    today).
  3. QUIC-scoped TLS 1.3 handshake + key schedule — Initial secrets (RFC 9001
    §5.2), RFC 8446 §7.1 key schedule, handshake message construction/parsing,
    quic_transport_parameters extension, client state machine. Includes
    authoring an RFC-8448-style test vector suite from scratch (RFC 8448 itself
    doesn't cover QUIC's no-record-layer variant).
  4. Packet protection & header protection (RFC 9001 §5) — AEAD + header
    protection masking, correct encrypt-then-mask ordering.
  5. Initial packet exchange — CRYPTO frame reassembly, core frame types,
    datagram coalescing, Retry and Version Negotiation handling.
  6. Full handshake completion — three independent packet number spaces,
    handshake complete vs. confirmed states, key discard, key update (RFC 9001
    §6).
  7. Stream layer — STREAM frames, stream states, connection- and
    stream-level flow control interplay (RFC 9000 §2-4).
  8. Loss detection & NewReno congestion control (RFC 9002) — RTT estimation,
    packet/time threshold loss detection, PTO, persistent congestion.
  9. Connection lifecycle — idle timeout, CONNECTION_CLOSE, stateless reset,
    ECN fallback behavior, PMTU (pinned to the 1200-byte safe minimum for v1).
  10. QuicConn — the top-level connection struct tying everything together,
    with the poll()/process_timeouts() event-loop contract.
  11. HTTP/3 framing (RFC 9114) — incremental/resumable frame parsing (a
    structural difference from HTTP/2's single-shot framing, since H3 frames
    ride inside QUIC STREAM frames).
  12. QPACK (RFC 9204) — static/dynamic tables with absolute indexing,
    encoder/decoder streams, blocked-stream handling.
  13. HTTP/3 client wiring — integration into the existing
    Request/Response/Transport types.
  14. (Follow-up, not v1) Server support.
  15. (Follow-up, not v1) 0-RTT.

Validation plan

  • Unit tests per new file (matching the existing h2_frame_test.v-style
    convention), covering the edge cases enumerated per phase (varint boundary
    values, packet-number reconstruction, coalesced-packet splitting, blocked
    QPACK streams, persistent congestion, etc.)
  • A from-scratch TLS 1.3 test vector suite (Phase 2), captured from a reference
    implementation via QUIC key-log export, cross-derived independently before
    being trusted as a fixture.
  • Interop testing: a local vlib/net/quic/interop/ harness (mirroring the
    existing h2spec/ directory's shape) pinned against one reference
    implementation (ngtcp2+nghttp3 or quiche), plus optional smoke tests against
    public HTTP/3 endpoints and, longer-term, registering with the QUIC working
    group's quic-interop-runner matrix.
  • Fuzzing of every parser that consumes attacker-controlled bytes before
    authentication completes (header parsing, packet/header protection, frame
    parsing, TLS 1.3 message parsing).

Critical new/modified files

  • vlib/net/quic/conn.v — architectural center (QuicConn)
  • vlib/net/quic/tls13_handshake.v — the TLS 1.3 state machine
  • vlib/net/quic/packet_protection.v / header_protection.v
  • vlib/crypto/ecdsa/ecdsa.c.v (modified — new P-256 ECDH binding)
  • vlib/net/mbedtls/mbedtls.c.v (modified — standalone X.509-only usage)
  • vlib/net/http/transport.v (modified — HTTP/3 pool + dispatch integration)

This is a large effort intended to be delivered incrementally across many PRs,
one phase (or sub-phase) at a time, each independently tested. Feedback on the
TLS approach and v1 scope decisions above is very welcome before implementation
goes further.

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Labels

Feature/Enhancement RequestThis issue is made to request a feature or an enhancement to an existing one.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions