TCP CUBIC Congestion Control Algorithm



TCP CUBIC (CUBIC Congestion Control) is an advanced congestion control algorithm that improves the performance of TCP in high-speed and long-distance networks. It addresses the limitations of traditional TCP algorithms (like Reno and New Reno) and also is improvement of TCP BIC with better fairness and easy implementation.

TCP CUBIC congestion control algorithm uses a cubic function to find the growth of congestion window (cwnd). It adjusts the rate of sending packets using a cubic function of the time duration since the last congestion event. It makes the window growth independent of RTT (Round Trip Time).

TCP CUBIC improves the performance of fast networks where data takes more time to travel between sender and receiver, for example, long distance fiber networks. It provides better RTT-fairness and scalability in high speed networks than TCP Reno, New Reno, and BIC algorithms. It uses a function also called cubic window increase function that has three different phases: first is the concave region also known as fast recovery, the second is plateau region also called cautious probing, and the third is convex region also known as aggressive probing.

Earlier TCP algorithms like Reno and New Reno had limitations in high-speed networks. TCP BIC solved the limitations of Reno and New Reno but uses complex logic with multiple phases. TCP CUBIC uses a single cubic function and maintains the performance.

Here are the important characteristics of TCP CUBIC algorithm

  • It uses a cubic function for getting window growth and make it independent of RTT.
  • Provides better RTT-fairness as compared to TCP BIC and other traditional TCP congestion control algorithms.
  • Better stability and fast convergence with high bandwidth utilization.

Working of TCP CUBIC Algorithm

The following image depicts all the phases of TCP CUBIC algorithm −

TCP CUBIC Algorithm Phase Diagram

Let's now understand the working of TCP CUBIC algorithm in various phases −

Cubic Window Growth Function

The window growth function in CUBIC algorithm is given as: W(t) = C(t - K)3 + Wmax, where C represents scaling constant, t represents time passed since last window reduction, K represents time taken by function to increase W to Wmax, and Wmax represents window size before the last reduction.

Concave Region

Just after a loss event, when t < K, it represents the concave region of cubic function. In this region, the window increases quickly but at decreasing rate. This helps to recover quickly towards the previous Wmax value. It is also known as Fast Recovery region.

Plateau Region (Cautious Probing)

When t ≈ K, the window is near Wmax. The cubic function flattens out, so there is slow growth in this region. It helps in avoiding immediate congestion after reaching the previous maximum. It is also known as Cautious Probing region.

Convex Region (Aggressive Probing)

When t > K, after surpassing Wmax without loss, the function enters the convex region. This window increases at a very fast rate to probe for additional available bandwidth. It is also known as Aggressive Probing region.

TCP-Friendly Region

In TCP-Friendly region, the cubic function grows slower than TCP Reno. CUBIC algorithm switches to a TCP-friendly mode to ensure fairness with standard TCP flows.

Loss Handling

In case of packet loss, CUBIC algorithm does the following −

  • It sets Wmax to the current cwnd.
  • It reduces cwnd using multiplicative decrease to 70% of current cwnd (β = 0.7).
  • It resets the timer t = 0.
  • It calculates K = ((Wmax - cwnd)/C)1/3 for the new cubic iteration.

Example

Here is an example demonstrating TCP CUBIC behavior with packet loss. Initially, cwnd = 10, C = 0.4, β = 0.7

