btw: MoQ is under active development. The APIs and protocols are still evolving and will change. Most of this documentation is AI generated until things get more stable.

Skip to content

QMux over WebSocket

INFO

Rendered from the Internet-Draft source in this repository. Submitted versions are on the IETF datatracker.

Abstract

QMux draft-ietf-quic-qmux is a polyfill that runs QUIC applications over an ordered, reliable byte-stream transport such as TCP with TLS. This document defines a binding for QMux over WebSocket RFC 6455. A WebSocket binding lets QUIC applications reach environments where UDP is blocked and where only an HTTP/WebSocket stack is available, including web browsers that lack WebTransport.

Conventions and Definitions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here.

This document uses the terms QMux Record, QMux Frame, and transport parameter as defined in draft-ietf-quic-qmux, and the terms WebSocket connection, message, frame, and subprotocol as defined in RFC 6455.

Introduction

QMux draft-ietf-quic-qmux lets an application written against the QUIC stream and datagram API run over an ordered, reliable byte-stream transport. It defines a binding over TCP and over TLS, but it does not define a binding over WebSocket; the WebSocket binding is out of scope for the QUIC working group charter.

A WebSocket binding is nevertheless useful. WebSocket RFC 6455 is available in essentially every deployment environment, including:

  • Networks where UDP (and therefore QUIC) is blocked by a firewall or middlebox.
  • Web browsers, which expose a WebSocket API but do not universally expose a WebTransport or raw-socket API.
  • HTTP load balancers and proxies that can route and terminate WebSocket but not raw TCP or QUIC.

This document specifies how to carry QMux over WebSocket. It defines the message framing, the subprotocol negotiation used in place of TLS ALPN, how the QMux version is selected, keep-alive behavior, and the handling of datagrams. All other QMux semantics — in-order STREAM frame delivery, stream identifiers, flow control, transport parameters, and connection close — apply unchanged from draft-ietf-quic-qmux.

This binding is application agnostic: any QUIC application that can run over QMux can run over QMux over WebSocket. Media over QUIC Transport draft-ietf-moq-transport is one such application and is the motivating use case, but nothing in this document is specific to it.

WebSocket Binding Overview

A QMux-over-WebSocket connection is an ordinary WebSocket connection RFC 6455 whose binary messages carry QMux frames.

Both the QMux Record layer and the WebSocket message layer provide self-delimiting messages over a reliable, ordered byte stream. The two layers are therefore collapsed: instead of prefixing each Record with its Size, the binding relies on the WebSocket message boundary to delimit it.

The WebSocket connection takes the place of the underlying byte-stream transport in draft-ietf-quic-qmux. Once the WebSocket handshake completes, each endpoint sends and receives QMux frames inside WebSocket binary messages as defined below.

Establishing a Connection

A client establishes a QMux-over-WebSocket connection by opening a WebSocket connection per RFC 6455 (the opening handshake over HTTP/1.1) or per RFC 9220 (the bootstrapping mechanism over HTTP/2 or HTTP/3).

The ws URI scheme is used over an unencrypted transport and the wss URI scheme is used over a TLS-encrypted transport. Deployments SHOULD use wss; an application that expects a TLS transport when running natively over QUIC SHOULD require wss here.

How the underlying connection is authenticated and authorized is out of scope for this document, as it is for draft-ietf-quic-qmux.

Subprotocol Negotiation

QMux over TCP/TLS uses TLS ALPN RFC 8446 to agree on the application protocol. The QMux wire-format version is not negotiated separately: it is determined by the negotiated application protocol, as described in QMux Version. WebSocket has no ALPN exchange, so this binding uses the WebSocket subprotocol negotiation of RFC 6455 Section 1.9 — the Sec-WebSocket-Protocol header — in its place, carrying the same application protocol identifier.

Subprotocol Identifier

The subprotocol identifier is exactly the application protocol identifier that the application would use as its ALPN over native QUIC; for example moq-transport-18. The application protocol identifier also determines the QMux wire-format version — for example moq-transport-18 indicates that qmux-01 is to be used — so there is no separate QMux version negotiation (see QMux Version).

Client Behavior

A client offers one or more application protocol identifiers in the Sec-WebSocket-Protocol request header, in decreasing order of preference. A client that supports multiple application protocols, or multiple versions of one, offers one identifier per protocol version it is willing to use (for example moq-transport-18 and moq-transport-17).

A client MUST treat the absence of a Sec-WebSocket-Protocol response header, or a response value it did not offer, as a failed handshake per RFC 6455.

Server Behavior

A server selects at most one of the client's offered identifiers and echoes it in the Sec-WebSocket-Protocol response header. A server MUST NOT select an identifier the client did not offer.

A server SHOULD select identifiers in its own order of preference (for example, preferring a newer application protocol version), independent of the client's ordering. If the server supports none of the offered identifiers, it MUST fail the handshake.

The selected identifier determines both the application protocol and, via QMux Version, the QMux wire-format version for the connection.

Record Framing

Each WebSocket binary message carries exactly one QMux Record's Frames field: one or more QMux frames concatenated, as defined in draft-ietf-quic-qmux. Because the WebSocket framing layer already delimits each message, the QMux Record Size field is redundant: it MUST NOT be transmitted and MUST NOT be expected by the receiver.

A QMux-over-WebSocket record is therefore:

WebSocket Binary Message {
  Frames (..),
}

