# Problem description
We built a MASQUE proxy on top of quic-go that acts as the ingress gateway for our [ZTNA service](https://www.jamf.com/blog/jamf-network-relay-service-mobile-secure-connectivity/). The clients are Apple devices using Apple’s built-in QUIC stack.
We’re seeing noticeable performance drops on networks with packet reordering — especially dual-connectivity cellular setups (e.g., 4G+5G). It looks like the reordering is triggering loss detection, causing New Reno to reduce the congestion window even when there isn’t real congestion.
# Supporting evidence
## 5G connection
We captured a QLOG trace on the proxy for an affected customer connection. The client was on [Verizon 5G Home Internet](https://www.verizon.com/home/internet/5g/). The ~200 ms RTT in the trace is expected (California → Australia path), and the connection itself was otherwise stable — no signs of persistent congestion or random loss.
QLOG: https://green-quic-cc.s3.eu-west-2.amazonaws.com/verizon-5g-home.sqlog

## Wireless connection
We have another example of an affected connection. In this case, the client was using a local wireless ISP in the Czech Republic ([ZrnkoNET](http://zrnko.net/)).
We managed to obtain a PCAP of the same connection with the proxy disabled, and it shows significant packet reordering on the path. This aligns with what we’re seeing in the QUIC traces — the issue isn’t random loss or instability, but substantial reordering that appears to be triggering premature loss detection and cwnd reductions.
QLOG: https://green-quic-cc.s3.eu-west-2.amazonaws.com/zrnko-net.sqlog
PCAP: https://green-quic-cc.s3.eu-west-2.amazonaws.com/zrnkonet-download-200MB.pcapng

# Experimental setup
To verify our hypothesis, we built an experimental setup consisting of the Masque proxy, a [VyOS](https://vyos.io/) DNAT proxy configured as a WAN emulator, and S3 as the upstream server.

We simulated the packet reordering with the aid of tc netem on VyOS in the following way:
`With "tc_delay: 50ms" and "tc_reorder: 25":`
` - 75% of packets get 50ms delay`
` - 25% of packets bypass delay (sent immediately ~0ms)`
` - Result: "fast" packets arrive before "slow" packets`
| Scenario | Baseline | Reorder light | Reorder heavy |
|----------|----------|----------|----------|
| tc_delay | 0ms | 30ms | 50ms |
| tc_reorder | 0.0 | 0.01 | 0.05 |
In the reorder-heavy scenario, 0.05% of packets bypass the 50ms delay queue, frequently causing 3+ packets to arrive [out of order](https://datatracker.ietf.org/doc/html/rfc9002#name-packet-threshold). In the reorder-light, this happens only occasionally.
## New Reno
According to our observations, the default CC does not handle frequent packet reordering very well (due to the RFC9002 kPacketThreshold=3).
When reordering happens during the slow start, the connection exits slow start too soon, and growth becomes much more conservative. Since the Apple client connections are long-lived and multiplex streams, that early cwnd reduction isn’t just a transient dip — it effectively caps throughput for the lifetime of the connection, affecting all subsequent traffic.
| Baseline |
Reorder light |
Reorder heavy |
 |
 |
 |
## CUBIC
We tested the current CUBIC implementation (which is disabled); it performed better than New Reno in the light reordering scenario, but does not seem to handle the reordering-heavy workload well.
| Baseline |
Reorder light |
Reorder heavy |
 |
 |
 |
## BBRv1
The BBRv1 implementation outperformed the loss-based algorithms in all scenarios. We didn’t have time to invest in proper implementation, so this was just a POC port from another library.
POC implementation: [GitHub - tdragoun/quic-go at bbr_v1](https://github.com/tdragoun/quic-go/tree/bbr_v1)
| Baseline |
Reorder light |
Reorder heavy |
 |
 |
 |
# Further steps & questions
We’ve noticed there are already 10+ open issues related to congestion control, and we don’t want this to turn into just another “please implement BBRvX” thread.
That said, both our production data and the lab setup results consistently point to congestion control behavior as the main bottleneck. From what we’re seeing, this feels like an area where improvements in quic-go could have a meaningful impact.
Could you please share your thoughts on:
- What does the current roadmap look like around congestion control?
- Could the library handle spurious losses more gracefully? We experimented with RACK-like behavior, but without any significant improvement.
- Could CUBIC be made available via configuration when already present?
- What would it take to make CC pluggable so we can create a custom implementation if there is no capacity to improve in this area? ([Pluggable congestion control · Issue #776 · quic-go/quic-go](https://github.com/quic-go/quic-go/issues/776))
- If we’re able to allocate budget to support development, would that help move CC-related improvements higher in priority?
We’re happy to collaborate further, let us know what would be most useful from your side. Thanks!