---
title: UDP and QUIC Transport Layer
url: https://doc.liz6.com/en/networking/03-l4-transport-layer/02-udp-and-quic-transport-layer
locale: en
area: networking
tags:
- networking
- l4-transport-layer
date: 2026-06-30
modified: 2026-07-16
description: '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.'
---

# 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:

<svg viewBox="0 0 720 390" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="QUIC connection migration process: Connection ID remains unchanged, connection persists even if IP changes">
  <defs>
    <marker id="cma" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="390" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">QUIC Connection Migration: Connection ID Unchanged, Connection Persists Despite IP Change</text>
  <rect x="150" y="48" width="420" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">Client · WiFi</text>
  <text x="360" y="88" text-anchor="middle" font-size="11" fill="#4f46e5">IP 192.168.1.x · QUIC flow uses CID_A → CID_B to identify connection</text>
  <line x1="360" y1="104" x2="360" y2="142" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="127" font-size="11" font-weight="700" fill="#334155">Switch to 4G (Network Switch)</text>
  <rect x="150" y="142" width="420" height="56" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="163" text-anchor="middle" font-size="13" font-weight="700" fill="#115e59">Client · 4G</text>
  <text x="360" y="182" text-anchor="middle" font-size="11" fill="#0f766e">IP 10.10.10.x · Sends PATH_CHALLENGE (new IP, same CID_A → CID_B)</text>
  <line x1="360" y1="198" x2="360" y2="236" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <text x="380" y="221" font-size="11" font-weight="700" fill="#334155">Server Response</text>
  <rect x="150" y="236" width="420" height="56" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="360" y="257" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">PATH_RESPONSE</text>
  <text x="360" y="276" text-anchor="middle" font-size="11" fill="#4338ca">Verify new path is reachable → Migration confirmed</text>
  <line x1="360" y1="292" x2="360" y2="310" stroke="#475569" stroke-width="1.8" marker-end="url(#cma)"/>
  <rect x="60" y="314" width="600" height="56" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="76" y="336" font-size="12.5" fill="#166534">Connection seamlessly migrated! The application layer sees no interruption—</text>
  <text x="76" y="356" font-size="12.5" fill="#166534">QUIC connections are identified by Connection ID (not IP/port), so switching networks does not drop the connection.</text>
</svg>

### 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*
