Loading content...
If you're reading this page, there's a good chance that TCP CUBIC is carrying your data right now. Since becoming the default congestion control algorithm in Linux kernel 2.6.19 (November 2006), CUBIC has grown to dominate Internet traffic. It powers the majority of web servers, cloud infrastructure, mobile devices, and data center communications.
CUBIC emerged from research at North Carolina State University by Injong Rhee and Lisong Xu, building on their earlier BIC-TCP (Binary Increase Congestion control). The key insight driving CUBIC's design is deceptively simple: the congestion window should grow as a function of time since the last congestion event, not as a function of received ACKs.
This time-based approach solves a fundamental unfairness in traditional TCP: the RTT-unfairness problem. Traditional TCP variants (Tahoe, Reno, NewReno) grow their window faster when ACKs return quickly. This means flows with short round-trip times dominate bandwidth over flows with long RTTs. CUBIC makes window growth independent of RTT, achieving fairness across paths of vastly different latencies.
By the end of this page, you will understand CUBIC's cubic window growth function, how it achieves RTT-fairness, the three distinct growth phases (concave, plateau, convex), how CUBIC interacts with traditional Reno-style flows, and the practical considerations for CUBIC deployment and tuning.
Before diving into CUBIC's solution, we must understand the problem it solves: the systematic unfairness of traditional AIMD-based congestion control toward flows with different round-trip times.
Traditional TCP Window Growth:
In Congestion Avoidance, traditional TCP increases cwnd by approximately 1 MSS per RTT:
cwnd_increase_per_second = MSS / RTT
This means:
The short-RTT flow grows its window 10 times faster than the long-RTT flow.
| Flow RTT | Growth Rate (MSS/sec) | Time to grow 100 MSS | Relative Speed |
|---|---|---|---|
| 10 ms | 100 | 1 second | 10× baseline |
| 50 ms | 20 | 5 seconds | 2× baseline |
| 100 ms | 10 | 10 seconds | 1× (baseline) |
| 200 ms | 5 | 20 seconds | 0.5× baseline |
| 500 ms | 2 | 50 seconds | 0.2× baseline |
Why This Matters:
When flows with different RTTs share a bottleneck link:
This creates unfair outcomes:
The Mathematical Inequity:
The bandwidth share ratio between two competing flows is approximately:
Bandwidth_1 / Bandwidth_2 ≈ RTT_2 / RTT_1
A flow with 10ms RTT gets roughly 10× the bandwidth of a flow with 100ms RTT when they share a link. This is grossly unfair.
As the Internet became global, the RTT-unfairness problem grew critical. Users in Australia connecting to US servers, or users in Africa connecting to European servers, face RTTs of 200-400ms. Traditional TCP would give them a fraction of local users' bandwidth, even when capacity exists.
CUBIC's fundamental innovation is making window growth a function of elapsed time since the last congestion event rather than a function of received ACKs. This decouples growth rate from RTT.
The CUBIC Window Function:
W(t) = C × (t - K)³ + W_max
Where:
The constant K is calculated as:
K = ∛(W_max × β / C)
Where β is the multiplicative decrease factor (default: 0.7 in CUBIC, compared to 0.5 in Reno).
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
/* CUBIC Window Growth Function */ #include <math.h> /* CUBIC parameters (from RFC 8312) */#define CUBIC_C 0.4 /* Scaling constant */#define CUBIC_BETA 0.7 /* Multiplicative decrease factor */ typedef struct { double wmax; /* W_max: window before last loss */ double cwnd; /* Current congestion window */ double ssthresh; /* Slow start threshold */ double K; /* Time to reach W_max */ double epoch_start; /* Time when current epoch started */ uint32_t mss; /* Maximum segment size */} CUBICState; /* * Calculate target window size at time t since last loss */double cubic_window(CUBICState* s, double t) { /* * W(t) = C * (t - K)^3 + W_max * * This is a cubic function centered at (K, W_max) * - For t < K: window grows concavely toward W_max * - At t = K: window reaches W_max exactly * - For t > K: window grows convexly beyond W_max */ double diff = t - s->K; double target = CUBIC_C * diff * diff * diff + s->wmax; return target;} /* * Calculate K: time to reach W_max */double calculate_K(double wmax_segments) { /* * K = ∛(W_max * β / C) * * After loss, cwnd becomes W_max * β. * K is the time for cubic function to return to W_max. */ return cbrt(wmax_segments * (1 - CUBIC_BETA) / CUBIC_C);} /* * Enter congestion epoch (after loss detection) */void cubic_enter_congestion(CUBICState* s, double current_time) { /* Record W_max as current window */ s->wmax = s->cwnd; /* Calculate K for this epoch */ s->K = calculate_K(s->wmax / s->mss); /* Multiplicative decrease */ s->cwnd = s->wmax * CUBIC_BETA; s->ssthresh = s->cwnd; /* Mark epoch start time */ s->epoch_start = current_time;} /* * Update cwnd on ACK */void cubic_on_ack(CUBICState* s, double current_time) { /* Time since congestion event */ double t = current_time - s->epoch_start; /* Calculate target window */ double target = cubic_window(s, t); /* CUBIC growth: move toward target */ if (target > s->cwnd) { /* Window should increase */ double increase = (target - s->cwnd) / s->cwnd * s->mss; s->cwnd += increase; } /* * Note: In practice, there's also TCP-friendliness logic * to ensure CUBIC doesn't underperform compared to Reno. * See next section for details. */}Why a Cubic Function?
The cubic (third-degree polynomial) shape was carefully chosen:
Concave Region (t < K): After a loss, grow quickly toward the last known good window (W_max). Don't waste time far below capacity.
Plateau Region (t ≈ K): Slow down as we approach W_max. This is where we lost packets before—probe carefully.
Convex Region (t > K): Having passed W_max without loss, accelerate again to discover additional capacity.
The cubic function provides aggressive growth in regions that are probably safe and cautious growth near the danger zone.
Since W(t) depends only on elapsed clock time (not on ACK arrivals), flows with different RTTs follow the same growth trajectory. A 10ms RTT flow and a 200ms RTT flow, starting recovery at the same time, will have identical windows after 5 seconds—regardless of how many ACKs each received.
CUBIC's distinctive window growth pattern can be divided into three phases, each with different characteristics and purposes.
WindowSize ^ | . | . ' Convex | . ' (probingW_max ---+- - - - - - - - - - - - -+ capacity) | . ' . ' . ' | | .' ' ' | | . Concave | Plateau | . (recovering) | (cautious) | . |W_max*β -+------------------------' | +----------------------------> Time t=0 t=K (loss) (reach W_max) Phase 1: CONCAVE (t < K, W < W_max)- Rapid growth toward W_max- Shape: derivative decreasing (slowing as we approach W_max) - Goal: Quickly recover bandwidth after loss Phase 2: PLATEAU (t ≈ K, W ≈ W_max)- Very slow growth near W_max- The cubic function has near-zero slope at this point- Goal: Careful probing—we lost packets here before Phase 3: CONVEX (t > K, W > W_max)- Accelerating growth beyond W_max- Shape: derivative increasing (faster as we move into new territory)- Goal: Discover if more capacity has become availablePhase 1: Concave Growth (Recovery Phase)
Immediately after a loss, cwnd drops to W_max × β (typically W_max × 0.7). The concave phase aggressively grows the window back toward W_max.
Phase 2: Plateau (Steady-State Phase)
As the window approaches W_max, the cubic function flattens. Growth slows dramatically.
Phase 3: Convex Growth (Exploration Phase)
If no loss occurs near W_max, the network may have additional capacity. The convex phase probes for it.
The convex phase is crucial for adapting to increased capacity. If a cross-traffic flow ends or if you're moved to a less congested path, the convex growth will eventually discover and utilize the newly available bandwidth.
CUBIC includes a TCP-friendliness mode to ensure it doesn't perform worse than traditional Reno TCP under normal conditions. This is important for two reasons:
Backwards Compatibility: CUBIC should compete fairly with Reno flows, not disadvantage itself.
Small-Window Performance: For small windows (low BDP paths), the cubic function can grow slower than Reno's linear growth.
How It Works:
CUBIC maintains two simultaneous window calculations:
At any given time, CUBIC uses the maximum of these two values.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
/* CUBIC TCP-Friendliness Mechanism */ typedef struct { double cwnd; double wmax; double K; double epoch_start; double tcp_cwnd; /* Simulated Reno window */ double ack_count; /* ACKs since epoch start */ uint32_t mss;} CUBICFriendly; /* * Reno window growth simulation * * Standard TCP: cwnd += MSS / cwnd per ACK * In Congestion Avoidance: ~1 MSS per RTT * * We simulate this to ensure we're never slower than Reno. */double compute_tcp_window(CUBICFriendly* s, double time_since_epoch) { /* * Reno after loss: cwnd = W_max * 0.5 * Then linear growth: 1 MSS per RTT * * If RTT = time_since_epoch / ACKs_received: * RTTs elapsed ≈ time_since_epoch / RTT * W_tcp = W_max * 0.5 + (time_since_epoch / RTT) * MSS * * Simplified estimate: * W_tcp = 3 * (1 - β) / (2 - β) * (t/RTT) + W_max * β * * This creates a linear growth from the post-loss window. */ double beta_factor = (3 * (1 - CUBIC_BETA)) / (2 - CUBIC_BETA); double rtt_estimate = get_smoothed_rtt(); double elapsed_rtts = time_since_epoch / rtt_estimate; return s->wmax * CUBIC_BETA + beta_factor * elapsed_rtts * s->mss;} /* * Main CUBIC update: use maximum of cubic and Reno windows */void cubic_friendly_on_ack(CUBICFriendly* s, double current_time) { double t = current_time - s->epoch_start; /* CUBIC's window calculation */ double w_cubic = cubic_window(s, t); /* Simulated Reno's window */ double w_tcp = compute_tcp_window(s, t); /* TCP-friendliness: use the larger window */ double target = MAX(w_cubic, w_tcp); /* * If w_tcp > w_cubic, we're in "TCP mode" * This typically happens when: * 1. W_max is small (low-BDP path) * 2. Shortly after loss (Reno's linear growth beats concave) * 3. K is very large (slow cubic convergence) */ if (target > s->cwnd) { /* Grow toward target */ double segments_to_add = (target - s->cwnd) / s->cwnd; s->cwnd += MAX(1, segments_to_add) * s->mss; }} /* * Visualization of CUBIC vs TCP window growth * * Window * ^ * | . CUBIC * | .' * | .' * W_max-+- - - - - -.+- - - - - - * | .' '. * | .' '. <- Plateau region * | .' '. * | .' .-----------.-- Reno (linear) * | .' / * | .' / <- CUBIC concave * | . / * W*β+---./ * +-------------------------------------> t * 0 K * * Below the intersection: TCP-friendly mode uses Reno growth * Above the intersection: CUBIC's cubic function takes over */When TCP-Friendliness Activates:
Low BDP Paths: When W_max is small, the cubic function is shallow. Reno's linear growth (1 MSS per RTT) can be faster.
Early Concave Phase: Right after a loss, both start from W_max × β, but Reno immediately begins linear growth while CUBIC's concave may start slower.
High-RTT Paths with Small Windows: The cubic function's time-based nature doesn't help if the window is already small.
By always choosing the maximum of the two approaches, CUBIC guarantees at least Reno performance while achieving potentially much better performance on high-BDP paths.
TCP-friendliness means CUBIC is never slower than Reno on any path. It matches Reno on low-BDP paths (where Reno is already good) and exceeds Reno on high-BDP paths (where Reno struggles). This made CUBIC a safe default for Linux.
CUBIC has several tuneable parameters, standardized in RFC 8312. Understanding these parameters is essential for diagnosing performance issues and optimizing for specific environments.
| Parameter | Value | Purpose | Effect of Increasing |
|---|---|---|---|
| C | 0.4 | Scaling factor for cubic function | More aggressive growth in convex region |
| β (beta) | 0.7 | Multiplicative decrease factor | Smaller window reduction on loss; more aggressive |
| Fast Convergence | On | Adaptive W_max reduction | Faster convergence to fair share |
| TCP-Friendliness | On | Reno compatibility mode | Never underperforms Reno |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
/* CUBIC Parameter Impact Analysis */ /* * C = 0.4 (CUBIC scaling constant) * * Higher C: More aggressive growth, especially in convex phase * Lower C: More conservative, slower to probe beyond W_max * * The value 0.4 was chosen empirically to balance aggressiveness * with network stability. Typical range: 0.2 - 0.8 */#define CUBIC_C 0.4 /* * β = 0.7 (Multiplicative decrease) * * Compare to Reno's β = 0.5 (halving on loss) * * CUBIC's gentler reduction (keeping 70% instead of 50%): * - Reduces window oscillation amplitude * - Faster recovery (less ground to make up) * - Potentially more packets in flight during congestion * * Trade-off: More aggressive toward network, less backoff on loss */#define CUBIC_BETA 0.7 /* * Fast Convergence * * When a new flow joins a bottleneck, existing flows should * release bandwidth. Fast Convergence accelerates this. * * Logic: If current W_max < previous W_max, assume new competition. * Set W_max = current_cwnd * (1 + β) / 2 * This lowers K, speeding convergence to new equilibrium. */void fast_convergence_check(CUBICState* s) { if (s->cwnd < s->wmax) { /* * Lost at a window smaller than last W_max * Likely means more competition on the link */ s->wmax = s->cwnd * (1 + CUBIC_BETA) / 2; /* Recalculate K for faster convergence */ s->K = calculate_K(s->wmax / s->mss); } else { /* Normal case: W_max from current cwnd */ s->wmax = s->cwnd; }} /* * HyStart: Hybrid Slow Start * * Linux CUBIC includes HyStart to exit Slow Start before * massive loss. HyStart detects incipient congestion via: * * 1. ACK Train: Monitor inter-ACK spacing. If ACKs slow down, * queues are building → exit Slow Start early. * * 2. Delay Increase: Monitor RTT samples. If current RTT * exceeds baseline by a threshold → exit early. * * This avoids the Slow Start overshoot that causes burst losses. */typedef struct { bool hystart_enabled; double min_rtt; /* Baseline RTT */ double curr_rtt; /* Current RTT sample */ double delay_threshold; /* Trigger threshold */ int sample_count; /* RTT samples in this window */} HyStartState; void hystart_check(HyStartState* h, CUBICState* s) { if (!h->hystart_enabled) return; if (s->cwnd >= s->ssthresh) return; /* Already in CA */ h->sample_count++; /* Delay increase detection */ if (h->curr_rtt > h->min_rtt + h->delay_threshold) { /* Exit Slow Start early */ s->ssthresh = s->cwnd; /* Transition to CA */ }}Linux Tuning Options:
On Linux systems, CUBIC parameters can be adjusted via sysctl:
# View current congestion control
cat /proc/sys/net/ipv4/tcp_congestion_control
# View available options
cat /proc/sys/net/ipv4/tcp_available_congestion_control
# Enable/disable HyStart
echo 0 > /proc/sys/net/ipv4/tcp_cubic_hystart
# CUBIC parameters are usually compile-time constants
# Modifying requires kernel recompilation or BPF-based tuning
When to Tune:
Most deployments use default parameters. Consider tuning when:
Let's consolidate CUBIC into a complete, implementable algorithm that shows how all pieces fit together.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
/* Complete TCP CUBIC Implementation */ #include <math.h>#include <stdint.h>#include <stdbool.h> #define CUBIC_C 0.4#define CUBIC_BETA 0.7 typedef struct { /* Core state */ uint32_t cwnd; /* Congestion window (bytes) */ uint32_t ssthresh; /* Slow start threshold */ uint32_t mss; /* Maximum segment size */ /* CUBIC specific */ double wmax; /* Window at last loss */ double K; /* Time to reach W_max */ double epoch_start; /* Time of last loss */ double tcp_cwnd; /* TCP-friendly window estimate */ /* For fast convergence */ double last_wmax; /* Previous W_max (for fast convergence) */ /* Timing */ double min_rtt; /* Minimum observed RTT */ /* Flags */ bool in_slow_start; uint8_t dupacks;} TCPCubic; /* Initialize CUBIC state */void cubic_init(TCPCubic* c, uint32_t mss) { c->cwnd = mss; /* IW = 1 MSS (or 10 in modern impl) */ c->ssthresh = UINT32_MAX; c->mss = mss; c->wmax = 0; c->K = 0; c->epoch_start = 0; c->tcp_cwnd = 0; c->last_wmax = 0; c->min_rtt = 0; c->in_slow_start = true; c->dupacks = 0;} /* Cubic function: W(t) = C*(t-K)³ + W_max */double cubic_func(TCPCubic* c, double t) { double offset = t - c->K; return CUBIC_C * offset * offset * offset * c->mss + c->wmax;} /* Calculate TCP-friendly window estimate */double tcp_friendly_window(TCPCubic* c, double t) { /* Simulate Reno: linear growth from β*W_max */ double rtt = c->min_rtt > 0 ? c->min_rtt : 0.1; /* Default 100ms */ double elapsed_rtts = t / rtt; double alpha = 3 * (1 - CUBIC_BETA) / (2 - CUBIC_BETA); return c->wmax * CUBIC_BETA + alpha * elapsed_rtts * c->mss;} /* Enter loss recovery */void cubic_on_loss(TCPCubic* c, double current_time) { c->dupacks = 0; /* Fast Convergence: if W_max decreasing, reduce further */ if (c->cwnd < c->last_wmax) { /* New flow likely joined - converge faster */ c->wmax = c->cwnd * (1 + CUBIC_BETA) / 2; } else { c->wmax = c->cwnd; } c->last_wmax = c->cwnd; /* Calculate K for this epoch */ double wmax_segs = c->wmax / c->mss; c->K = cbrt(wmax_segs * (1 - CUBIC_BETA) / CUBIC_C); /* Multiplicative decrease */ c->ssthresh = (uint32_t)(c->cwnd * CUBIC_BETA); if (c->ssthresh < 2 * c->mss) { c->ssthresh = 2 * c->mss; } c->cwnd = c->ssthresh; /* Mark epoch start */ c->epoch_start = current_time; c->tcp_cwnd = c->cwnd; c->in_slow_start = false;} /* Handle ACK in Congestion Avoidance */void cubic_on_ack_ca(TCPCubic* c, double current_time, double rtt_sample) { /* Update minimum RTT if needed */ if (c->min_rtt == 0 || rtt_sample < c->min_rtt) { c->min_rtt = rtt_sample; } /* Time since epoch */ double t = current_time - c->epoch_start; /* Calculate CUBIC target */ double w_cubic = cubic_func(c, t); /* Calculate TCP-friendly target */ double w_tcp = tcp_friendly_window(c, t); /* Use maximum (TCP-friendliness) */ double target = (w_cubic > w_tcp) ? w_cubic : w_tcp; /* Calculate increment */ if (target > c->cwnd) { /* cnt: how many ACKs to increase cwnd by 1 MSS */ double cnt = c->cwnd / (target - c->cwnd); if (cnt < 1) cnt = 1; c->cwnd += c->mss / cnt; }} /* Handle ACK in Slow Start */void cubic_on_ack_ss(TCPCubic* c) { /* Exponential growth: cwnd += MSS per ACK */ c->cwnd += c->mss; /* Check for Slow Start exit */ if (c->cwnd >= c->ssthresh) { c->in_slow_start = false; /* Note: HyStart may cause earlier exit */ }} /* Main ACK handler */void cubic_on_ack(TCPCubic* c, double current_time, double rtt_sample, bool is_dup_ack) { if (is_dup_ack) { c->dupacks++; if (c->dupacks == 3) { /* Enter Fast Recovery (uses NewReno-style recovery) */ cubic_on_loss(c, current_time); retransmit_first_unacked(); } return; } /* New ACK */ c->dupacks = 0; if (c->in_slow_start) { cubic_on_ack_ss(c); } else { cubic_on_ack_ca(c, current_time, rtt_sample); }} /* Timeout handler */void cubic_on_timeout(TCPCubic* c, double current_time) { /* Severe congestion: full reset like Tahoe */ c->ssthresh = (uint32_t)(c->cwnd * CUBIC_BETA); if (c->ssthresh < 2 * c->mss) { c->ssthresh = 2 * c->mss; } c->cwnd = c->mss; c->epoch_start = current_time; c->wmax = c->ssthresh; c->K = 0; c->in_slow_start = true; c->dupacks = 0;}Let's quantify CUBIC's advantages in various network scenarios, comparing it to traditional Reno-style TCP.
| Scenario | Reno Performance | CUBIC Performance | Winner |
|---|---|---|---|
| Low BDP (10ms, 10Mbps) | 12.5 KB window, ~95% util | ~Same (TCP-friendly mode) | Tie |
| Medium BDP (50ms, 100Mbps) | 625 KB window, ~90% util | 625 KB, ~92% util | CUBIC (slight) |
| High BDP (100ms, 1Gbps) | Struggles to reach optimal | Reaches equilibrium faster | CUBIC (significant) |
| Transcontinental (200ms, 10Gbps) | Cannot utilize link | Achieves high utilization | CUBIC (dramatic) |
| Mixed RTT competition | Short-RTT flows dominate | Fair sharing regardless of RTT | CUBIC |
| Post-loss recovery | Linear climb from half | Cubic jump to 70%, then plateau | CUBIC |
High-BDP Link Analysis:
Consider a 10 Gbps link with 100ms RTT (common for transcontinental connections):
Time to reach optimal window after loss:
Reno: Starting from 43,000 segments (half), at 1 segment per RTT:
CUBIC: With K = ∛(86,000 × 0.3 / 0.4) ≈ 40 seconds:
CUBIC recovers 70× faster than Reno on this high-BDP path.
CUBIC made high-bandwidth, long-distance connections practical. Before CUBIC, transcontinental and transoceanic links were severely underutilized. Data centers, CDNs, and cloud services all benefit enormously from CUBIC's ability to quickly utilize high-BDP paths.
CUBIC remains the dominant congestion control algorithm on the Internet, though it now shares space with newer algorithms like BBR. Understanding its deployment landscape is essential for network engineers.
CUBIC vs. BBR:
Google's BBR (Bottleneck Bandwidth and RTT) represents a different philosophy:
| Aspect | CUBIC | BBR |
|---|---|---|
| Loss signal | Primary congestion indicator | Secondary (pacing-based) |
| RTT measurement | Used for TCP-friendliness | Primary congestion signal |
| Buffer behavior | Fills buffers (loss-based) | Minimizes buffers (model-based) |
| Deployment | Universal default | Google services, opt-in elsewhere |
| Maturity | 15+ years production use | Newer, still evolving (BBRv2, v3) |
BBR can achieve lower latency through active buffer management, but CUBIC's universal deployment and proven stability make it the safe choice for most applications.
CUBIC is the conservative choice—battle-tested and universally compatible. BBR excels in specific scenarios (YouTube streaming, Google Cloud) where low latency matters and the infrastructure is tuned for it. For most applications, CUBIC's defaults are excellent.
TCP CUBIC represents the mature state of loss-based congestion control, solving RTT-fairness while maintaining backwards compatibility with traditional TCP. Let's consolidate the key concepts:
What's Next:
In the final page of this module, we provide a comprehensive comparison of all TCP variants covered: Tahoe, Reno, NewReno, and CUBIC. We'll analyze their behavior under various scenarios, provide guidance on selecting appropriate variants, and summarize the evolution of TCP congestion control.
You now have comprehensive understanding of TCP CUBIC—its RTT-fairness innovation, the cubic window function, three-phase growth pattern, TCP-friendliness mode, and modern deployment status. You can explain why CUBIC dominates the Internet and analyze its behavior in various network scenarios.