How Chromium Implements QUIC and HTTP/3 Over UDP Sockets
This article provides a technical breakdown of how the Chromium web browser implements the QUIC transport protocol and HTTP/3 using User Datagram Protocol (UDP) sockets. It explores the architecture of Chromium's network stack, detailing the transition from traditional TCP to user-space UDP connections, the role of the QUICHE library, how the browser discovers HTTP/3 support, and how it manages encryption, multiplexing, and congestion control to deliver faster web performance.
Moving the Transport Layer to User Space
Traditionally, web browsers rely on the Transmission Control Protocol (TCP), which is managed entirely by the operating system's kernel. To implement QUIC—the transport layer underlying HTTP/3—Chromium bypasses the OS TCP stack and uses UDP instead.
Because UDP is connectionless and provides no built-in reliability,
ordering, or congestion control, Chromium uses UDP sockets merely as a
raw pipe to send and receive encrypted datagrams. It interacts with the
operating system using its internal UDPClientSocket class.
All the complex transport logic is moved out of the OS kernel and
directly into Chromium's user-space C++ code. This allows Chromium
engineers to update and optimize the transport protocol alongside
regular browser updates without waiting for users to upgrade their
operating systems.
Discovering HTTP/3 and Racing Connections
Chromium does not immediately know if a remote server supports HTTP/3 over UDP. To figure this out, the browser relies on protocol discovery and connection racing:
- Initial Connection: On the first visit to a website, Chromium usually connects via HTTP/2 over a standard TCP socket.
- The Alt-Svc Header: If the server supports HTTP/3,
it includes an
Alt-Svc(Alternative-Service) header in its HTTP response (e.g.,Alt-Svc: h3=":443"). This tells Chromium that an HTTP/3 endpoint is available on UDP port 443. - Caching and Racing: Chromium caches this
Alt-Svcmapping in itsHttpServerProperties. On subsequent requests to the same domain, Chromium’sHttpStreamFactoryinitiates a connection race. It simultaneously attempts to open a QUIC session over UDP and a fallback TCP connection (typically with a slight delay given to TCP). Whichever protocol completes its handshake first wins the race and handles the HTTP request.
The Core
Architecture: QUICHE and the net/ Directory
Chromium’s implementation of QUIC and HTTP/3 lives primarily in the
net/quic/ directory and is powered by QUICHE (QUIC, HTTP/2,
Etc.), Google's shared, production-ready C++ library for networking
protocols. Chromium wraps the core QUICHE engine in browser-specific
classes to integrate it with its asynchronous event loop:
QuicStreamFactory: This is the central manager for all QUIC connections in Chromium. It handles socket creation, binds theUDPClientSocket, and pools existing QUIC sessions so multiple tabs requesting the same domain can share a single UDP socket.QuicChromiumClientSession: This class represents a single, active QUIC connection to a server. It manages the state of the connection and handles stream multiplexing.QuicChromiumClientStream: This represents an individual HTTP/3 request/response pair. Multiple streams are multiplexed over a singleQuicChromiumClientSession. Because QUIC manages streams independently in user space, a lost UDP packet only delays the specific stream whose data was inside that packet, completely eliminating TCP's head-of-line blocking issue.
Integrated Encryption with BoringSSL
Unlike TCP, where the transport handshake and the TLS cryptographic handshake happen sequentially, QUIC combines them into a single process. Chromium uses BoringSSL (Google's fork of OpenSSL) to provide TLS 1.3 encryption for QUIC.
Instead of BoringSSL writing directly to a socket, Chromium hooks
BoringSSL directly into the QUIC engine. BoringSSL generates the
cryptographic handshake data, and Chromium encapsulates it into special
QUIC CRYPTO frames, which are then packed into UDP
datagrams. This tight integration allows Chromium to establish a secure
HTTP/3 connection in a single round-trip (1-RTT). If Chromium has
connected to the server previously, it caches the cryptographic keys and
uses 0-RTT (Zero Round Trip Time) resumption, sending encrypted HTTP/3
request data in the very first UDP packet.
The Packet Read/Write Loop and Congestion Control
To make unreliable UDP act like a reliable stream for HTTP/3, Chromium implements a continuous packet processing loop coupled with user-space congestion control.
- Writing Packets: When the browser makes an HTTP/3
request, Chromium compresses the HTTP headers using QPACK (the HTTP/3
equivalent of HPACK). The data is broken into QUIC
STREAMframes, encrypted, and handed to theQuicChromiumPacketWriter. This class pushes the final UDP datagram to the OS socket. To prevent overwhelming the network, Chromium uses pacing timers to space out the transmission of these UDP packets. - Reading Packets: The
QuicChromiumPacketReadercontinuously listens to theUDPClientSocketfor incoming data. When a datagram arrives, it is read from the OS buffer, decrypted via BoringSSL, and parsed into frames. The data is then reassembled in the correct order and passed up to the browser's rendering engine. - Reliability and Congestion Control: Because the OS
does not track dropped UDP packets, Chromium tracks them using QUIC
packet numbers and
ACK(Acknowledgment) frames. If a packet is not acknowledged by the server, Chromium retransmits the missing data in a newly numbered UDP packet. To manage network bandwidth, Chromium runs advanced congestion control algorithms—such as Cubic and BBR (Bottleneck Bandwidth and Round-trip propagation time)—directly in its network thread, dynamically adjusting the UDP sending rate based on packet loss and round-trip time measurements.
Connection Migration and TCP Fallback
Using UDP sockets allows Chromium to implement Connection Migration.
In TCP, a connection is identified by a 4-tuple (Source IP, Source Port,
Destination IP, Destination Port). If a user's device switches from
Wi-Fi to cellular data, the IP changes, and the TCP connection breaks.
Chromium's QUIC implementation circumvents this by embedding a random
64-bit Connection ID in every UDP packet header. When a network switch
occurs, Chromium opens a new UDPClientSocket on the new
network interface and continues sending packets with the same Connection
ID. The server recognizes the ID, and the HTTP/3 session continues
without interruption.
Finally, because some corporate firewalls and routers aggressively
block UDP traffic, Chromium maintains a strict fallback mechanism. If
the QuicStreamFactory detects that UDP packets are being
blackholed (silently dropped by the network) either during the handshake
or mid-connection, it immediately abandons the QUIC session, marks QUIC
as broken for that specific network interface, and seamlessly retries
the request over a standard TCP socket using HTTP/2.