Learning objective. Trace a single UDP payload byte-for-byte as it travels from a client, through a MASQUE proxy, to its target, seeing every layer of encapsulation the chapter introduced stacked on the wire, and the reverse trip back.
The whole stack, on one packet #
The chapter built MASQUE up in layers; this section puts them all on a single tunneled packet. Suppose a
client has an open connect-udp tunnel on request stream 8, and its application wants to send one UDP
payload (a DNS query, or an inner QUIC packet) to the target. Here is exactly what goes on the wire from
the client to the proxy, outer layer to inner:
Reading the stack top to bottom is reading the encapsulation outer to inner. It is the whole chapter in one figure:
- QUIC 1-RTT packet (Chapter 1). The outer QUIC connection to the proxy. Its short header carries a connection ID and packet number, and its encrypted frame payload holds a DATAGRAM frame. Everything below this line is AEAD-encrypted on the wire; the network sees only an opaque QUIC packet to the proxy.
- DATAGRAM frame (
0x30, §9.3, [RFC 9221]). The unreliable frame type, the one that makes MASQUE efficient (§12.1). Type0x30carries its data to the end of the packet with no length field. Its data is an HTTP/3 Datagram. - HTTP/3 Datagram (§12.2, [RFC 9297]). A single varint, the Quarter Stream ID
0x02, associates this datagram with request stream 8 (2 × 4), the stream the tunnel was opened on. The rest is the HTTP Datagram Payload. - CONNECT-UDP payload (§12.3, [RFC 9298]). A Context ID of
0x00declares that what follows is a plain UDP packet's payload. - Inner UDP payload: the actual bytes the application wanted to send, which the proxy will forward to the target as a genuine UDP packet. To the proxy these are opaque; it never looks inside.
So the "extra" cost of tunneling this UDP payload, beyond the outer QUIC/UDP/IP headers it would need anyway, is just two varint bytes (the Quarter Stream ID and Context ID 0) plus the one-byte DATAGRAM frame type. That thinness is the point.
What the proxy does with it #
When the proxy receives this packet it unwinds the stack from the outside in, mirroring the layers above:
it decrypts the QUIC packet, reads the DATAGRAM frame, reads the Quarter Stream ID 2 and multiplies by
four to find request stream 8 (the tunnel), reads Context ID 0 to know the rest is a UDP payload, and
sends those remaining bytes as a real UDP packet from its own address to the target named when the tunnel
was opened (§12.3). The proxy did four cheap reads and one socket write; it never parsed the
inner payload.
The return trip #
The reply retraces the stack in reverse. The target sends a UDP packet back to the proxy's address; the
proxy validates the source (§12.3), takes the UDP payload, and re-encapsulates it: Context ID
0, Quarter Stream ID 2, into a DATAGRAM frame, into a 1-RTT packet on the proxy→client direction of
the same outer connection. The client decrypts it, follows the Quarter Stream ID back to stream 8, strips
Context ID 0, and hands the inner UDP payload to its application. From the application's point of view it
sent a UDP packet and got a UDP reply; every layer in between was transparent.
Where CONNECT-IP differs #
For a CONNECT-IP tunnel (§12.4) only the two innermost layers change: the masque layer's
Context ID 0 now means "a full IP packet follows" rather than a UDP payload ([RFC 9484 §6]), and the
innermost bytes are a complete IP packet (headers included) that the proxy routes rather than sends to a
single fixed target. Everything above (the QUIC packet, the DATAGRAM frame, the HTTP/3 Datagram with its
Quarter Stream ID) is byte-for-byte identical. That is the layered design paying off one last time: UDP
proxying and IP proxying share the entire outer encapsulation and differ only in what the innermost payload
is.
Worked example: counting the nesting for QUIC-in-QUIC #
Take the sharpest case from §12.1, a client running a QUIC connection to a website through the proxy, and count the QUIC layers in the innermost payload. The inner UDP payload here is itself a whole inner QUIC packet (to the website). So on the wire, outer to inner, there are: the outer QUIC packet (to the proxy) → DATAGRAM frame → HTTP/3 Datagram → CONNECT-UDP Context ID → the inner QUIC packet (to the website) → its own frames and streams. Two independent QUIC connections are stacked, each with its own encryption, and the proxy sits between them able to read neither inner payload; it only routes opaque datagrams by Quarter Stream ID. The whole apparatus adds two varints per packet over a direct connection, which is why relays can carry real QUIC traffic at scale without the nested-retransmission penalty a reliable tunnel would impose (§12.1).
Played out in time, the inner connection's whole handshake is just four of the tunnel's datagrams. Each inner flight from Chapter 2 becomes one Context-ID-0 payload; the proxy unwraps each into a real UDP packet toward the website and wraps the replies coming back:
This figure is the MASQUE counterpart to the "shape of a GET" from §1.3, and the comparison is instructive. A GET nests HTTP/3 frames inside QUIC STREAM frames inside a packet: application data over a reliable, ordered stream. A tunneled packet nests a UDP (or IP) payload inside an HTTP/3 Datagram inside a QUIC DATAGRAM frame: application data over an unreliable, unordered channel. Same connection, same multiplexing, opposite delivery guarantee: the STREAM path for things that must arrive in order, the DATAGRAM path for packets a tunnel should carry as-is. MASQUE is what you get when you point HTTP/3's datagram machinery at forwarding other people's packets.
editorial When you debug a MASQUE deployment, read the stack exactly as this figure lays it out. And remember §11.3: with the outer connection's keys you can decrypt down to the DATAGRAM frame and see the Quarter Stream ID and Context ID, but the inner payload of a QUIC-in-QUIC tunnel is protected by keys you don't have, so it stays opaque even in a decrypted capture. That is a feature, not a limitation: the proxy is supposed to be blind to the inner traffic. Verify the two varints (Quarter Stream ID matches the tunnel's stream; Context ID is 0 for a plain packet), confirm the outer datagram fits the path MTU (§9.2), and only then look for problems in the inner protocol.
Takeaways #
A tunneled UDP payload nests, outer to inner: a QUIC 1-RTT packet to the proxy, a DATAGRAM frame, an HTTP/3 Datagram (Quarter Stream ID), a CONNECT-UDP payload (Context ID 0), and the inner UDP payload, adding only two varints of overhead per packet. The proxy unwinds the stack, forwards the opaque inner bytes, and re-encapsulates the reply; CONNECT-IP is identical except the innermost payload is a full IP packet. This completes the MASQUE chapter, the whole datagram/capsule/proxying story built on QUIC's DATAGRAM frames; the chapter quiz checks the model, and Chapter 13 closes the book with implementation and interoperability guidance.