§12.2
Chapter 12 · MASQUE: Proxying UDP and IP over HTTP/3

§12.2HTTP Datagrams and the Capsule Protocol

RFC 9297RFC 9221

Learning objective. Understand the foundation both MASQUE proxying protocols stand on: what an HTTP Datagram is, how HTTP/3 carries it in a QUIC DATAGRAM frame using a Quarter Stream ID, and the Capsule Protocol that provides a reliable, stream-based alternative, and when each is used.

The problem HTTP Datagrams solve #

A tunnel needs to carry packets: units of data associated with the request that opened the tunnel, delivered ideally without the overhead of reliability. But an HTTP request/response is a reliable, ordered byte stream (Chapter 5); it has no native notion of "a datagram tied to this request." HTTP Datagrams ([RFC 9297]) add exactly that: "a convention for conveying multiplexed, potentially unreliable datagrams inside an HTTP connection" ([RFC 9297 §1]), where "all HTTP Datagrams are associated with an HTTP request" ([RFC 9297 §2]). They are the mechanism a connect-udp or connect-ip tunnel uses to carry its inner packets, and they are usable by any HTTP extension, but not by plain GET or POST, which have no defined datagram semantics.

There are two ways to carry one, and the wire shows both:

Are these part of HTTP/3? No: they are extensions, each closing a gap #

A fair question at this point: are HTTP Datagrams and the Capsule Protocol part of HTTP/3, or something new invented for MASQUE, and why so many separate pieces? The honest answer is that none of this is in base HTTP/3 or base QUIC. Base QUIC ([RFC 9000]) and base HTTP/3 ([RFC 9114]) offer only reliable, ordered streams. No datagrams at all. Everything here is a stack of extensions, and each one exists because the layer beneath it left a specific gap:

each layer is an extension that closes a gap the layer below leaves open — read bottom to top Base QUIC (RFC 9000) + HTTP/3 (RFC 9114) gives you: reliable, ordered STREAM data — every byte arrives, in order no datagrams at all — the base specs have no unreliable delivery gap: real-time and tunneling want UNRELIABLE delivery QUIC DATAGRAM frames — RFC 9221 (a QUIC extension) · Chapter 9 adds: unreliable, unordered datagrams inside the QUIC connection but a datagram belongs to the whole connection — it carries no request/stream association gap: which tunnel is it for? TCP-based HTTP has no datagrams HTTP Datagrams + Capsule Protocol — RFC 9297 (an HTTP extension) HTTP Datagram = a datagram + a Quarter Stream ID → tied to a specific request stream Capsule Protocol = reliable type-length-value on the stream → a datagram carrier for TCP-based HTTP, and a channel for control data that must arrive (e.g. address assignment) now any HTTP extension can carry request-tied packets MASQUE — CONNECT-UDP (RFC 9298) · CONNECT-IP (RFC 9484) uses all of the above to proxy UDP and IP: inner packets ride as HTTP Datagrams tied to a tunnel's request
Fig. 12.2-2The ladder of extensions behind MASQUE, read bottom to top. Base QUIC/HTTP-3 give only reliable streams; RFC 9221 adds unreliable QUIC datagrams but they belong to the whole connection; RFC 9297 adds the Quarter Stream ID (tying a datagram to a request) and the Capsule Protocol (a reliable carrier for TCP-based HTTP and for control data); and MASQUE (RFC 9298/9484) uses all of it to proxy UDP and IP. Each layer closes the gap the one below leaves open.RFC 9221 §1; RFC 9297 §1, §3