Initial State:
cwnd = 10, Wmax = undefined, C = 0.4, β = 0.7
RTT 1-4: Cubic growth (concave phase)
cwnd grows: 10 => 42 => 74 => 106
RTT 5: cwnd = 138, Packet loss occurs
=> Wmax = 138
=> cwnd = 138 * 0.7 = 97 (using β = 0.7)
=> K = ((138 - 97)/0.4)1/3 = (102.5)1/3 ≈ 4.68
=> t = 0
RTT 6: Concave phase begins (t = 1)
=> W(1) = 0.4(1 - 4.68)3 + 138 ≈ 118
=> cwnd = 118
RTT 7: Continuing concave growth (t = 2)
=> W(2) = 0.4(2 - 4.68)3 + 138 ≈ 130
=> cwnd = 130
RTT 8: Approaching plateau (t = 3)
=> W(3) = 0.4(3 - 4.68)3 + 138 ≈ 136
=> cwnd = 136
RTT 9: Near Wmax (t = 4)
=> W(4) = 0.4(4 - 4.68)3 + 138 ≈ 138
=> cwnd = 138
RTT 10: Plateau region (t = 5)
=> W(5) = 0.4(5 - 4.68)3 + 138 ≈ 138
=> cwnd = 138
RTT 11: Entering convex region (t = 6)
=> W(6) = 0.4(6 - 4.68)3 + 138 ≈ 139
=> cwnd = 139
RTT 12: Convex growth (t = 7), Packet loss occurs
=> Wmax = 139
=> cwnd = 139 * 0.7 = 97
=> K = ((139 - 97)/0.4)1/3 ≈ 4.71
=> t = 0
RTT 13: New epoch begins
=> W(1) = 0.4(1 - 4.71)3 + 139 ≈ 118
=> cwnd = 118
RTT 14: Concave growth
=> W(2) = 0.4(2 - 4.71)3 + 139 ≈ 131
=> cwnd = 131

Steps to Implement TCP CUBIC Algorithm

Here are the steps to implement TCP CUBIC algorithm −

  • Initially, set cwnd = 1 MSS, initial ssthresh, cubic constant C = 0.4, β = 0.7, the number of iterations, and round at which the loss will occur i.e., 5 and 12.
  • In Slow Start phase, increase cwnd by 1 MSS for each ACK received.
  • When cwnd reaches ssthresh, switch to Congestion Avoidance phase using the CUBIC function.
  • In case of packet loss, set Wmax = cwnd and reduce cwnd to cwnd * β (where β = 0.7).
  • Calculate the value of K using ((Wmax - cwnd)/C)1/3 and reset the time to t = 0.
  • Calculate target window using W(t) = C(t - K)3 + Wmax for each RTT in Congestion Avoidance phase.
  • In the concave phase when (t < K), cwnd increases quickly toward Wmax.
  • In the plateau region phase when (t ≈ K), cwnd growth slows down for cautious probing.
  • In the convex region phase when (t > K), cwnd increases quickly to probe for more bandwidth.
  • Implement TCP-friendly mode. If CUBIC growth becomes slower than TCP Reno, then use Reno's growth rate in place of CUBIC growth.
  • In case of timeout, reset cwnd to 1 MSS, set Wmax = 0, and restart from Slow Start.

Code Implementation

Here is the code implementation of the above steps for TCP CUBIC algorithm in C++, Java, and Python −

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
class TCPCUBIC {
private:
    double cwnd;
    double wmax;
    double C;
    double beta;
    int t;
    double K;
    bool tcp_friendly;
public:
    TCPCUBIC(double init_cwnd = 10.0, double cubic_c = 0.4, 
             double b = 0.7, bool tf = true) {
        cwnd = init_cwnd;
        wmax = 0.0;
        C = cubic_c;
        beta = b;
        t = 0;
        K = 0.0;
        tcp_friendly = tf;
    }
    void onAck() {
        t++;
        
        if (wmax == 0.0) {
            // No previous loss, use simple growth
            cwnd += 1.0;
        } else {
            // CUBIC function: W(t) = C(t-K)^3 + Wmax
            double time_diff = t - K;
            double target = C * pow(time_diff, 3) + wmax;
            
            // TCP-friendly mode check
            double tcp_cwnd = cwnd + (3 * beta) / (2 - beta) * (t / cwnd);
            
            if (tcp_friendly && target < tcp_cwnd) {
                cwnd = tcp_cwnd;
            } else {
                cwnd = target;
            }
            
            // Ensure cwnd doesn't decrease
            if (cwnd < wmax * beta) {
                cwnd = wmax * beta;
            }
        }
    }
    void onLoss() {
        wmax = cwnd;
        cwnd = cwnd * beta;
        t = 0;
        
        // Calculate K: time to reach Wmax again
        K = cbrt((wmax - cwnd) / C);
    }
    double getCwnd() { return cwnd; }
    double getWmax() { return wmax; }
    
