Skip to content

Implement L4S support and the Prague CCA - #2729

Open
zoltanszatmary wants to merge 3 commits into
quinn-rs:mainfrom
zoltanszatmary:l4s-final
Open

zoltanszatmary wants to merge 3 commits into
quinn-rs:mainfrom
zoltanszatmary:l4s-final

Conversation

@zoltanszatmary

@zoltanszatmary zoltanszatmary commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

This change set introduces support for the Low Latency, Low Loss, and Scalable Throughput (L4S) architecture (RFC 9330) by exposing ECN configuration and implementing the Prague scalable congestion control algorithm.

Changes

The changes made are three-fold:

  1. ECN Configuration: Adds configurable ECN modes (Classic, L4S, or Disabled) to TransportConfig and enables ECT(1) markings.
  2. TCP Prague: Implements the Prague congestion control algorithm with EWMA-based $\alpha$ (CE-fraction) estimation, virtual RTT scaling to reduce RTT dependence, and loss/ECN event double-reduction cooldowns.
  3. quinn-perf Support: Updates quinn-perf to support Prague and configured ECN, tracks stream RTT/RTTvar, and optimizes client shutdown logic.

Testing & Verification

To verify the implementation, I created a Mininet-based testbed (available here, though unfortunately, not everything is documented as of yet), the topology of which is seen in the figure below.

env

I measured six metrics in time (four endpoint-based, and one network-based).
Namely, one-way delay, RTT, CWND, network queue length, Prague's alpha value,
and the estimated throughput. The endpoint-based metrics were gathered and/or
derived from qlog entries (e.g., for calculating the one-way delay estimates,
the script paired up corresponding 1RTT packets -- assuming most packets were
not dropped). On the other hand, the queue length was monitored by a short
script running on the host taking the role of the router (configured with a
DualPI2 queue on its outbound interface facing the quinn-perf server host).

During the measurement scenario here, the quinn-perf client (on host h1) ran
for 60 seconds, continuously sending data to the quinn-perf server (on host h2)
through the network, with each round-trip experiencing a total delay of 24 ms,
and the network bandwidth being limited to 32 Mbps (with HTB).

The results of this run are depicted in the figure below.

plot

Referenced Issues

Fixes #1498 #2487.

@zoltanszatmary zoltanszatmary changed the title Implement L4S support for QUIC with the Prague CCA Implement L4S support and the Prague CCA Jul 10, 2026
@zoltanszatmary
zoltanszatmary force-pushed the l4s-final branch 3 times, most recently from 4e00076 to a53adec Compare July 11, 2026 17:50
@djc

djc commented Jul 13, 2026

Copy link
Copy Markdown
Member

Thanks for working on this! My review queue is a bit full so it might take me some time to give this a serious look. Also, I will be reviewing

before getting to this, which also makes some changes to the congestion controller interface. Would be good if you can have a look at whether any changes you've made to the trait are incompatible (if so, maybe give feedback on that PR).

@zoltanszatmary