Read the ladder from the bottom:

  1. Base QUIC + HTTP/3 give only reliable streams. Great for a web page; useless for a tunnel that wants to carry packets as-is without a second reliability layer (§12.1).
  2. QUIC DATAGRAM frames ([RFC 9221], Chapter 9) add unreliable delivery. But a QUIC DATAGRAM frame belongs to the whole connection, with no field saying which request or tunnel it is for. On a proxy with many tunnels multiplexed on one connection, that is not enough.
  3. HTTP Datagrams ([RFC 9297]) add the missing association (the Quarter Stream ID that ties each datagram to a specific request stream) and a transport-independent abstraction, so the same idea works over HTTP versions that have no QUIC datagrams. The Capsule Protocol ([RFC 9297]) is the other half: a reliable in-stream channel that both carries datagrams where QUIC datagrams don't exist (HTTP/1.1, HTTP/2 over TCP) and carries control data that must arrive.
  4. MASQUE ([RFC 9298], [RFC 9484]) uses all of it to actually proxy UDP and IP.

Wait — doesn't classic CONNECT already tunnel to one endpoint? #

A natural objection to step 2 above: a classic CONNECT tunnel is the whole connection. That is exactly what you want when tunneling to a remote endpoint, so why worry that a QUIC datagram is connection-wide? The answer is that "the connection is the tunnel" describes the HTTP/1.1 model, and that model is precisely the limitation HTTP/2 and HTTP/3 were built to remove ([RFC 9114 §4.4]):

"In HTTP/1.x, CONNECT is used to convert an entire HTTP connection into a tunnel to a remote host. In HTTP/2 and HTTP/3, the CONNECT method is used to establish a tunnel over a single stream."

That difference is the whole point. In HTTP/1.1 one connection carries exactly one tunnel and nothing else, so a datagram would be unambiguous, but you also pay a full connection and handshake per tunnel. In HTTP/2 and HTTP/3, a CONNECT tunnel is scoped to a single stream, so one connection multiplexes many tunnels at once: dozens of connect-udp tunnels to different targets, plus ordinary requests, all sharing one handshake (§4.1). That is exactly the efficiency a proxy or privacy relay needs: it opens one connection to the proxy and runs all a client's flows over it, rather than a fresh handshake for every tunnel. (In HTTP/1.1, with no multiplexing, "any reference to 'stream'... represents the entire connection" ([RFC 9298 §2]): the old one-tunnel-per-connection behaviour.)

Now the gap is clear. For the reliable tunnel bytes, multiplexing already works: each tunnel's stream carries its own bytes, disambiguated by stream ID (Chapter 4). But QUIC DATAGRAM frames carry no stream association (they belong to the connection as a whole, §9.3), so the moment many tunnels share one connection, a bare datagram is ambiguous: which of the open tunnels is it for? The Quarter Stream ID is what re-attaches that missing association to each datagram. Your intuition holds perfectly for HTTP/1.1's one-tunnel-per-connection world; MASQUE lives in the multiplexed one, where it does not.

So "why invent so many new things" has a clean answer: each layer is the minimal addition that closes one gap: unreliable delivery (RFC 9221), request association plus version-independence (RFC 9297 datagrams), reliable control and a TCP-based fallback (RFC 9297 capsules). MASQUE is what you get once all three gaps are closed. RFC 9297 was written in the MASQUE working group but is general-purpose: WebTransport, for instance, uses the same HTTP Datagram and Capsule machinery. The rest of this section looks at the two RFC 9297 pieces in turn.

HTTP/3 Datagrams: a Quarter Stream ID plus a payload #

Over HTTP/3, an HTTP Datagram is simply the payload of a QUIC DATAGRAM frame ([RFC 9221], §9.3): unreliable, unordered, exactly the delivery a tunnel wants. But a DATAGRAM frame belongs to the connection, not to any stream (§9.3), so HTTP/3 prefixes it with a Quarter Stream ID to associate it with the request that opened the tunnel ([RFC 9297 §2.1]):

  • The Quarter Stream ID is "the value of the client-initiated bidirectional stream that this datagram is associated with divided by four" ([RFC 9297 §2.1]). The division is free information: client-initiated bidirectional stream IDs are always multiples of 4 (§4.1), so dividing by four wastes no bits and keeps the varint small.
  • The rest is the HTTP Datagram Payload, whose meaning "is defined by the extension that is using HTTP Datagrams" ([RFC 9297 §2.1]), opaque to RFC 9297 itself. CONNECT-UDP and CONNECT-IP fill it with a Context ID and a packet (§12.3§12.4).