    void printState(int rtt) {
        cout << rtt << "\t" << (int)cwnd << "\t\t" << (int)wmax 
             << "\t\t" << t << endl;
    }
};
int main() {
    TCPCUBIC cubic(10.0);
    int maxRTT = 15;
    int lossRound1 = 5;
    int lossRound2 = 12;
    
    cout << "RTT\tcwnd\t\tWmax\t\tTime(t)" << endl;
    
    for (int r = 1; r <= maxRTT; r++) {
        cubic.printState(r);
        
        if (r == lossRound1) {
            cout << "=> Packet loss detected at RTT " << r << endl;
            cubic.onLoss();
            cout << "=> cwnd reduced, Wmax updated, epoch reset" << endl;
        } else if (r == lossRound2) {
            cout << "=> Packet loss detected at RTT " << r << endl;
            cubic.onLoss();
            cout << "=> cwnd reduced, new epoch begins" << endl;
        } else {
            cubic.onAck();
        }
    }
    
    return 0;
}

The output of the above code is as follows −

RTT	cwnd		Wmax		Time(t)
1	10		0		0
2	11		0		1
3	12		0		2
4	13		0		3
5	14		0		4
=> Packet loss detected at RTT 5
=> cwnd reduced, Wmax updated, epoch reset
6	9		14		0
7	11		14		1
8	12		14		2
9	13		14		3
10	14		14		4
11	14		14		5
12	14		14		6
=> Packet loss detected at RTT 12
=> cwnd reduced, new epoch begins
13	9		14		0
14	11		14		1
15	12		14		2
class TCPCUBIC {
    private double cwnd;
    private double wmax;
    private double C;
    private double beta;
    private int t;
    private double K;
    private boolean tcpFriendly;
    public TCPCUBIC(double initCwnd, double cubicC, double b, boolean tf) {
        this.cwnd = initCwnd;
        this.wmax = 0.0;
        this.C = cubicC;
        this.beta = b;
        this.t = 0;
        this.K = 0.0;
        this.tcpFriendly = tf;
    }
    public void onAck() {
        t++;
        
        if (wmax == 0.0) {
            cwnd += 1.0;
        } else {
            // CUBIC function
            double timeDiff = t - K;
            double target = C * Math.pow(timeDiff, 3) + wmax;
            
            // TCP-friendly check
            double tcpCwnd = cwnd + (3 * beta) / (2 - beta) * (t / cwnd);
            
            if (tcpFriendly && target < tcpCwnd) {
                cwnd = tcpCwnd;
            } else {
                cwnd = target;
            }
            
            if (cwnd < wmax * beta) {
                cwnd = wmax * beta;
            }
        }
    }
    public void onLoss() {
        wmax = cwnd;
        cwnd = cwnd * beta;
        t = 0;
        K = Math.cbrt((wmax - cwnd) / C);
    }
    public double getCwnd() { return cwnd; }
    public double getWmax() { return wmax; }
    
    public void printState(int rtt) {
        System.out.println(rtt + "\t" + (int)cwnd + "\t\t" + (int)wmax 
                         + "\t\t" + t);
    }
}
public class Main {
    public static void main(String[] args) {
        TCPCUBIC cubic = new TCPCUBIC(10.0, 0.4, 0.7, true);
        int maxRTT = 15;
        int lossRound1 = 5;
        int lossRound2 = 12;
        
        System.out.println("RTT\tcwnd\t\tWmax\t\tTime(t)");
        
        for (int r = 1; r <= maxRTT; r++) {
            cubic.printState(r);
            
            if (r == lossRound1) {
                System.out.println("=> Packet loss detected at RTT " + r);
                cubic.onLoss();
                System.out.println("=> cwnd reduced, Wmax updated, epoch reset");
            } else if (r == lossRound2) {
                System.out.println("=> Packet loss detected at RTT " + r);
                cubic.onLoss();
                System.out.println("=> cwnd reduced, new epoch begins");
            } else {
                cubic.onAck();
            }
        }
    }
}

The output of the above code is as follows −

