TCP Reno Algorithm



TCP Reno algorithm is an improved congestion control algorithm by which TCP controls the amount of data that can be sent over a network for avoiding network congestion.

What is TCP Reno Algorithm?

TCP Reno is an improved version of the TCP Tahoe algorithm. One major feature of TCP Reno is its fast recovery phase from packet loss using 3 duplicate ACKs. When a packet loss occurs, it does not retransmitting all unacknowledged packets. It only retransmits the lost packets with new packets. This reduces the delays in data transmission and improves network throughput.

The primary aim of TCP Reno algorithm is to provide a fast and efficient data transfer over congested networks. It improves the throughput of network as it avoids restarting slow start phase on minor packet losses.

Characteristics of TCP Reno Algorithm

The characteristics of TCP Reno algorithm are mentioned below −

  • It provides faster recovery from packet loss compared to TCP Tahoe.
  • It distinguishes between timeout and duplicate ACKs.
  • It is suitable for moderate to high-bandwidth networks.
  • It maintains better throughput than TCP Tahoe.

Working of TCP Reno Algorithm

TCP Reno works in four main phases −

  • Slow start − In slow start phase, the congestion window (cwnd) starts with a small value and then increases exponentially with each acknowledgment received. It keeps increasing until it reaches a threshold (ssthresh) or packet loss occurs.
  • Congestion avoidance − It uses the AIMD (Additive Increase Multiplicative Decrease) method. Once the cwnd reaches the ssthresh, the congestion avoidance phase starts. In this phase, the cwnd increases linearly (earlier it was increasing exponentially) with each acknowledgment received.
  • Fast retransmit − When three duplicate ACKs are received, it means that the packet has been lost. It then immediately retransmits the missing packet without waiting for a timeout.
  • Fast recovery − After fast retransmit, it sets the ssthresh to half of the current cwnd and sets cwnd to ssthresh plus 3 MSS. Then it enters the congestion avoidance phase directly without entering slow start phase. It is the main improvement of TCP Reno over TCP Tahoe as TCP Tahoe would reset cwnd to 1 MSS. Here, if a timeout occurs, then cwnd is reset to 1 MSS and the slow start phase begins again.

The following image depicts how the TCP Reno algorithm works −

Working of TCP Reno Algorithm

Here is an example based on the above working of TCP Reno algorithm. Initially, cwnd = 1 MSS, ssthresh = 8 MSS, and a packet loss occurs when cwnd = 9 MSS

1. Start in Slow Start, cwnd = 1
Receive ACK -> cwnd = 2
Receive ACK -> cwnd = 4
Receive ACK -> cwnd = 8
cwnd = ssthresh = 8 => stop exponential growth.
 
2. Congestion Avoidance Phase:
cwnd = 8, increase slowly (approx +1 per RTT): cwnd = 9
 
3. Packet Loss at cwnd = 9 (3 duplicate ACKs)
=> New ssthresh = (cwnd / 2) = (9 / 2) = 4
=> cwnd = ssthresh + 3 = 4 + 3 = 7 (Fast Recovery)
 
4. Lost packet gets acknowledged
=> Exit Fast Recovery
=> cwnd = ssthresh = 4
 
5. Continue in Congestion Avoidance
cwnd = 4, increase slowly: cwnd = 5
cwnd = 5 -> cwnd = 6
=> Continue from congestion avoidance.

Steps to Implement TCP Reno Algorithm

Here are the steps to implement TCP Reno algorithm −

  • Set the cwnd = 1 MSS and initial value of ssthresh and the number of iterations.
  • In Slow Start phase increase cwnd by 1 MSS for every ACK received.
  • When cwnd reaches ssthresh, switch to Congestion Avoidance phase and increase cwnd slowly.
  • On receiving 3 duplicate ACKs, trigger Fast Retransmit and Fast Recovery.
  • Now, set ssthresh = cwnd / 2, set cwnd = ssthresh, and continue in Congestion Avoidance.
  • On timeout, reset cwnd to 1 MSS and go back to Slow Start.

Code Implementation

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

#include <iostream>
#include <algorithm>
using namespace std;

enum Phase
{
    SLOW_START,
    CONGESTION_AVOIDANCE,
    FAST_RECOVERY
};