zoltanszatmary commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Well, aside from that few parts of the code base where congestion control was touched by both branches (e.g., quinn-perf's --congestion selector, which could cause a minor merge conflicts), I don't see any major incompatibilities. Although, I certainly see that the Controller interface is getting a bit out of hand.

I mean I extended it with 7 more methods in addition to the 3 new methods added in his patch.

I've been thinking of better ways to achieve the same thing with less addition to the interface, but I couldn't come up with any so far. Or maybe one...?

So currently, Prague relies on an inner Controller composed inside of it for increasing the window until it receives increased ECN counts (e.g., regular old AIMD with NewReno). This however, could be changed by integrating the logic from NewReno directly into Prague at the cost of some code duplication.

Pacer::delay has also been modified by both of us, so there's that, but I find it one of the less important changes from this PR.

@zoltanszatmary
zoltanszatmary force-pushed the l4s-final branch 2 times, most recently from ee510aa to 1007ad2 Compare July 20, 2026 08:59
@zoltanszatmary
zoltanszatmary force-pushed the l4s-final branch 3 times, most recently from a00daa6 to 57d8981 Compare August 6, 2026 09:00
@zoltanszatmary
zoltanszatmary force-pushed the l4s-final branch 5 times, most recently from a774b02 to ce843fe Compare August 18, 2026 14:17
zoltanszatmary and others added 3 commits August 25, 2026 18:22
Extend the handling of ECN  to support classic (RFC 3168) and L4S (RFC 9330)
ECN validation, configuration, and congestion control algorithms (CCAs)
that make use of ECN (e.g. scalable congestion control algorithms as
defined in L4S).

Changes:

- Make ECN mode configurable via `TransportConfig` (Classic, L4S, or
  Disabled), enabling ECT(0) by default to preserve baseline CCA
  expectations.

- Detect changes to ECT(0)/ECT(1) markings during validation, and drop
  the check preventing ECT(1) to support L4S.

- Consult the active controller on ECN support, informing it of the
  pending ECN mode during querying, and pass ECN increments (rather than
  absolute counts) to decouple ECN processing from CCAs.

- Add an external slow start threshold setter and fix CUBIC's cwnd
  manipulation to decouple the recovery boundary from epoch start and
  recompute W_max on cwnd changes.

- Fix NewReno recovery clock-underflow by migrating
  `recovery_start_time` to `Option<Instant>`.

- Add qlog event types for ECN-CE increments, L4S CCA alpha values, and
  recovery exits.

- Expose RTT and jitter stats through send and receive streams.

Co-Authored-By: Felician Nemeth <[email protected]>
TCP Prague emerged as the de facto implementation for scalable
congestion control defined by the Low Latency, Low Loss, and Scalable
Throughput (L4S) architecture (see RFC 9330
<https://datatracker.ietf.org/doc/html/rfc9330>).

The need for L4S is motivated by the desire to prevent the build-up of
large network queues that add significant delays to packets, while at
the same time avoiding underutilization or severe losses (due to
tail-drops or AQM-elicited early drops).

In essence, L4S builds on three pillars:

1) support from the network: network queues in routers and other nodes
   along a given network path capable of isolating L4S-capable flows and
   treating their ECN marking logic according to the specification

2) support from the protocols: reinterpretation of ECN bits in the
   network- and transport-layer headers

3) support from the hosts (endpoints): use of so-called scalable
   congestion control algorithms that react to congestion signals
   (primarily ECN-CE marks on packets) according to the behavior defined
   in L4S (e.g., scaling back the congestion window in fractional
   amounts proportionally to the ratio of ECN-CE marked packets seen
   recently -- which, in a properly functional network queue, is
   proportional to the current queue length compared to the queue's
   capacity)

Of course, Quinn is only concerned with the third point. Thus, this
change implements the Prague congestion control algorithm (CCA) in Quinn
to provide basic support for L4S, following the TCP Prague draft
<https://datatracker.ietf.org/doc/draft-briscoe-iccrg-prague-congestion-control/04/>.

Implementation notes:

- NewReno is used as Prague's inner CCA, since its RTT-independent
  multiplicative-decrease behavior is a closer match for what the
  scalable congestion control response in L4S expects.

- Congestion window reduction is paced on RTT, and general packet pacing
  for Prague-controlled flows is aligned with the draft's requirements.

- Loss detection and ECN processing for the same incoming ACK can both
  fire `on_congestion_event` for what is really a single queue-overflow
  event (`detect_lost_packets` runs ahead of `process_ecn` in
  `Connection::on_ack_received`, and many AQMs drop and CE-mark off the
  same queue-occupancy signal). Without mitigation this compounds to a
  ~75% window reduction instead of the ~50% floor required by the TCP
  Prague draft §2.4.1. The ECN cooldown is now armed from the loss event
  itself, mirroring the existing handling of the reverse (ECN-then-loss)
  ordering.

- Basic tracing for congestion window reduction and qlog emission of
  L4S-type events are added for observability.

Co-Authored-By: Felician Nemeth <[email protected]>
Add ECN configuration, Prague support to `quinn-perf`.

Changes:

- Expose the `--ecn` CLI option to set the ECN mode.

- Add `Prague` to the `--congestion` algorithm choices.

- Track and report RTT and RTT variance metrics per stream to analyze
  latency characteristics under L4S similarly to iperf3.

- Document L4S ECN requirements and configuration guidelines.

Also includes two unrelated fixes surfaced while working on the above:

- Close connections immediately once completed to prevent waiting on the
  30-second idle timeout, which caused perf runs to be needlessly slow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider Prague (L4S-compliant) congestion control support

2 participants