Why one TCP connection can be slow on a fast line
Tools that open many parallel connections, such as iperf3 -P
or multi-connection download managers, often achieve much higher speed than
a single transfer over the same line. This usually happens for one of two
reasons:
- Per-flow traffic shaping: the provider or a middlebox limits bandwidth per TCP flow (source and destination address and port) rather than only capping the line as a whole.
- High latency and packet loss: on long-distance paths with a high bandwidth-delay product, loss-based TCP congestion control shrinks its sending window after a dropped packet and takes many round trips to recover.
Opening more application streams inside one connection does not avoid either limit, because all traffic still shares the same outer TCP flow.
| Method | TCP flows | App interface | Per-flow limit |
|---|---|---|---|
| Single TCP connection | 1 | 1 stream | limited to one flow |
Multiplexed streams (HTTP/2, yamux) |
1 | many streams | limited to one flow |
Parallel transfers (iperf3 -P) |
many | many connections | needs app support |
| Aggligator | many | 1 stream | combines flows |
Splitting one stream across multiple flows
Aggligator accepts a single byte stream from the application and sends its packets across multiple independent transport links. The receiving endpoint reorders the incoming packets back into one stream and resends unacknowledged data if a link fails.
To get higher throughput in practice, three settings matter:
-
Multiple flows at the bottleneck: open several parallel links
with
set_links_per_pathon the TCP, SOCKS5 or WebSocket connector (or--tcp-linksand--socks-linksinagg-tunnel). -
Buffer sizes: on high-latency paths with many links, increasing
send_bufferandrecv_bufferin the configuration allows enough in-flight data to keep all links busy. -
Congestion control: enabling BBR (
--tcp-congestion bbr) prevents occasional packet loss from throttling individual TCP links.
In a practical user report on a line where each outgoing TCP flow was throttled to 2.5 Mbit/s, combining 16 parallel flows with Aggligator raised the throughput of a single logical connection to about 180 Mbit/s.
Direct TCP: multiple parallel links per path
When connecting directly over TCP, calling set_links_per_path
on TcpConnector (or passing --tcp-links to
agg-tunnel) opens multiple TCP connections with distinct
source ports over each network path.
use aggligator_transport_tcp::TcpConnector;
let mut tcp = TcpConnector::new(["server.example"], 5800).await?;
tcp.set_links_per_path(4);
let mut connector = Connector::new();
connector.add(tcp);
let stream = connector.channel().unwrap().await?.into_stream();
agg-tunnelagg-tunnel client --tcp server.example:5800 --tcp-links 4 --port 22:2222
SOCKS5 proxies: multiple tunnel or proxy paths
When traffic goes through local tunnel instances (such as ssh -D
or an encrypted proxy client) or remote SOCKS5 servers, the
SOCKS5
transport can combine several proxy endpoints, or open multiple
parallel links through one proxy with set_links_per_path
(--socks-links).
use aggligator_transport_socks::{Socks5Connector, Socks5Target};
let target = Socks5Target::parse("server.example", 5800)?;
let proxies = ["127.0.0.1:1081", "127.0.0.1:1082", "127.0.0.1:1083", "127.0.0.1:1084"];
let mut connector = Connector::new();
connector.add(Socks5Connector::new(proxies, target)?);
let stream = connector.channel().unwrap().await?.into_stream();
agg-tunnelagg-tunnel client \
--socks 127.0.0.1:1081 --socks 127.0.0.1:1082 \
--socks 127.0.0.1:1083 --socks 127.0.0.1:1084 \
--socks-target server.example:5800 \
--port 22:2222
Questions
Why does multiplexing streams inside one connection not help?
Stream multiplexers such as HTTP/2, yamux or SSH connection sharing still send all data through one outer TCP connection. A traffic shaper that limits each flow by its address and port pair applies the same limit regardless of how many streams are multiplexed inside it. Aggligator instead distributes one stream across several separate transport connections.
Does this increase speed when the whole line is already saturated?
No. Combining multiple flows only helps when individual TCP connections are held below the capacity of the line, for example by per-flow traffic shaping or single-connection window limits on high-latency paths. It cannot exceed the total bandwidth of the internet connection.
How many parallel links can be opened over one interface?
By default, the TCP, SOCKS5 and WebSocket transports establish one link per path. Calling set_links_per_path on their connector (or passing --tcp-links or --socks-links to agg-tunnel) opens multiple parallel connections over each path.
Is this suitable for real-time traffic such as voice or gaming?
Aggligator provides an ordered, reliable byte stream. When a packet is lost on one link, later packets received on other links wait in the receive buffer until the missing packet is retransmitted. This head-of-line blocking does not hurt bulk transfers or shell sessions, but can add latency spikes to real-time UDP traffic tunneled over TCP.
Do both ends need to run Aggligator?
Yes. Packets sent over the separate flows must be reordered and reassembled at the receiving end, so the server must run Aggligator or agg-tunnel as well.
Related pages
The Aggligator overview explains
how capacity is discovered on each link
and lists all transports. See also
combining multiple internet connections,
keeping connections alive while
switching networks and
tunneling existing applications with
agg-tunnel.