erlang_quic Features

View Source

Core Protocol (RFC 9000)

Connection Management

  • [x] Connection establishment with TLS 1.3 handshake
  • [x] Connection close (immediate and draining states)
  • [x] Idle timeout enforcement (configurable via idle_timeout option)
  • [x] Lazy idle and keep-alive timers: armed once and re-armed only when they fire, using the last_activity timestamp, so steady-state traffic does not cancel and reschedule a timer on every packet
  • [x] Version negotiation: server emits a Version Negotiation packet for an unknown version; a client that receives one and shares no version closes with {version_negotiation, Versions} (RFC 9000 §6.2)
  • [x] Retry packets for address validation
  • [x] IPv6 client connections: hostname, IP-literal (bracketed or bare), or inet:ip_address() tuple host
  • [x] Happy Eyeballs v2 (RFC 8305): dual-stack hostnames race IPv6-first, with happy_eyeballs, family, connection_attempt_delay and connect_timeout options on quic:connect/4
  • [x] Latency spin bit (RFC 9000 §17.4) with spin_bit => true | false

  • [x] NEW_TOKEN frame dispatch (server rejects peer-received tokens per §8.1.3); client caches received tokens keyed by {Host, Port} and reuses them in the Initial of the next connect to the same endpoint
  • [x] Stateless reset (RFC 9000 §10.3): listener emits resets for orphan packets; per-connection NEW_CONNECTION_ID tokens share the listener's HMAC secret so they match orphan-path tokens
  • [x] Server-side address validation (RFC 9000 §8.1): opt in with address_validation => always on quic:start_server/3. Listener emits a Retry packet with an HMAC-signed retry token when a client Initial arrives without one; subsequent Initials carrying a valid token skip retry and spawn a connection that echoes retry_source_connection_id. Server issues a NEW_TOKEN after handshake so the next reconnect skips retry entirely

Streams

  • [x] Bidirectional streams (client and server initiated)
  • [x] Unidirectional streams
  • [x] Stream prioritization (RFC 9218) with 8 urgency levels
  • [x] Incremental delivery flag support
  • [x] RESET_STREAM_AT extension (draft-ietf-quic-reliable-stream-reset-07)

Flow Control

  • [x] Connection-level flow control (MAX_DATA)
  • [x] Stream-level flow control (MAX_STREAM_DATA)
  • [x] MAX_STREAMS limits (bidirectional and unidirectional)

Packet Handling

  • [x] Initial, Handshake, and 1-RTT packet types
  • [x] Short header (1-RTT) packets
  • [x] Packet number encoding (1-4 bytes)
  • [x] Packet number reconstruction per RFC 9000 Appendix A
  • [x] Coalesced packets
  • [x] Frame coalescing (ACK + small stream data in single packet)
  • [x] HTTP/3 response HEADERS coalesced with the first DATA frame so the response headers and first body bytes ride in one 1-RTT packet (a large body still fragments; only the standalone HEADERS packet is removed)

Connection Migration (RFC 9000 Section 9)

  • [x] PATH_CHALLENGE / PATH_RESPONSE validation
  • [x] Active connection migration (quic:migrate/1, quic:migrate/2)
  • [x] Preferred address handling (RFC 9000 Section 9.6)
  • [x] Server-side address change detection (NAT rebinding and active migration)
  • [x] Congestion control reset on path change (RFC 9002 Section 9.4)
  • [x] CID rotation on migration for path unlinkability (RFC 9000 Section 9.5)
  • [x] disable_active_migration transport parameter support
  • [x] Path validation timeout with retry (3 * PTO, up to 3 attempts)

Connection ID Management

  • [x] Multiple connection IDs
  • [x] NEW_CONNECTION_ID frames
  • [x] RETIRE_CONNECTION_ID frames
  • [x] Active connection ID limit

Loss Detection & Congestion Control (RFC 9002)

Loss Detection

  • [x] Packet loss detection
  • [x] Probe timeout (PTO)
  • [x] RTT measurement (smoothed RTT, RTT variance)

Congestion Control

  • [x] Pluggable congestion control behavior
  • [x] NewReno (default, RFC 9002)
  • [x] CUBIC (RFC 9438)
  • [x] BBR (Bottleneck Bandwidth and RTT)
  • [x] HyStart++ slow start (RFC 9406) for all algorithms
  • [x] Slow start with improved exit detection
  • [x] Congestion avoidance
  • [x] Recovery on packet loss
  • [x] Persistent congestion detection (resets cwnd after PTO * 3)
  • [x] ECN support (ECN-CE triggers congestion response)
  • [x] Packet pacing (RFC 9002 Section 7.7) to prevent bursts
  • [x] RTT-based flow control auto-tuning