int main()
{
    int maxRTT = 15;    // total RTT rounds to simulate
    int lossRound = 7;  // a loss occurs in this round
    // set true to simulate timeout
    // false for triple duplicate ACKs
    bool lossIsTimeout = false; 

    int cwnd = 1; // in MSS units
    int ssthresh = 8;   // initial slow start threshold
    Phase phase = SLOW_START;

    cout << "RTT\tPhase\t\tcwnd\tssthresh\n";
    for (int r = 1; r <= maxRTT; ++r)
    {
        // Print current state at start of RTT
        string ph = (phase == SLOW_START) ? "Slow Start" : (phase == CONGESTION_AVOIDANCE) ? "Cong Avoid"
                                                                                           : "Fast Recov";
        cout << r << "\t" << ph << "\t" << cwnd << "\t" << ssthresh << "\n";

        // Simulate loss event
        if (r == lossRound)
        {
            if (lossIsTimeout)
            {
                // Timeout => go to slow start
                ssthresh = max(cwnd / 2, 1);
                cwnd = 1;
                phase = SLOW_START;
                cout << "=> Timeout detected: ssthresh=" << ssthresh 
                     << ", \n \t cwnd=" << cwnd << " (enter Slow Start)\n";
                continue;
            }
            else
            {
                // Triple duplicate ACKs => Fast Recovery
                ssthresh = max(cwnd / 2, 1);
                // cwnd = ssthresh + 3 
                cwnd = ssthresh + 3;
                phase = FAST_RECOVERY;
                cout << "=> Triple duplicate ACKs: ssthresh=" << ssthresh 
                     << ", \n \t cwnd=" << cwnd << " (enter Fast Recovery)\n";
                continue;
            }
        }

        // In no loss
        if (phase == SLOW_START)
        {
            // Exponential growth 
            cwnd = cwnd * 2;
            if (cwnd >= ssthresh)
            {
                // Crossing ssthresh => move to congestion avoidance
                phase = CONGESTION_AVOIDANCE;
            }
        }
        else if (phase == CONGESTION_AVOIDANCE)
        {
            // Linear increase => cwnd += 1 MSS per RTT
            cwnd = cwnd + 1;
        }
        else if (phase == FAST_RECOVERY)
        {
            cwnd = ssthresh;
            phase = CONGESTION_AVOIDANCE;
            cout << "=> New ACK received: exit Fast Recovery," 
                 << "\n\tcwnd set to ssthresh=" << cwnd << "\n";
        }
        // keep values same
        if (cwnd < 1)
            cwnd = 1;
    }

    return 0;
}

The output of the above code is as follows −

RTT	Phase		cwnd	ssthresh
1	Slow Start	1	8
2	Slow Start	2	8
3	Slow Start	4	8
4	Cong Avoid	8	8
5	Cong Avoid	9	8
6	Cong Avoid	10	8
7	Cong Avoid	11	8
=> Triple duplicate ACKs: ssthresh=5, 
 	 cwnd=8 (enter Fast Recovery)
8	Fast Recov	8	5
=> New ACK received: exit Fast Recovery,
	cwnd set to ssthresh=5
9	Cong Avoid	5	5
10	Cong Avoid	6	5
11	Cong Avoid	7	5
12	Cong Avoid	8	5
13	Cong Avoid	9	5
14	Cong Avoid	10	5
15	Cong Avoid	11	5
public class Main {
    enum Phase { SLOW_START, CONGESTION_AVOIDANCE, FAST_RECOVERY }

    public static void main(String[] args) {
        int maxRTT = 15;            // total RTT rounds to run
        int lossRound = 7;          // a loss occurs in this round
        // set true to simulate timeout
        // false for triple duplicate ACKs
        boolean lossIsTimeout = false; 

        int cwnd = 1; 
        int ssthresh = 8;           // initial slow start threshold
        Phase phase = Phase.SLOW_START;

        System.out.println("RTT\tPhase\t\tcwnd\tssthresh");

        for (int r = 1; r <= maxRTT; r++) {
            String ph = (phase == Phase.SLOW_START) ? "Slow Start"
                       : (phase == Phase.CONGESTION_AVOIDANCE) ? "Cong Avoid" : "Fast Recov";
            System.out.println(r + "\t" + ph + "\t" + cwnd + "\t" + ssthresh);

            // Simulate loss event 
            if (r == lossRound) {
                if (lossIsTimeout) {
                    // Timeout => go to slow start
                    ssthresh = Math.max(cwnd / 2, 1);
                    cwnd = 1;
                    phase = Phase.SLOW_START;
                    System.out.println("=> Timeout detected: ssthresh=" + ssthresh
                                     + ", \n \t cwnd=" + cwnd + " (enter Slow Start)");
                    continue;
                } else {
                    // Triple duplicate ACKs => Fast Recovery
                    ssthresh = Math.max(cwnd / 2, 1);
                    // cwnd = ssthresh + 3 
                    cwnd = ssthresh + 3;
                    phase = Phase.FAST_RECOVERY;
                    System.out.println("=> Triple duplicate ACKs: ssthresh=" + ssthresh
                                     + ", \n \t cwnd=" + cwnd + " (enter Fast Recovery)");
                    continue;
                }
            }

            // In no loss
            if (phase == Phase.SLOW_START) {
                // Exponential growth
                cwnd = cwnd * 2;
                if (cwnd >= ssthresh) {
                    // Crossing ssthresh => move to congestion avoidance
                    phase = Phase.CONGESTION_AVOIDANCE;
                }
            } else if (phase == Phase.CONGESTION_AVOIDANCE) {
                // Linear increase => cwnd += 1 MSS per RTT
                cwnd = cwnd + 1;
            } else if (phase == Phase.FAST_RECOVERY) {
                cwnd = ssthresh;
                phase = Phase.CONGESTION_AVOIDANCE;
                System.out.println("=> New ACK received: exit Fast Recovery,"
                                 + "\n\tcwnd set to ssthresh=" + cwnd);
            }

            if (cwnd < 1) 
                cwnd = 1;
        }
    }
}