The frames inside a message are encoded exactly as in draft-ietf-quic-qmux, including the in-order STREAM frame requirement: for each QUIC stream, a sender MUST send that stream's payload in order, so a receiver can deliver payload to the application as it arrives without reassembly.

An endpoint MAY place multiple frames in a single binary message and MAY split a logical sequence of frames across multiple messages, subject to the constraint that each STREAM frame's payload bytes are delivered in order. An empty binary message (zero frames) is permitted and carries no frames; a receiver MUST accept it and treat it as a no-op.

The maximum size of a binary message is bounded by the max_record_size transport parameter defined in draft-ietf-quic-qmux and by any WebSocket implementation limits. An endpoint MUST NOT send a binary message whose payload exceeds the peer's advertised max_record_size, and MAY treat receipt of an oversized message as a connection error.

WebSocket Message Types

This binding uses WebSocket message and control frames as follows:

  • Binary messages carry QMux frames as defined in Record Framing.
  • Text messages MUST NOT be sent. A receiver MUST treat a text message as a connection error and close the WebSocket connection.
  • Close frames terminate the connection as defined in Connection Close.
  • Ping and Pong frames are used for keep-alive as defined in Keep-Alive and Idle Timeout and are otherwise handled by the WebSocket layer; they carry no QMux frames.

The first QMux frame sent by each endpoint MUST be the QX_TRANSPORT_PARAMETERS frame, exactly as required by draft-ietf-quic-qmux; this binding does not change that requirement.

QMux Version

This binding builds on QMux as defined in draft-ietf-quic-qmux (draft-ietf-quic-qmux-01), which introduced the QMux Record layer that this binding relies on (see Record Framing).

The QMux version is not signaled on the wire and is not carried in the subprotocol identifier. As with QMux over TLS, it is implied by the negotiated application protocol: each application protocol that runs over QMux specifies which QMux version each of its ALPN identifiers uses. For example, Media over QUIC Transport draft-ietf-moq-transport identifier moq-transport-18 uses draft-ietf-quic-qmux.

An application protocol used with this binding MUST select a QMux version that provides the Record layer, i.e. draft-ietf-quic-qmux or later.

Keep-Alive and Idle Timeout

QUIC and QMux detect a dead peer with an idle timeout. A WebSocket connection has no built-in idle timeout: if the peer's host crashes or its network drops without a TCP FIN, the local socket can remain "open" until OS-level TCP keep-alive eventually probes, which may take hours.

To detect a dead peer in a timely manner, an endpoint SHOULD send WebSocket Ping frames RFC 6455 periodically and SHOULD close the connection if no WebSocket frame of any kind is received from the peer within a timeout. The timeout SHOULD be a small multiple of the ping interval to tolerate transient delays. Reasonable defaults are a 5-second ping interval and a 30-second timeout, matching common QUIC idle-timeout configurations, but the values are a local policy decision.

Receipt of any WebSocket frame from the peer — binary, Ping, or Pong — resets the idle timer. An endpoint replies to a Ping with a Pong per RFC 6455; this is handled by the WebSocket layer and is independent of QMux frames.

This keep-alive operates at the WebSocket layer and is separate from the QMux max_idle_timeout transport parameter and the QX_PING frame defined in draft-ietf-quic-qmux, either of which an endpoint MAY also use.

Datagrams

QMux datagrams are supported. They are negotiated and encoded exactly as in draft-ietf-quic-qmux: an endpoint advertises the datagram transport parameter and carries QMux DATAGRAM frames inside binary messages, like any other frame (Record Framing).

Connection Close

An endpoint terminates a QMux-over-WebSocket connection by sending a WebSocket Close frame RFC 6455 and then closing the underlying transport.

A QMux CONNECTION_CLOSE frame, if sent, conveys the QMux-level error code and reason and SHOULD be sent in a final binary message before the WebSocket Close frame. Because the WebSocket layer provides its own connection close, there is no draining period: an endpoint MAY close immediately after sending its Close frame.

Receipt of a WebSocket Close frame, or loss of the underlying transport, terminates the QMux connection and all of its streams.

Security Considerations

This binding inherits the security considerations of QMux draft-ietf-quic-qmux, WebSocket RFC 6455, and, when wss is used, TLS RFC 8446.

Carrying QMux over WebSocket does not add or remove any QMux-level security property. In particular, this binding provides no transport-layer confidentiality or integrity of its own; deployments that require those properties MUST use wss (WebSocket over TLS).

The keep-alive mechanism in Keep-Alive and Idle Timeout causes an endpoint to send periodic Ping frames. An endpoint SHOULD bound the rate at which it sends and responds to Ping/Pong frames to avoid amplification or resource-exhaustion concerns.

Because a server selects the subprotocol from a client-supplied list, a server MUST validate the selected identifier against its own supported set and MUST NOT echo an arbitrary client-supplied value, as required by RFC 6455.

IANA Considerations

This document has no IANA actions.

This binding defines no subprotocol identifiers of its own: the Sec-WebSocket-Protocol value is the application protocol identifier, which is owned by the application protocol's specification (for example, draft-ietf-moq-transport). Whether those identifiers are registered in the WebSocket Subprotocol Name Registry RFC 6455 is therefore up to each application protocol, not this document.

Normative References

Informative References

Acknowledgments

QMux is the work of the QUIC working group; this document only defines a WebSocket binding for it. Thanks to the Media over QUIC working group for motivating a transport that works where UDP does not.

Licensed under MIT or Apache-2.0