The feature is negotiated by the SETTINGS_H3_DATAGRAM setting (0x33, constants.md §11). Both endpoints must send it with value 1: "QUIC DATAGRAM frames MUST NOT be sent until the SETTINGS_H3_DATAGRAM setting has been both sent and received with a value of 1" ([RFC 9297 §2.1.1]). A datagram for a request with no datagram semantics aborts the stream with H3_DATAGRAM_ERROR (0x33, [RFC 9297 §2]).

The Capsule Protocol: reliable data on the request stream #

QUIC DATAGRAM frames only exist over HTTP/3, and even there they are unreliable. To carry tunnel data over HTTP/2 or HTTP/1.1, or to send data that must be reliable, like the address-assignment control messages of CONNECT-IP (§12.4), RFC 9297 defines the Capsule Protocol: "a sequence of type-length-value tuples" carried in the request's data stream, the message content that follows the header section after a successful (2xx) or upgraded (101) response ([RFC 9297 §3, §3.1]).

The natural question is: HTTP/3 already frames data on a stream (a QUIC STREAM frame carries an HTTP/3 DATA frame, §5.1), so why invent another framing? The answer is that a capsule is not an alternative to HTTP/3 frames; it sits one layer above them. In HTTP/3 the data stream "consists of all bytes sent in DATA frames" ([RFC 9297 §3.1]), so a capsule is simply the content those DATA frames carry — QUIC STREAM frame → HTTP/3 DATA frame → capsules:

a capsule is not an alternative to HTTP/3 frames — it is the CONTENT a DATA frame carries, one layer above them reliable — capsules on the request stream QUIC STREAM frame the request stream's reliable, ordered bytes HTTP/3 DATA frame the request's content / data stream (RFC 9297 §3.1) Capsule · Type + Length + Value TLV in the content · reliable, ordered, extensible e.g. DATAGRAM (0x00), ADDRESS_ASSIGN (0x01) unreliable — datagrams bypass the stream QUIC DATAGRAM frame no stream — connection-wide, unreliable HTTP/3 Datagram Quarter Stream ID + payload no HTTP/3 DATA frame involved (the fast path — §12.2 top) why not just define new HTTP/3 frame types? Because capsules ride in the request's data stream (not version-specific framing), the SAME format works whether the CARRYING hop is HTTP/1.1, HTTP/2, or HTTP/3, and an intermediary forwards them unchanged even across a version boundary. (This is about the carrier hop — not the tunneled payload, which is opaque packets.) [RFC 9297 §3, §3.2]
Fig. 12.2-3Where a capsule lives. On the reliable path (left) a capsule is the message content inside an HTTP/3 DATA frame, itself inside a QUIC STREAM frame — one layer above HTTP/3 framing, not a replacement for it. The unreliable datagram path (right) skips streams entirely. Capsules are defined in message content so they are identical across HTTP versions and pass through HTTP intermediaries unchanged — which new HTTP/3 frame types could not.RFC 9297 §3.1, §3.2

So "why not just define new HTTP/3 frame types for tunnel data?" has a precise answer, and it is the same reason HTTP Datagrams are transport-independent: capsules are defined at the HTTP-content layer, not the HTTP-version framing layer. That buys two things HTTP/3-specific frames cannot:

  • They work over every HTTP version. Those are three separate connection types (HTTP/1.1 and HTTP/2 run over TCP, HTTP/3 over QUIC), and a given client-to-proxy connection is exactly one of them (there is no "HTTP/1.1 inside QUIC"). The same capsule format is the content stream over all three ([RFC 9297 §3.1]); an HTTP/3 DATA frame type would not exist on a TCP-based hop. Only the request that opens the tunnel adapts to the version: Extended CONNECT (the :protocol pseudo-header) over HTTP/2 and HTTP/3, versus an HTTP Upgrade: connect-udp101 Switching Protocols over HTTP/1.1 ([RFC 9298 §3.2]), while the connect-udp token and the datagram/capsule data model stay identical.
  • They survive intermediaries end to end. The Capsule Protocol lets endpoints "reliably communicate request-related information end-to-end... even in the presence of HTTP intermediaries" ([RFC 9297 §3]), and an intermediary (even one translating between HTTP versions) "SHOULD forward Capsules without modification" ([RFC 9297 §3.2]). Tunnel control data thus reaches the far end intact through a chain of heterogeneous HTTP hops, which new frame types re-framed at each version boundary could not guarantee.
