skip to content

QUIC identifies a connection by connection IDs rather than by the source IP address and port. What does that enable when a phone moves from Wi-Fi to cellular, and what routing and privacy issues follow?

level: middleimportance: should knowfreq 30%

answer

  1. identity = connection ID, not the 4-tuple
  2. Wi-Fi → cellular keeps the connection alive
  3. PATH_CHALLENGE / PATH_RESPONSE validates the new path
  4. NEW_CONNECTION_ID pool + rotate to avoid linkability
  5. LBs must route on connection ID (encode server ID inside)

basics

~20 s

Because the connection is keyed by a connection ID, a client that changes IP address keeps the same QUIC connection: no new handshake, no lost streams. The server validates the new path first. Load balancers must route by connection ID, and IDs are rotated so observers cannot link a user across networks.

solid answer

~60 s

A TCP connection is the 4-tuple of source and destination IP and port, so changing networks destroys it and the application must reconnect — new handshake, new TLS, lost in-flight requests. QUIC packets instead carry a **destination connection ID**, and the endpoints keep state keyed by that, so an IP change is just a new path for the same connection: downloads and streams continue. The safeguards around it matter. Before the server sends much on the new path it performs **path validation** with PATH_CHALLENGE / PATH_RESPONSE, so an attacker cannot redirect a victim's traffic by spoofing a source address, and the anti-amplification limit still applies. Congestion state is reset for the new path since it is a different network. Two consequences to name: **load balancers must key on connection ID**, not on the 4-tuple, which is why servers encode routing information into the IDs they issue. And because a stable ID would let an on-path observer link a user across networks, each endpoint issues a pool of IDs via NEW_CONNECTION_ID and the peer switches to an unused one when it migrates, retiring the old with RETIRE_CONNECTION_ID.

code

http · 9 lines
http
[old path: 192.0.2.7:51820]  packets with DCID=A
server -> client  NEW_CONNECTION_ID  seq=1 cid=B
server -> client  NEW_CONNECTION_ID  seq=2 cid=C

[new path: 198.51.100.4:44311]  first packet uses DCID=B
server -> client  PATH_CHALLENGE  data=0x9f3c...
client -> server  PATH_RESPONSE   data=0x9f3c...
client -> server  RETIRE_CONNECTION_ID seq=0
(congestion controller and RTT estimate reset for the new path)

go deeper

for a junior

Know that QUIC identifies connections by an ID, so switching from Wi-Fi to cellular does not drop the connection or force a new handshake.

for a middle

Add path validation with PATH_CHALLENGE, the congestion reset on the new path, and connection ID rotation for privacy.

for a senior

Focus on operations: routable connection IDs for load balancers, initial-packet routing, stateless reset, and IP-bound session logic that breaks under migration.

for a principal

Treat connection identity as an architectural choice — where routing state lives, how re-sharding and drains interact with already-issued IDs, and what the privacy and IP-based-security implications are for your platform.

