On this page
UDP and QUIC Transport Layer
UDP is essentially just "IP with port numbers." QUIC wraps a complete reliable transport layer on top of UDP: 0-RTT handshake, head-of-line blocking-free stream multiplexing, connection migration—QUIC gets it right in one go, whereas TCP took 30 years to learn these lessons.
Overview
UDP is the simplest transport layer protocol—an 8-byte header, connectionless, no retransmission, no flow control. Its "rudimentary" nature is actually an advantage: DNS queries (one round-trip), real-time audio/video (dropping a few frames doesn't affect experience), and VPNs (WireGuard) all choose UDP. QUIC implements a complete modern transport layer on top of UDP, merging TCP's flow control/congestion control and TLS 1.3 encryption into a single protocol, solving TCP's head-of-line blocking and connection migration issues. HTTP/3 runs on QUIC.
UDP
UDP Header (fixed 8 bytes):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Length: Total length of UDP header + payload (minimum 8 = header only, maximum 65535)
Checksum: Optional for IPv4 (0=no checksum), mandatory for IPv6 — covers pseudo-header (src/dst IP +
protocol + length) + UDP header + payload
UDP does nothing beyond "port + length + optional checksum"—no connection, no retransmission, no flow control, no congestion control. If the application layer needs reliability, it must implement it itself. QUIC is precisely a case of "the application layer implementing the transport layer."
QUIC Transport Layer
QUIC is not "UDP + reliability wrapper"—it is a brand-new transport layer implemented on top of UDP, merging TCP's flow control/congestion control and TLS's encryption/authentication into one protocol.
QUIC Packet Structure
QUIC Long Header (Initial/Handshake/0-RTT packets):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1| Type | Version (32 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DCID Len | Destination Connection ID (0-20 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SCID Len | Source Connection ID (0-20 bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
QUIC Short Header (1-RTT data packets):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|S|R|R|R| Destination Connection ID (...) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Header Form (bit 0): 1=Long, 0=Short
Fixed Bit (bit 1): Always 1 — used for version negotiation detection (middleboxes cannot set this bit)
Spin Bit (bit 2 in Short): Flips once per RTT — observers can measure RTT
Reserved Bits: 0, if middleboxes modify these bits → GREASE detection → connection close
QUIC Connection: Connection ID
TCP connection = 5-tuple (src_ip, dst_ip, src_port, dst_port). IP changes → connection drops.
QUIC connection = Destination Connection ID + Source Connection ID. Endpoints can change Connection IDs at any time (via NEW_CONNECTION_ID frame) → IP/port become fully variable.
Connection Migration:
Stream Multiplexing
A single QUIC connection has multiple streams:
Stream ID (62-bit, variable-length encoding):
Bit 0: 0=client-initiated, 1=server-initiated
Bit 1: 0=bidi, 1=unidirectional
Bits 2-61: stream number (0, 1, 4, 8, 12, ... — client starts at 0, server at 1,
bidi-only: even client, odd server)
Each stream is independent:
- Has independent flow control (MAX_STREAM_DATA / STREAM_DATA_BLOCKED)
- Packet loss on Stream 1 → only blocks data for Stream 1, Stream 3 continues delivery
- Solves TCP head-of-line blocking: In TCP, all streams' bytes are in the same byte stream → one loss blocks everything
Connection-level flow control: MAX_DATA / DATA_BLOCKED — limits total reception across all streams
0-RTT
First Connection:
1-RTT: ClientHello → ServerHello → EncryptedExtensions → Finished → (data)
Subsequent Connections (PSK mode):
Client: Caches the previous session ticket (containing PSK + transport parameters + server address)
0-RTT: ClientHello + PSK + early data — starts sending data before receiving ServerHello
Server: Verifies PSK → accepts early data → processes directly
0-RTT Security Risk — Replay:
Attacker replays 0-RTT packets → server processes duplicates → duplicate side effects (e.g., double charge)
QUIC Mitigation:
- Application layer: Only idempotent operations (GET, HEAD, OPTIONS) are allowed in 0-RTT
- Transport layer: Server can send REJECTED frame → client must retransmit 0-RTT data
- AEAD limits: The same key cannot be used for too many 0-RTT packets → limits replay window
QUIC Loss Detection
QUIC's loss detection is more precise than TCP's:
TCP: 3 duplicate ACKs → retransmit (but for SACK, distinguishes reordering vs loss)
QUIC:
- Each packet has an independent packet number (monotonically increasing, even retransmissions get new numbers)
- Receiver ACK frame: max received + missing ranges → precisely knows which packets are lost
- Loss detection:
1. Time threshold: Exceeds kTimeThreshold (9/8) × latest RTT → packet loss
2. Packet threshold: Exceeds kPacketThreshold (3) larger packets acknowledged → packet loss
QUIC Applications
HTTP/3: QUIC's largest user (RFC 9114)
DNS/DoQ: DNS over QUIC (RFC 9250) — 0-RTT query
SMB over QUIC: Windows Server 2022
RDP over QUIC: Remote desktop resistant to packet loss
References
- RFC: 768, 8999-9002, 9000 Appendix A (design rationale), 9250
- Implementations: quiche (Cloudflare), lsquic (LiteSpeed), quinn (Rust), ngtcp2 (C)
- LWN: "QUIC and HTTP/3"
Keywords: UDP, QUIC, connection migration, 0-RTT, stream multiplexing, head-of-line blocking, spin bit, GREASE