The output of the above code is as follows −

RTT	Phase		cwnd	ssthresh
1	Slow Start	1	8
2	Slow Start	2	8
3	Slow Start	4	8
4	Cong Avoid	8	8
5	Cong Avoid	9	8
6	Cong Avoid	10	8
7	Cong Avoid	11	8
=> Triple duplicate ACKs: ssthresh=5, 
 	 cwnd=8 (enter Fast Recovery)
8	Fast Recov	8	5
=> New ACK received: exit Fast Recovery,
	cwnd set to ssthresh=5
9	Cong Avoid	5	5
10	Cong Avoid	6	5
11	Cong Avoid	7	5
12	Cong Avoid	8	5
13	Cong Avoid	9	5
14	Cong Avoid	10	5
15	Cong Avoid	11	5
def simulate(max_rtt=15, loss_round=7, loss_is_timeout=False):
    cwnd = 1   
    ssthresh = 8
    phase = "SLOW_START"  # "SLOW_START", "CONGESTION_AVOIDANCE", "FAST_RECOVERY"

    print("RTT\tPhase\t\tcwnd\tssthresh")

    for r in range(1, max_rtt + 1):
        ph = "Slow Start" if phase == "SLOW_START" else ("Cong Avoid" if phase == "CONGESTION_AVOIDANCE" else "Fast Recov")
        print(f"{r}\t{ph}\t{cwnd}\t{ssthresh}")

        # In loss event 
        if r == loss_round:
            if loss_is_timeout:
                # Timeout => go to slow start
                ssthresh = max(cwnd // 2, 1)
                cwnd = 1
                phase = "SLOW_START"
                print(f"=> Timeout detected: ssthresh={ssthresh}, cwnd={cwnd} (enter Slow Start)")
                continue
            else:
                # Triple duplicate ACKs => Fast Recovery
                ssthresh = max(cwnd // 2, 1)
                # cwnd = ssthresh + 3 
                cwnd = ssthresh + 3
                phase = "FAST_RECOVERY"
                print(f"=> Triple duplicate ACKs: ssthresh={ssthresh}, \n \t cwnd={cwnd} (enter Fast Recovery)")
                continue

        # In no loss
        if phase == "SLOW_START":
            # Exponential growth 
            cwnd = cwnd * 2
            if cwnd >= ssthresh:
                # crossing ssthresh => move to congestion avoidance
                phase = "CONGESTION_AVOIDANCE"
        elif phase == "CONGESTION_AVOIDANCE":
            # Linear increase
            cwnd = cwnd + 1
        elif phase == "FAST_RECOVERY":
            cwnd = ssthresh
            phase = "CONGESTION_AVOIDANCE"
            print(f"=> New ACK received: exit Fast Recovery,\n \t cwnd set to ssthresh={cwnd}")

        if cwnd < 1:
            cwnd = 1

if __name__ == "__main__":
    simulate(max_rtt=15, loss_round=7, loss_is_timeout=False)

The output of the above code is as follows −

RTT	Phase		cwnd	ssthresh
1	Slow Start	1	8
2	Slow Start	2	8
3	Slow Start	4	8
4	Cong Avoid	8	8
5	Cong Avoid	9	8
6	Cong Avoid	10	8
7	Cong Avoid	11	8
=> Triple duplicate ACKs: ssthresh=5, 
 	 cwnd=8 (enter Fast Recovery)
8	Fast Recov	8	5
=> New ACK received: exit Fast Recovery,
 	 cwnd set to ssthresh=5
9	Cong Avoid	5	5
10	Cong Avoid	6	5
11	Cong Avoid	7	5
12	Cong Avoid	8	5
13	Cong Avoid	9	5
14	Cong Avoid	10	5
15	Cong Avoid	11	5

Pros and Cons of TCP Reno Algorithm

The following list highlights the advantages of TCP Reno algorithm

  • It provides better throughput than TCP Tahoe.
  • It recovers faster from packet loss using fast recovery.
  • It maintains higher network utilization.
  • It is more efficient in handling isolated packet losses.
  • It reduces the recovery time significantly.

Here are the limitations of TCP Reno algorithm

  • It cannot handle multiple packet losses in a single window.
  • Its throughput decreases in a high loss networks.

Conclusion

TCP Reno is an improved congestion control algorithm that provides a reliable, ordered, and error-checked delivery of data packets with better performance than TCP Tahoe. In this chapter, we covered the features, working, implementation steps, code implementation, advantages, and limitations of the TCP Reno algorithm.

Advertisements