Note

Two misreadings to head off. First, "identical over HTTP/1.1, HTTP/2, and HTTP/3" is about the HTTP version of the connection carrying the tunnel (the client-to-proxy hop and any intermediaries on it), not about the tunneled payload. That payload is opaque, non-HTTP packets (UDP payloads or IP packets); it has no HTTP version, and the proxy never parses it. The version-independence is a property of the carrier, not the cargo. Second, the packets being proxied to the origin do not normally travel in capsules at all: over HTTP/3 they ride HTTP Datagrams in QUIC DATAGRAM frames, the fast path above. A capsule value is either control data the tunnel needs reliably (CONNECT-IP's address and route assignment, §12.4) or, only when the carrying connection is HTTP/2 or HTTP/1.1 and has no QUIC datagrams, a tunneled packet in a DATAGRAM-capsule fallback. So the Capsule Protocol is not a way to "proxy HTTP content"; MASQUE proxies packets, and capsules are its reliable control-and-fallback channel.

Each capsule is a small type-length-value tuple ([RFC 9297 §3.2]):

  • a Capsule Type (varint) identifying it,
  • a Capsule Length (varint), and
  • a Capsule Value of that many bytes, "whose semantics are determined by the Capsule Type."

How does a receiver know a given data stream speaks capsules at all? Never by sniffing the bytes. The knowledge is bound to the request that opened the stream: "definitions of new HTTP upgrade tokens can state that their associated request's data stream uses the Capsule Protocol" ([RFC 9297 §3.2]), and connect-udp and connect-ip are exactly such definitions. So when an endpoint accepts an Extended CONNECT (or 101 upgrade) for one of those protocols, it records, as per-stream state, that everything on that stream's data stream from now on is capsules — the same way it already knows the stream's other properties. On the wire the capsules are indistinguishable from any other message content. An endpoint additionally signals the protocol explicitly with the Capsule-Protocol: ?1 header field (?1 is a Structured-Fields boolean true, [RFC 9297 §3.4]), which also lets intermediaries handle datagrams for upgrade tokens they do not recognize. Like every extensible varint space in this book, unknown capsule types are skipped, not errors: a receiver "MUST silently drop that Capsule and skip over it" ([RFC 9297 §3.2]): the same forward-compatibility discipline as HTTP/3 frames and GREASE (§5.1).

The bridge between the two carriers is the DATAGRAM capsule (type 0x00): it "allows HTTP Datagrams to be sent on a stream using the Capsule Protocol," and such datagrams "have the same semantics as those sent in QUIC DATAGRAM frames" ([RFC 9297 §3.5]). So the same HTTP Datagram Payload rides either in a QUIC DATAGRAM frame (over HTTP/3) or inside a DATAGRAM capsule (over TCP-based HTTP), the payload format is identical; only the carrier changes.

One control message, three carriers #

If the motivation still feels abstract, walk one concrete control message through every carrier. When a CONNECT-IP proxy assigns a client the address 192.0.2.11/32 (§12.4), that assignment is an ADDRESS_ASSIGN capsule ([RFC 9484 §4.7.1]): nine bytes, 01 07 00 04 c0 00 02 0b 20. Losing it is not an option — a VPN whose address assignment "maybe arrived" is not a VPN — so it cannot travel as a datagram. And it must arrive whether the client reached the proxy over HTTP/3, over HTTP/2 on TCP, or over HTTP/1.1 via an Upgrade. (If a TCP carrier sounds contradictory for MASQUE: HTTP/3 is the preferred substrate, but the TCP variants are fully specified — §12.3 shows the HTTP/1.1 exchange on the wire.) Capsules make version-independence automatic by living in message content: each version's ordinary, reliable data stream carries the bytes without knowing or caring what they mean.

Read the figure right to left: the capsule bytes never change; only the grey carrier framing to their left does, and none of that framing belongs to the Capsule Protocol. That is the whole motivation in one picture. A tunnel needs two channels: an unreliable one for the packets themselves (HTTP Datagrams, the fast path) and a reliable, ordered one for control messages the tunnel cannot function without. The reliable channel already existed, because every HTTP version gives a request a reliable data stream ([RFC 9297 §3.1]); capsules are nothing more than the minimal type-length-value framing that turns that byte stream into distinct messages. A version-translating intermediary re-frames the carrier but forwards the content, so the same nine bytes reach the far end intact ([RFC 9297 §3.2]).

Reliable or not: the choice that matters #

The two carriers differ in exactly one property, and it is the one Chapter 9 made central:

  • QUIC DATAGRAM frames are unreliable and unordered: a lost inner packet is simply lost (§9.3), which is what a tunnel wants so the inner protocol handles recovery.
  • Capsules are reliable and ordered, but an intermediary "can re-encode DATAGRAM Capsules into QUIC DATAGRAM frames when forwarding," which "could result in loss or reordering" ([RFC 9297 §3.5]).

This is why HTTP/3 is MASQUE's natural substrate (§12.1): it can carry tunneled packets unreliably and avoid the nested-retransmission pathology. The Capsule Protocol is the compatibility path and the channel for genuinely control-plane data that must arrive.

Worked example: reading a datagram off the wire #

Suppose a client opened a tunnel on request stream 8. Its Quarter Stream ID is 8 ÷ 4 = 2. To send an inner packet, the client emits a QUIC DATAGRAM frame whose payload begins with the varint 2 (the Quarter Stream ID), followed by the HTTP Datagram Payload, which for CONNECT-UDP is a Context ID and the inner UDP payload (§12.3). A receiver reads the varint 2, multiplies by four to recover stream 8, finds the tunnel that stream opened, and hands the remaining bytes to that tunnel's handler. Had the connection been HTTP/2 over TCP, the very same payload would instead arrive inside a DATAGRAM capsule (Type 0x00, a Length, then the payload) on stream 8's data stream, reliably, in order.

Note

The Quarter Stream ID is a small but clever piece of engineering. Because a datagram must name which request it belongs to, and QUIC DATAGRAM frames carry no stream association of their own (§9.3), some identifier has to lead every datagram. Using the stream ID directly would waste two low bits that are always zero for client request streams; dividing by four reclaims them, shrinking the varint (often to a single byte) on every tunneled packet: a per-packet saving that matters when the tunnel carries millions of them.

In practice

editorial When you turn on datagram support, remember the negotiation is bidirectional and both sides must advertise SETTINGS_H3_DATAGRAM = 1 before any QUIC DATAGRAM frame is legal: a one-sided setting silently yields a tunnel that can open but never carry a datagram. If you also support the HTTP/2 fallback, share one payload encoder between the QUIC-DATAGRAM and DATAGRAM-capsule paths, since the HTTP Datagram Payload is byte-identical; only the framing differs. And treat unknown capsule types as skippable exactly as the spec requires: a proxy that errors on an unrecognized capsule will break against peers using a newer extension.

Takeaways #

An HTTP Datagram is request-associated datagram data, carried over HTTP/3 as the payload of a QUIC DATAGRAM frame prefixed with a Quarter Stream ID (the request stream ÷ 4), negotiated by SETTINGS_H3_DATAGRAM. The Capsule Protocol is the reliable, ordered alternative: type-length-value capsules on the request's data stream, signalled by Capsule-Protocol: ?1, with the DATAGRAM capsule (0x00) carrying the identical payload for non-HTTP/3 transports. Unreliable datagrams are what MASQUE wants; capsules are the fallback and the control channel. With the foundation in place, §12.3 uses it to proxy UDP.