Three Axes of Traffic Shape: Why Two Mechanisms Demand Opposite Settings

One restriction demands splitting connections, another demands merging them. The contradiction dissolves once count, concurrency and volume become independent axes.

2026-08-31 GIGATAP Team #vpn
#vpn#anticensorship#network#dpi#traffic-analysis

Three Axes of Traffic Shape: Why Two Mechanisms Demand Opposite Settings

An engineer tuning a transport against current restrictions runs fairly quickly into something awkward: two documented mechanisms demand mutually exclusive settings. One wants connections held open and reused; the other wants them rotated often. The contradiction dissolves once you see that the mechanisms count different quantities rather than one.

Here is where the real limit lies.

Two mechanisms#

Volume restriction. A connection is allowed to carry on the order of sixteen kilobytes, after which the stream stalls. Accounting is per TCP connection. Cloudflare publicly described this behaviour from 9 June 2025; the community discussed it as “TCP 16-20” and later renamed it once it became clear the limit applies to TCP and UDP alike.

The obvious countermeasure: split. Many short connections, none reaching the threshold.

Concurrency restriction. Several simultaneous secure connections to one address inside a short window trigger a state in which new attempts fail for a while.

The obvious countermeasure: merge. Few connections, many streams inside each.

The settings are directly opposed. The classic reaction is to pick which mechanism to suffer.

Why is the contradiction only apparent?#

It dissolves once you notice that “many connections” is not one quantity but two. One quantity is how many connections are opened over a period, the other how many live at once, and the mechanisms count them separately.

Axis What the volume mechanism constrains What the concurrency mechanism constrains
Connections in total per session needs to be many indifferent
Connections at the same time indifferent needs to be few
Bytes per connection needs to be few indifferent

The first mechanism constrains accumulated volume. The second constrains rate of opening. Those are different quantities, and they conflict only if connections are opened in bursts.

Smooth rotation satisfies both: many connections sequentially, never more than two or three simultaneous handshakes. The count accrues over time rather than at an instant.

Rotation over time is not the same as parallelism at a moment — and the whole resolution rests on that distinction.

Walkthrough: a burst versus smooth rotation#

The difference between two ways of reaching the same connection count, on a timeline.

IN A BURST — the concurrency restriction fires
time →   0ms   100ms  200ms  300ms  400ms
         │     │      │      │      │
conn 1   ├─────────────────────────────────►
conn 2   ├─────────────────────────────────►   4 simultaneous
conn 3   ├─────────────────────────────────►   handshakes
conn 4   ├─────────────────────────────────►   inside a 400 ms window
         ▲
         └── threshold exceeded here

SMOOTHLY — same count, restriction does not fire
time →   0s    3s     6s     9s     12s
         │     │      │      │      │
conn 1   ├──────────►│
conn 2         ├──────────►│                   never more than 1–2
conn 3                ├──────────►│            at the same time
conn 4                       ├──────────►│

Both cases use four connections. Only their distribution in time differs.

The lower variant satisfies both mechanisms at once: the count accrues, so no single connection accumulates volume; concurrency stays low, so the parallelism threshold is never reached.

A numeric example: exactly where one-sided tuning breaks#

Take a 2 MB download and work through three configurations.

Setting Connections Bytes per connection Simultaneous Outcome
“Few connections, many streams” 2 ~1,000,000 2 ❌ volume restriction at 16 KB
“Many connections, few streams” 130 ~16,000 8 ❌ concurrency threshold
Smooth rotation 130 ~16,000 2 ✅ neither threshold reached

The first row is the standard advice against the concurrency restriction. It puts a megabyte through each connection — sixty times over the volume threshold.

The second is the standard advice against the volume restriction. Volume is fine, but eight simultaneous handshakes exceed the concurrency threshold.

The third differs from the second in exactly one parameter — how many connections are held open at any instant. Count, volume and duration are identical.

That is the practical meaning of separating the axes: the third configuration is not a compromise between the first two. It satisfies both restrictions fully, because they constrain different quantities.

Where the resolution hits its ceiling#

Elegant in theory, limited in practice, for a specific reason.

Transports that multiplex over HTTP usually let you configure when to reopen the underlying connection. But the rotation triggers are almost always time-based and request-count-based, not volume-based.

You cannot directly say “reopen after fifteen kilobytes”. You can approximate it by lowering the permitted number of requests per connection, but that is an indirect handle on a quantity measured in bytes.

This is precisely why an open request has been sitting in the Xray tracker since June 2025 asking for downstream splitting across separate TCP connections of a given size. Its author — the same person who described the mechanism — prices it honestly: roughly two and a half thousand TCP connections for a fifty-megabyte file, which is “a lot and quite suspicious, but clearly better than not working at all”.

The request remains unimplemented. Parameter tuning here is a palliative, not a solution.

A common mistake: splitting at the wrong layer#

Worth naming separately, because many people fall into it.

Transports such as XHTTP can break a transfer into many HTTP requests. It is tempting to treat that as the splitting that defeats a volume restriction.

It is not. If all those requests travel inside one TCP connection, the mechanism’s counter — which counts per TCP connection — keeps rising. The mechanism’s author put it plainly: splitting into several HTTP requests within one connection is a different thing, and it will not help.

A useful corollary when reviewing any configuration: check which layer the splitting happens at. If it happens at the application layer while the counter sits at the transport layer, you have not solved the problem, only hidden it from yourself.

A second trap: one-sided optimisation#

Both obvious countermeasures are one-sided, and both circulate as ready-made advice.

A setting of “one or two connections, many streams in each” is right against concurrency and the worst possible against volume: the streams funnel into a minimum of connections and fill each one as fast as possible.

The inverse — “many connections, few streams” — is right against volume and provokes the concurrency trigger.

Ready-made parameter sets from discussion threads are usually optimised against one mechanism, because that is the one the author observed. Transplanting such a set into different conditions performs worse than the defaults.

What is practically usable here#

Label every parameter with its axis. A traffic-shape parameter given without stating what it constrains — count, concurrency or volume — is not a decision. It is a guess.

Verify what you are measuring. The same symptom, “it does not work”, is produced by both mechanisms, and they differ by one trait: the volume one stalls after N kilobytes, the concurrency one refuses new connections while an existing one stays alive.

Do not carry numbers between conditions. Thresholds were observed by different people on different operators at different times. A numeric threshold from an external source is best treated as a hypothesis, and expressed in configuration as a range rather than a constant.

Conclusion#

The contradiction between the two mechanisms exists only in the phrasing “many or few connections”. Once the quantity is split into count, concurrency and volume, both problems turn out to be solvable at once.

But the resolution has a ceiling: rotation triggers are not byte-based, and until transports offer splitting by volume, parameter tuning remains an approximation. Knowing its limit matters more than finding a lucky set of numbers — because a set of numbers expires with the observation behind it, and an understanding of the axes does not.

Terms#

  • Count axis - how many connections are opened over a period — the quantity one mechanism counts.
  • Concurrency axis - how many connections are alive in parallel at a single moment.
  • Per-connection volume axis - how many bytes travel inside one connection before a restriction engages.

FAQ#

Hold connections open, or rotate them often?#

Neither in pure form. Smooth rotation satisfies both mechanisms: connections do change, but neither in bursts nor one at a time.

Why does one-sided tuning lose?#

It optimises one axis and worsens another. The mechanisms count different quantities, so a gain on one is paid for on the other.

Does this resolution have a limit?#

Yes. At sufficient traffic both axes hit their ceilings at once, and no compromise satisfying both remains.

Further reading#