Path MTU Discovery (RFC 8899 - DPLPMTUD)

  • [x] Binary search probing for optimal MTU
  • [x] Integration with peer's max_udp_payload_size transport parameter
  • [x] Black hole detection and recovery
  • [x] Automatic MTU reset on connection migration
  • [x] Periodic re-probing for MTU increases
  • [x] Congestion control integration (updates cwnd-related parameters)

TLS 1.3 Integration (RFC 9001)

Handshake

  • [x] Full TLS 1.3 handshake
  • [x] ALPN negotiation
  • [x] Transport parameters exchange
  • [x] Certificate verification
  • [x] HelloRetryRequest (RFC 8446 §4.1.4)

Key-exchange groups

  • [x] x25519 (default)
  • [x] secp256r1, secp384r1 (opt-in via groups)
  • [x] x25519mlkem768 post-quantum hybrid (draft-ietf-tls-ecdhe-mlkem;
    opt-in via `groups`, needs the ML-KEM APIs in `crypto`, OTP 28.1+)
  • [x] Multi-group key_share + supported_groups negotiation
  • [ ] secp521r1, x448 (constants only; see Roadmap)

Signature algorithms

  • [x] ECDSA secp256r1-SHA256, secp384r1-SHA384
  • [x] RSA-PSS-RSAE SHA256/384/512
  • [x] Ed25519
  • [x] Per-handshake signature_algorithms negotiation (signature_algs)
  • [ ] Ed448, ECDSA secp521r1-SHA512 (constants only; see Roadmap)

Encryption

  • [x] AES-128-GCM cipher suite
  • [x] AES-256-GCM cipher suite
  • [x] ChaCha20-Poly1305 cipher suite
  • [x] Header protection
  • [x] Key derivation (HKDF)

Key Management

  • [x] Initial secrets derivation
  • [x] Handshake secrets
  • [x] Application secrets
  • [x] Key updates (RFC 9001 Section 6)

Session Resumption

  • [x] Session tickets (NewSessionTicket)
  • [x] PSK-based resumption
  • [x] 0-RTT early data

External PSK (RFC 8446 §4.2.11)

  • [x] psk_dhe_ke mode (forward-secret)
  • [x] psk_ke mode (no DHE)
  • [x] Server-side binder verification (constant-time)
  • [x] Identity lookup via callback and/or static map
  • [x] Cert + PSK coexistence on the same listener
  • [x] Client downgrade protection

See docs/PSK.md. Pending follow-ups tracked in the Roadmap below.

QUIC Version 2 (RFC 9369)

  • [x] Version 2 (0x6b3343cf) support
  • [x] Updated initial salt
  • [x] Updated retry integrity tag key

QUIC-LB Load Balancer Support (RFC 9312)

  • [x] Server ID encoding in Connection IDs for LB routing
  • [x] Config rotation bits for LB coordination
  • [x] Variable CID length support (1-20 bytes)
  • [x] Three encoding algorithms:
    • Plaintext: Server ID visible in CID (no encryption)
    • Stream Cipher: AES-128-CTR encryption
    • Block Cipher: Feistel network for variable lengths
  • [x] LB-aware CID generation in listener and connection

Reliable Stream Reset (draft-ietf-quic-reliable-stream-reset-07)

RESET_STREAM_AT allows resetting a stream while ensuring data up to a specified offset is reliably delivered. Required for WebTransport where stream headers must be received even if the stream is immediately reset.

Features

  • [x] Frame type 0x24 (RESET_STREAM_AT) encode/decode
  • [x] Transport parameter negotiation (0x17f7586d2cb571)
  • [x] Reliable delivery guarantee up to ReliableSize
  • [x] Retransmission filtering (data beyond ReliableSize not retransmitted)
  • [x] Validation: ReliableSize cannot exceed FinalSize
  • [x] Validation: ReliableSize cannot be increased after initial reset
  • [x] Validation: ErrorCode cannot change after initial reset

Usage

%% Enable in connection options (both client and server)
Opts = #{reset_stream_at => true, alpn => [<<"webtransport">>]},
{ok, Conn} = quic:connect(Host, Port, Opts, self()),

