Skip to content

Commit 80308a3

Browse files
kixelatedclaude
andauthored
Add QMux over WebSocket draft (#29)
* Add QMux over WebSocket draft QMux (draft-ietf-quic-qmux-01) defines bindings over TCP and TLS but not over WebSocket, since that is out of the QUIC WG charter scope. moq-lite already relies on a WebSocket binding (and moq-dev/web-transport's rs/qmux crate implements one), so this adds a standalone, application-agnostic draft specifying it. Key points: - One WebSocket binary message carries one QMux Record's frames; the Record Size field is omitted because the WS message boundary delimits it. This relies on the Record layer introduced in qmux-01. - Sec-WebSocket-Protocol carries the application ALPN alone (e.g. moq-transport-18); the QMux version is implied by the application protocol, matching the QMux-over-TLS convention. - Text messages are forbidden; keep-alive uses WebSocket Ping/Pong since WebSocket has no built-in idle timeout; datagrams are not available on a reliable, ordered transport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review: trim abstract, clarify version selection, support datagrams - Abstract: drop the in-the-weeds Record/Size mechanism detail. - Subprotocol Identifier: state that the app protocol id selects the QMux version (moq-transport-18 -> qmux-01) with no separate version negotiation. - Datagrams: support all QMux features instead of prohibiting datagrams; they are negotiated and encoded as in qmux and carried in binary messages, delivered reliably/in-order as is inherent to a reliable byte-stream binding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Datagrams: drop redundant note on reliable delivery The loss of QUIC's unordered/unreliable semantics is inherent to qmux and already documented there; no need to restate it in this binding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 88808c6 commit 80308a3

1 file changed

Lines changed: 201 additions & 0 deletions

File tree

draft-lcurley-qmux-websocket.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
title: "QMux over WebSocket"
3+
abbrev: "qmux-ws"
4+
category: info
5+
6+
docname: draft-lcurley-qmux-websocket-latest
7+
submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF"
8+
number:
9+
date:
10+
v: 3
11+
area: wit
12+
workgroup: moq
13+
14+
author:
15+
-
16+
fullname: Luke Curley
17+
email: kixelated@gmail.com
18+
19+
normative:
20+
qmux: I-D.ietf-quic-qmux
21+
RFC6455:
22+
RFC9000:
23+
24+
informative:
25+
RFC8446:
26+
RFC9220:
27+
moqt: I-D.ietf-moq-transport
28+
29+
--- abstract
30+
31+
QMux [qmux] is a polyfill that runs QUIC applications over an ordered, reliable byte-stream transport such as TCP with TLS.
32+
This document defines a binding for QMux over WebSocket [RFC6455].
33+
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.
34+
35+
--- middle
36+
37+
# Conventions and Definitions
38+
{::boilerplate bcp14-tagged}
39+
40+
This document uses the terms QMux Record, QMux Frame, and transport parameter as defined in [qmux], and the terms WebSocket connection, message, frame, and subprotocol as defined in [RFC6455].
41+
42+
43+
# Introduction
44+
QMux [qmux] lets an application written against the QUIC stream and datagram API run over an ordered, reliable byte-stream transport.
45+
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.
46+
47+
A WebSocket binding is nevertheless useful.
48+
WebSocket [RFC6455] is available in essentially every deployment environment, including:
49+
50+
- Networks where UDP (and therefore QUIC) is blocked by a firewall or middlebox.
51+
- Web browsers, which expose a WebSocket API but do not universally expose a WebTransport or raw-socket API.
52+
- HTTP load balancers and proxies that can route and terminate WebSocket but not raw TCP or QUIC.
53+
54+
This document specifies how to carry QMux over WebSocket.
55+
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.
56+
All other QMux semantics — in-order STREAM frame delivery, stream identifiers, flow control, transport parameters, and connection close — apply unchanged from [qmux].
57+
58+
This binding is application agnostic: any QUIC application that can run over QMux can run over QMux over WebSocket.
59+
Media over QUIC Transport [moqt] is one such application and is the motivating use case, but nothing in this document is specific to it.
60+
61+
62+
# WebSocket Binding Overview
63+
A QMux-over-WebSocket connection is an ordinary WebSocket connection [RFC6455] whose binary messages carry QMux frames.
64+
65+
Both the QMux Record layer and the WebSocket message layer provide self-delimiting messages over a reliable, ordered byte stream.
66+
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.
67+
68+
The WebSocket connection takes the place of the underlying byte-stream transport in [qmux].
69+
Once the WebSocket handshake completes, each endpoint sends and receives QMux frames inside WebSocket binary messages as defined below.
70+
71+
72+
# Establishing a Connection
73+
A client establishes a QMux-over-WebSocket connection by opening a WebSocket connection per [RFC6455] (the opening handshake over HTTP/1.1) or per [RFC9220] (the bootstrapping mechanism over HTTP/2 or HTTP/3).
74+
75+
The `ws` URI scheme is used over an unencrypted transport and the `wss` URI scheme is used over a TLS-encrypted transport.
76+
Deployments SHOULD use `wss`; an application that expects a TLS transport when running natively over QUIC SHOULD require `wss` here.
77+
78+
How the underlying connection is authenticated and authorized is out of scope for this document, as it is for [qmux].
79+
80+
81+
# Subprotocol Negotiation
82+
QMux over TCP/TLS uses TLS ALPN [RFC8446] to agree on the application protocol.
83+
The QMux wire-format version is *not* negotiated separately: it is determined by the negotiated application protocol, as described in {{versions}}.
84+
WebSocket has no ALPN exchange, so this binding uses the WebSocket subprotocol negotiation of [RFC6455] Section 1.9 — the `Sec-WebSocket-Protocol` header — in its place, carrying the same application protocol identifier.
85+
86+
## Subprotocol Identifier
87+
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`.
88+
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 {{versions}}).
89+
90+
## Client Behavior
91+
A client offers one or more application protocol identifiers in the `Sec-WebSocket-Protocol` request header, in decreasing order of preference.
92+
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`).
93+
94+
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 [RFC6455].
95+
96+
## Server Behavior
97+
A server selects at most one of the client's offered identifiers and echoes it in the `Sec-WebSocket-Protocol` response header.
98+
A server MUST NOT select an identifier the client did not offer.
99+
100+
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.
101+
If the server supports none of the offered identifiers, it MUST fail the handshake.
102+
103+
The selected identifier determines both the application protocol and, via {{versions}}, the QMux wire-format version for the connection.
104+
105+
106+
# Record Framing {#framing}
107+
Each WebSocket binary message carries exactly one QMux Record's `Frames` field: one or more QMux frames concatenated, as defined in [qmux].
108+
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.
109+
110+
A QMux-over-WebSocket record is therefore:
111+
112+
~~~
113+
WebSocket Binary Message {
114+
Frames (..),
115+
}
116+
~~~
117+
118+
The frames inside a message are encoded exactly as in [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.
119+
120+
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.
121+
An empty binary message (zero frames) is permitted and carries no frames; a receiver MUST accept it and treat it as a no-op.
122+
123+
The maximum size of a binary message is bounded by the `max_record_size` transport parameter defined in [qmux] and by any WebSocket implementation limits.
124+
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.
125+
126+
127+
# WebSocket Message Types
128+
This binding uses WebSocket message and control frames as follows:
129+
130+
- *Binary messages* carry QMux frames as defined in {{framing}}.
131+
- *Text messages* MUST NOT be sent. A receiver MUST treat a text message as a connection error and close the WebSocket connection.
132+
- *Close frames* terminate the connection as defined in {{close}}.
133+
- *Ping and Pong frames* are used for keep-alive as defined in {{keepalive}} and are otherwise handled by the WebSocket layer; they carry no QMux frames.
134+
135+
The first QMux frame sent by each endpoint MUST be the `QX_TRANSPORT_PARAMETERS` frame, exactly as required by [qmux]; this binding does not change that requirement.
136+
137+
138+
# QMux Version {#versions}
139+
This binding builds on QMux as defined in [qmux] (draft-ietf-quic-qmux-01), which introduced the QMux Record layer that this binding relies on (see {{framing}}).
140+
141+
The QMux version is not signaled on the wire and is not carried in the subprotocol identifier.
142+
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.
143+
For example, Media over QUIC Transport [moqt] identifier `moq-transport-18` uses [qmux].
144+
145+
An application protocol used with this binding MUST select a QMux version that provides the Record layer, i.e. [qmux] or later.
146+
147+
148+
# Keep-Alive and Idle Timeout {#keepalive}
149+
QUIC and QMux detect a dead peer with an idle timeout.
150+
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.
151+
152+
To detect a dead peer in a timely manner, an endpoint SHOULD send WebSocket Ping frames [RFC6455] periodically and SHOULD close the connection if no WebSocket frame of any kind is received from the peer within a timeout.
153+
The timeout SHOULD be a small multiple of the ping interval to tolerate transient delays.
154+
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.
155+
156+
Receipt of any WebSocket frame from the peer — binary, Ping, or Pong — resets the idle timer.
157+
An endpoint replies to a Ping with a Pong per [RFC6455]; this is handled by the WebSocket layer and is independent of QMux frames.
158+
159+
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 [qmux], either of which an endpoint MAY also use.
160+
161+
162+
# Datagrams {#datagrams}
163+
QMux datagrams are supported.
164+
They are negotiated and encoded exactly as in [qmux]: an endpoint advertises the datagram transport parameter and carries QMux DATAGRAM frames inside binary messages, like any other frame ({{framing}}).
165+
166+
167+
# Connection Close {#close}
168+
An endpoint terminates a QMux-over-WebSocket connection by sending a WebSocket Close frame [RFC6455] and then closing the underlying transport.
169+
170+
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.
171+
Because the WebSocket layer provides its own connection close, there is no draining period: an endpoint MAY close immediately after sending its Close frame.
172+
173+
Receipt of a WebSocket Close frame, or loss of the underlying transport, terminates the QMux connection and all of its streams.
174+
175+
176+
# Security Considerations
177+
This binding inherits the security considerations of QMux [qmux], WebSocket [RFC6455], and, when `wss` is used, TLS [RFC8446].
178+
179+
Carrying QMux over WebSocket does not add or remove any QMux-level security property.
180+
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).
181+
182+
The keep-alive mechanism in {{keepalive}} causes an endpoint to send periodic Ping frames.
183+
An endpoint SHOULD bound the rate at which it sends and responds to Ping/Pong frames to avoid amplification or resource-exhaustion concerns.
184+
185+
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 [RFC6455].
186+
187+
188+
# IANA Considerations
189+
This document has no IANA actions.
190+
191+
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, [moqt]).
192+
Whether those identifiers are registered in the WebSocket Subprotocol Name Registry [RFC6455] is therefore up to each application protocol, not this document.
193+
194+
195+
--- back
196+
197+
# Acknowledgments
198+
{:numbered="false"}
199+
200+
QMux is the work of the QUIC working group; this document only defines a WebSocket binding for it.
201+
Thanks to the Media over QUIC working group for motivating a transport that works where UDP does not.

0 commit comments

Comments
 (0)