## The problem with the 4-tuple A TCP connection is identified by (source IP, source port, destination IP, destination port). Change any element and it is a different connection. So a phone handing off from Wi-Fi to cellular gets a new source IP, and every TCP connection dies: the app sees resets or timeouts, then reconnects, re-handshakes TLS, and re-issues requests. Even without a handoff, a NAT rebinding — a home router reassigning the external port after an idle period — breaks connections the same way. ## Connection IDs QUIC decouples connection identity from the network path. Every QUIC packet carries a **destination connection ID** (DCID), a variable-length opaque value chosen by the *receiver* of that packet. Each endpoint tells its peer which IDs to use for packets sent to it. Because the endpoint state is keyed by connection ID, arriving packets from a new IP address and port still map to the existing connection, with its keys, streams, and flow-control state intact. ## Migration in practice Only the **client** may migrate in QUIC v1 (server migration is not supported). The sequence: 1. The client starts sending from the new address, using a connection ID it has not used on the old path. 2. The server sees packets on a new path. It must **validate the path**: send a PATH_CHALLENGE containing random data, and require a PATH_RESPONSE echoing it. This proves the peer at the new address can actually receive there, defeating address-spoofing attacks that would otherwise let an attacker aim a high-rate flow at a victim. 3. Until validation completes, the **anti-amplification limit** applies — the server sends at most three times the bytes it has received from the unvalidated address. 4. **Congestion controller and RTT estimates are reset** for the new path, because bandwidth and latency characteristics are different. The connection continues, but it re-probes capacity. The result for a user: a download or upload keeps going across a handoff, and there is no re-handshake. A NAT rebind is handled by exactly the same machinery, which is arguably the more common everyday benefit. ## Privacy: linkability If a connection kept one visible ID forever, a passive observer would see the same identifier appear from your home IP and then from your cellular IP, linking the two — a tracking primitive that TCP never offered (a TCP connection simply dies). QUIC therefore treats connection IDs as rotatable: - Each endpoint issues **several** connection IDs to its peer via **NEW_CONNECTION_ID** frames, each with a sequence number and a stateless-reset token. - On migration the client **must** use a previously unused connection ID, and retire the old ones with **RETIRE_CONNECTION_ID**. - IDs are opaque and should be unlinkable to each other from the outside. This is only partial protection — an observer that sees both paths can still correlate by timing or packet sizes — but it removes the trivial identifier. ## Routing: the load-balancer problem This is the practical operations issue. A layer-4 load balancer normally hashes the 4-tuple to pick a backend. Under QUIC that breaks in two ways: after migration the tuple changes so the hash sends the client to a different server, which has no state for that connection; and even without migration, UDP has no connection tracking to lean on. The standard solution is **routable connection IDs**: the server encodes routing information (a server identifier or shard index, usually encrypted or obfuscated so it does not leak topology) into the connection IDs it issues. The load balancer parses the DCID and forwards accordingly. Consequences to be aware of: - **The initial packet is special.** The client's first packet uses a connection ID it invented, so the balancer must route Initial packets by some other rule (hash) and the chosen server then issues its own routable IDs. - **Rotating server identity.** If you re-shard or drain a node, connection IDs already issued still point at it; drains must be graceful. - **Stateless reset.** If a server loses state (restart) it cannot decrypt the connection, so it emits a stateless reset using the token it previously issued, letting the client tear down immediately rather than time out. ## Limits Migration is not seamless in every sense: the new path is congestion-unknown so throughput dips while it re-probes; some middleboxes block UDP on the new network entirely, in which case the client must fall back; and applications that pin authorization to an IP address (session-bound-to-IP checks) will misbehave when the address legitimately changes mid-connection. If your security model uses source IP for anything, revisit it before enabling HTTP/3.

  • Why does the server challenge the new path instead of just accepting packets from the new address?
    Because source addresses can be spoofed. Without validation an attacker could send packets claiming a victim's address and have the server redirect a high-rate flow at that victim. PATH_CHALLENGE requires a response echoing random data, which only someone actually reachable at that address can produce, and the anti-amplification limit caps what the server sends before that proof arrives.
  • What does connection migration mean for a layer-4 load balancer?
    It can no longer route by the UDP 4-tuple, because the client's address changes mid-connection and the same connection must still reach the same backend. The standard approach is for each server to encode a routing identifier into the connection IDs it issues, obfuscated so topology does not leak, and for the balancer to parse the destination connection ID; initial packets, which use a client-chosen ID, need a separate rule.

TCP addresses you by the seat you are sitting in; move seats and the conversation ends. QUIC addresses you by a name tag you carry, and you swap tags when you move so nobody in the room can follow you between seats.

saying these in an interview costs you the question

  • Thinking migration works by re-handshaking quickly — the point is that no new handshake occurs
  • Believing the connection ID stays constant for the connection's lifetime, ignoring rotation for privacy
  • Assuming existing 4-tuple-hashing load balancers work unchanged with QUIC
  • Expecting full throughput immediately after migration, when congestion state is reset for the new path
  • Keeping authorization or session checks bound to the client IP address

context