RTT	cwnd		Wmax		Time(t)
1	10		0		0
2	11		0		1
3	12		0		2
4	13		0		3
5	14		0		4
=> Packet loss detected at RTT 5
=> cwnd reduced, Wmax updated, epoch reset
6	9		14		0
7	11		14		1
8	12		14		2
9	13		14		3
10	14		14		4
11	14		14		5
12	14		14		6
=> Packet loss detected at RTT 12
=> cwnd reduced, new epoch begins
13	9		14		0
14	11		14		1
15	12		14		2
class TCPCUBIC:
    def __init__(self, init_cwnd=10.0, cubic_c=0.4, beta=0.7, tcp_friendly=True):
        self.cwnd = init_cwnd
        self.wmax = 0.0
        self.C = cubic_c
        self.beta = beta
        self.t = 0
        self.K = 0.0
        self.tcp_friendly = tcp_friendly
    def on_ack(self):
        self.t += 1
        
        if self.wmax == 0.0:
            self.cwnd += 1.0
        else:
            # CUBIC function: W(t) = C(t-K)^3 + Wmax
            time_diff = self.t - self.K
            target = self.C * (time_diff ** 3) + self.wmax
            
            # TCP-friendly mode
            tcp_cwnd = self.cwnd + (3 * self.beta) / (2 - self.beta) * (self.t / self.cwnd)
            
            if self.tcp_friendly and target < tcp_cwnd:
                self.cwnd = tcp_cwnd
            else:
                self.cwnd = target
            
            if self.cwnd < self.wmax * self.beta:
                self.cwnd = self.wmax * self.beta
    def on_loss(self):
        self.wmax = self.cwnd
        self.cwnd = self.cwnd * self.beta
        self.t = 0
        self.K = ((self.wmax - self.cwnd) / self.C) ** (1/3)
    def print_state(self, rtt):
        print(f"{rtt}\t{int(self.cwnd)}\t\t{int(self.wmax)}\t\t{self.t}")
def simulate():
    cubic = TCPCUBIC(init_cwnd=10.0)
    max_rtt = 15
    loss_round1 = 5
    loss_round2 = 12
    
    print("RTT\tcwnd\t\tWmax\t\tTime(t)")
    
    for r in range(1, max_rtt + 1):
        cubic.print_state(r)
        
        if r == loss_round1:
            print(f"=> Packet loss detected at RTT {r}")
            cubic.on_loss()
            print("=> cwnd reduced, Wmax updated, epoch reset")
        elif r == loss_round2:
            print(f"=> Packet loss detected at RTT {r}")
            cubic.on_loss()
            print("=> cwnd reduced, new epoch begins")
        else:
            cubic.on_ack()
if __name__ == "__main__":
    simulate()

The output of the above code is as follows −

RTT	cwnd		Wmax		Time(t)
1	10		0		0
2	11		0		1
3	12		0		2
4	13		0		3
5	14		0		4
=> Packet loss detected at RTT 5
=> cwnd reduced, Wmax updated, epoch reset
6	9		14		0
7	11		14		1
8	12		14		2
9	13		14		3
10	14		14		4
11	14		14		5
12	14		14		6
=> Packet loss detected at RTT 12
=> cwnd reduced, new epoch begins
13	9		14		0
14	11		14		1
15	12		14		2

Pros and Cons of TCP CUBIC Algorithm

The advantages of TCP CUBIC algorithm are as follows −

  • Better scalability in high bandwidth-delay product networks.
  • Window growth does not depend on RTT which makes bandwidth sharing fair.
  • Simpler implementation than TCP BIC since CUBIC has single cubic function.

Here are the limitations of TCP CUBIC algorithm

  • In some network conditions, it leads to packet loss.
  • It requires accurate RTT measurements for better performance.
  • Complex computation.

Difference between TCP BIC and TCP CUBIC Algorithm

The follwing table highlights the major differences between TCP BIC and TCP CUBIC algorithms −

TCP BIC Algorithm TCP CUBIC Algorithm
It uses binary search with multiple phases. It uses a single cubic function for all phases.
Window growth depends on RTT. Window growth is RTT independent.
Complex implementation with phase transitions. Simpler implementation with single cubic function.
It reduces cwnd to 80% on packet loss. It reduces cwnd to 70% on packet loss.
There is no TCP friendly mode. It has TCP friendly mode.

Conclusion

TCP CUBIC congestion control algorithm uses a cubic function for adjusting the congestion window (cwnd) over time; it provides better performance in high-speed networks. TCP CUBIC is an improvement of TCP BIC with simpler implementation, better RTT-fairness, uses TCP-friendly mode.

Advertisements