Learning objective. Build reproducible test scenarios by emulating network conditions (fixed delay, loss, reordering, bandwidth limits, path changes) so that a QUIC behavior can be triggered on demand and a measurement can be compared release to release, using either kernel tools or the in-process proxies this book's labs already use.
Why emulate #
Real networks are the enemy of reproducibility. The RTT, loss, and reordering that drive QUIC's most interesting behavior (Chapter 8) vary minute to minute, so a test run against the live internet measures the network as much as the code. A regression that only appears at 2% loss and 200 ms RTT will hide on a fast office link. To test QUIC seriously you manufacture the conditions: pin the delay, inject a known loss rate, force reordering, cap the bandwidth, or trigger a migration, then run the same scenario every time.
Two ways to emulate #
There are two practical approaches, and this book uses both:
Kernel emulation (tc netem). On Linux, the traffic-control subsystem's netem qdisc injects
impairments at the interface: tc qdisc add dev eth0 root netem delay 100ms loss 2% reorder 25% 50%
adds 100 ms of delay, 2% loss, and reordering. A token-bucket filter (tbf/htb) caps bandwidth. Pair
it with network namespaces (ip netns) to build an isolated client–router–server topology on one host.
This is the most realistic option and the standard for serious performance testing — but it needs Linux
and root, and its randomness must be seeded for repeatability.
In-process emulation (a UDP proxy). The labs in this book take the portable route: a small asyncio
UDP proxy between client and server that applies impairments in user space, with no root and no
kernel and identical behavior on any OS. The §8.5 delay proxy adds a fixed one-way delay
to make RTT visible; the §10.5 rebinding proxy changes its source port to trigger a
migration. The same pattern extends to the other knobs: drop a datagram with some probability (loss),
swap two datagrams (reordering), or rate-limit forwarding (bandwidth). It is less faithful than netem
at high rates, but it is deterministic and offline, which is exactly what a repeatable test wants.
Mapping conditions to behaviors #
The point of each knob is to exercise a specific mechanism from earlier chapters:
| Emulated condition | QUIC behavior exercised | Reference |
|---|---|---|
| Fixed one-way delay | RTT estimate, BDP, window sizing | §8.4 |
| Loss percentage | loss detection, retransmission, PTO | §8.2 |
| Reordering beyond 3 packets | spurious retransmits, kPacketThreshold |
§8.2 |
| Bandwidth cap | congestion-control ramp and steady state | §8.2 |
| Jitter (variable delay) | de-jitter buffering, real-time playout | §9.1 |
| Source-port rebinding | connection migration / path validation | §10.3 |
A good test suite has one scenario per row, plus combinations: "long fat network" (high delay + high bandwidth) to stress flow-control windows, or "lossy satellite" (high delay + loss) to stress recovery.
Making a scenario reproducible #
Emulation only helps if the scenario is the same every time, which takes discipline on three fronts:
- Fix every parameter and seed. Delay, loss rate, bandwidth, and any randomness (loss draws,
reorder choices) must be set explicitly.
netem's loss is pseudo-random: seed it, or accept run-to-run variance and average many runs. The in-process proxies avoid this by being fully deterministic where possible (§10.5). - Fix the workload and the stack version. Same request pattern, same payload sizes, same pinned library version (Chapter 13); otherwise you are comparing two variables at once.
- Capture the evidence. Every run should emit a qlog (and optionally a keylog + pcap, §11.3) so the same scenario can be re-examined and two runs compared byte for byte.
Reproducibility is what turns a one-off "it seemed slower" into a measurement you can bisect against a code change — the foundation of the regression checklist in §11.5.
Worked example: reproducing a satellite regression #
A user reports poor throughput "on satellite." You cannot debug a satellite, but you can emulate one: a scenario with 600 ms RTT and 1% loss (§8.4's GEO profile), a fixed payload, and the pinned client. Run it against the release before and after the report, each emitting a qlog. The metrics from §11.2 (goodput, the limiting ceiling, loss rate, PTO count) are now directly comparable because everything but the code was held constant. If the newer run is congestion-limited with more PTOs at the same emulated loss, the regression is real and in the recovery path; if the two runs match, the problem is something the emulation didn't capture (a specific middlebox, a different real loss pattern) and you refine the scenario. Either way you have converted an unreproducible field report into a controlled experiment.
Emulation faithfully reproduces what you model, and nothing else — which is both its power and its trap.
A netem profile of "100 ms delay, 2% uniform loss" is not a real mobile network: real losses are bursty,
real RTT has structure, and real paths have middleboxes that rewrite or drop QUIC. So emulation is ideal
for isolating a mechanism (does my recovery handle 2% loss correctly?) and for regression testing
(is release N slower than N−1 under the identical model?), but a clean emulated pass does not guarantee
field performance. Pair reproducible emulation with occasional real-network captures (§11.3)
to catch what your model omits.
editorial Keep a small library of named scenarios ("lan", "mobile",
"long-fat-network", "lossy-satellite", "nat-rebind") as code, each pinning its exact parameters, and run
them in CI on every change. The in-process proxy approach is worth it precisely because it runs anywhere
without root, so the scenarios execute in the same CI that runs unit tests; reserve tc netem in a Linux
lab for the high-fidelity performance runs where user-space forwarding would distort the result. Whatever
you choose, make each scenario emit a qlog artifact, so a failing CI run hands you the trace to debug
instead of just a red X.
Takeaways #
Reproducible QUIC testing means emulating the network (fixed delay, loss, reordering, bandwidth caps,
migrations) with either kernel tools (tc netem plus namespaces, high fidelity, Linux+root) or the
in-process UDP proxies this book's labs use (deterministic, offline, portable). Each knob targets a
specific mechanism, and reproducibility requires fixing every parameter, seed, workload, and stack version
while capturing a qlog per run. That controlled foundation is what makes a performance regression provable,
which §11.5 turns into a checklist.