%% Send stream header (e.g., WebTransport session ID)
{ok, StreamId} = quic:open_stream(Conn),
ok = quic:send_data(Conn, StreamId, Header, false),

%% Reset stream but ensure header is delivered
ok = quic:reset_stream_at(Conn, StreamId, ErrorCode, byte_size(Header)).

API

Connection

Datagrams (RFC 9221)

HTTP Datagrams (RFC 9297)

  • quic_h3:send_datagram/3 - Send an HTTP datagram bound to a request stream
  • quic_h3:h3_datagrams_enabled/1 - Whether both sides negotiated support
  • quic_h3:max_datagram_size/2 - Max payload per datagram for a given stream
  • Owner event: {quic_h3, Conn, {datagram, StreamId, Payload}}
  • Set h3_datagram_enabled => true on connect/3 / start_server/3 to enable. CONNECT-UDP (RFC 9298) builds on this in a separate library.

HTTP/3 Extension Streams

  • quic_h3:open_bidi_stream/1,2 - Open a client-initiated bidi stream; with a non-negative SignalType varint the stream is pre-claimed and inbound bytes route as {stream_type_data, bidi, ...} owner messages instead of HTTP/3 request frames (e.g. WebTransport's 0x41)
  • stream_type_handler option on start_server/3 claims peer-initiated uni / bidi streams whose first varint matches a caller-supplied filter
  • Owner events: {stream_type_open, Direction, StreamId, VarintType}, {stream_type_data, Direction, StreamId, Data, Fin}, {stream_type_closed, Direction, StreamId}, {stream_type_reset, Direction, StreamId, ErrorCode}, {stream_type_stop_sending, Direction, StreamId, ErrorCode}
  • Per-connection owner override via connection_handler callback on start_server/3 for hosting many sessions on one listener

Streams

Server / Multi-Pool Server Management

Load Balancer (RFC 9312)

Options

  • idle_timeout - Connection idle timeout in milliseconds (0 to disable)
  • max_data - Connection-level flow control limit
  • max_stream_data - Stream-level flow control limit
  • max_datagram_frame_size - Max datagram size to accept (0 = disabled, default: 0)
  • datagram_recv_queue_len - Bounded receive queue for inbound datagrams (default: infinity; drops oldest on overflow, tracked via datagram_stats/1)
  • reset_stream_at - Enable RESET_STREAM_AT extension (default: false)
  • alpn - ALPN protocols list
  • verify - Server certificate verification on the client (default: true; verifies the CertificateVerify signature, the chain, and the hostname). The hostname check follows the RFC 6125 HTTPS rules, so a leftmost-label wildcard SAN such as *.example.com matches host.example.com. Set false to accept any certificate, e.g. a self-signed test server.
  • cacerts - Trust anchors for client chain validation, as a list of DER-encoded certificates (default: the operating system trust store)
  • preferred_ipv4 - Server preferred IPv4 address
  • preferred_ipv6 - Server preferred IPv6 address
  • pool_size - Number of listener processes for server pools (default: 1)
  • connection_handler - Callback for handling new connections
  • monitor_owner - Stop the connection with {shutdown, owner_down} and send CONNECTION_CLOSE when its owner process exits (default: true for accepted connections, false for client connections, which are linked to their caller unless supervised). Set false in the listener options to keep an accepted connection alive after its handler exits
  • lb_config - QUIC-LB configuration map for load balancer routing
  • keep_alive_interval - Keep-alive PING interval (disabled, auto, or milliseconds)
  • pmtu_enabled - Enable Path MTU Discovery (default: true)
  • pmtu_max_mtu - Maximum MTU to probe (default: 1500)
  • max_udp_payload_size - Largest UDP payload this endpoint is willing to receive, advertised as the RFC 9000 §18.2 transport parameter (minimum 1200; default: what a 1500-byte path carries for the address family, 1472 over IPv4 and 1452 over IPv6)
  • recbuf - UDP receive buffer size in bytes (default: 7MB)
  • sndbuf - UDP send buffer size in bytes (default: 7MB)
  • server_send_batching - Per-connection send batching on the server (default: true). On Linux + socket_backend => socket with UDP_SEGMENT, outgoing packets are coalesced into GSO super-datagrams via sendmsg cmsg; neutral on macOS / gen_udp. Set to false to fall back to direct gen_udp:send/4
  • hibernate_after - Hibernate an idle connection process after this many milliseconds, running a fullsweep so handshake garbage is not pinned to a heap that never collects (default: 5000; infinity opts out)
  • versions - QUIC versions this connection also accepts, for RFC 9368 compatible version negotiation (default: the negotiated version alone)
  • ciphers - TLS 1.3 cipher suites to offer or accept, in preference order (default: [aes_128_gcm, aes_256_gcm, chacha20_poly1305] on a CPU with AES instructions, [chacha20_poly1305, aes_128_gcm, aes_256_gcm] without them, per /proc/cpuinfo). A server honours a client that lists chacha20_poly1305 first whenever its own list allows the suite, so a peer without AES hardware gets ChaCha from either side
  • max_burst_packets - Packets allowed to leave per send drain, so a large queued write cannot monopolise the scheduler (default: 64)
  • ack_packet_tolerance - Ack-eliciting packets received before a 1-RTT ACK is sent, the RFC 9000 section 13.2.1 decimation threshold (default: 2)
  • disconnect_timeout - Milliseconds without a response from the peer before the connection is given up on, checked on its own timer (default: 16000)
  • pmtu_raise_interval - Milliseconds between PMTU raise probes (default: 600000)
  • delivery_coalescing - Merge consecutive same-stream stream_data deliveries of one receive pass into a single owner message (default: false). Arrival order across streams is preserved and a stream_reset never overtakes data delivered before it, but an owner that counts messages rather than bytes sees fewer of them

PMTU Discovery

Erlang Distribution (quic_dist)

QUIC-based Erlang distribution protocol implementation.

Features

  • [x] Full distribution protocol over QUIC transport
  • [x] TLS 1.3 encryption built-in (no separate SSL setup)
  • [x] 0-RTT session resumption for fast reconnection
  • [x] Multiple streams: control (urgency 0) + data (urgency 4-6)
  • [x] Stream prioritization for tick/control messages
  • [x] QUIC-level liveness detection (packet counts, not blocked by flow control)
  • [x] Keep-alive PING frames for transport liveness
  • [x] Backpressure mechanism for congestion control
  • [x] Session ticket storage for 0-RTT

Modules

  • quic_dist - Distribution protocol callbacks
  • quic_dist_controller - Per-connection state machine
  • quic_dist_sup - Distribution supervisor
  • quic_dist_tickets - Session ticket storage
  • quic_epmd - EPMD replacement module
  • quic_dist_auth - Optional auth-handshake behaviour

Discovery Backends

  • quic_discovery_static - Static node configuration
  • quic_discovery_dns - DNS SRV-based discovery
  • Custom backends via quic_discovery behaviour

Distribution API

Extension Hooks

  • auth_callback (default undefined): {Mod, Fun} or fun/3 invoked on both sides after the QUIC handshake but before the distutil handshake. Returning `{error, }closes the connection without the node ever joining. Seequic_dist_authand the Configuration Reference indocs/QUIC_DIST.md`.
  • register_with_epmd (default false): when true, the listener registers its port via the configured epmd_module so external tooling (e.g. epmd -names) can resolve the node.

Roadmap

Tracked future work. Items here are not committed deliverables; they mark known gaps that may land in a later release.

TLS 1.3 external PSK follow-ups

  • [ ] 0-RTT on external PSK. The server currently ignores early_data on PSK handshakes; full EndOfEarlyData state-machine support is deferred.
  • [ ] NewSessionTicket on PSK-authenticated handshakes. Suppressed in v1 to avoid binding-identity ambiguity; revisit when a concrete use case appears.
  • [ ] RFC 9258 PSK Importer. The current API consumes the secret as raw IKM; an importer layer would derive an epsk -> psk mapping bound to a target protocol/KDF.

TLS 1.3 negotiation follow-ups

  • [ ] secp521r1 / x448 key-exchange groups (constants defined; key generation and key_share wiring deferred).
  • [ ] Ed448 / ECDSA secp521r1-SHA512 signature schemes (constants defined; sign/verify branches deferred).
  • [ ] PSK + HelloRetryRequest in one handshake. Currently the client aborts if HRR follows a PSK ClientHello (binder recompute over the synthetic transcript is not implemented).

Interop Runner Compliance

All 10 QUIC Interop Runner test cases pass:

Test CaseStatus
handshakePass
transferPass
retryPass
keyupdatePass
chacha20Pass
multiconnectPass
v2Pass
resumptionPass
zerorttPass
connectionmigrationPass