TCP New Reno Algorithm



TCP New Reno is an improved version of the TCP Reno algorithm. It has extended fast recovery phase that can handle multiple packet losses within a single window. TCP Reno suffered from the disadvantage that it could handle only single packet loss in the same window.

On multiple packet losses, TCP New Reno does not exit fast recovery phase after retransmitting single lost packet. It identifies and retransmits all the lost packets in the same window before returning to the congestion avoidance phase using partial acknowledgments (partial ACKs). It reduces the data transmission delay.

Characteristics of TCP New Reno Algorithm

The characteristics of TCP New Reno algorithm are mentioned below −

  • It handles multiple packet losses within a single window efficiently.
  • It uses partial acknowledgments to detect additional lost packets.
  • It maintains fast recovery until all packets are acknowledged.
  • It provides better throughput than TCP Reno in lossy networks.

Working of TCP New Reno Algorithm

TCP New Reno works in four main phases −

Slow Start Phase

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 Phase

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 Phase

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 Phase

  • After fast retransmit, it sets the ssthresh to half of the current cwnd and sets cwnd to ssthresh plus 3 MSS. TCP New Reno remains in fast recovery and monitors incoming acknowledgments.
  • If a partial ACK is received, it indicates another lost packet in the same window. TCP New Reno retransmits the next unacknowledged packet and remains in fast recovery. It only exits fast recovery when a full ACK is received.
  • This is the main improvement of TCP New Reno over TCP Reno, which would exit fast recovery on the first new ACK. 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 New Reno algorithm works −

TCP New Reno Algorithm

Example

Here is an example based on the above working of TCP New Reno algorithm. Initially, cwnd = 1 MSS, ssthresh = 8 MSS, and multiple packet losses occur when cwnd = 10 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
cwnd = 9 -> cwnd = 10

3. Multiple Packet Losses at cwnd = 10 (3 duplicate ACKs)
=> New ssthresh = (cwnd / 2) = (10 / 2) = 5
=> cwnd = ssthresh + 3 = 5 + 3 = 8 (Fast Recovery)
=> Retransmit first lost packet

4. Partial ACK received (not all packets acknowledged)
=> Another packet lost, retransmit next lost packet
=> Remain in Fast Recovery

5. Full ACK received (all packets acknowledged)
=> Exit Fast Recovery
=> cwnd = ssthresh = 5

6. Continue in Congestion Avoidance
cwnd = 5, increase slowly: cwnd = 6
cwnd = 6 -> cwnd = 7
=> Continue from congestion avoidance.

Steps to Implement TCP New Reno Algorithm

Here are the steps to implement TCP New 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.
  • Set ssthresh = cwnd / 2, set cwnd = ssthresh + 3, and enter Fast Recovery.
  • While in Fast Recovery, check for partial ACKs and retransmit next lost packet if detected.
  • Exit Fast Recovery only when full ACK is received, then set cwnd = ssthresh.
  • 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 New 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 = 18;    // total RTT rounds to simulate
    int lossRound = 7;  // first loss occurs in this round
    int partialAckRound = 8; // partial ACK received in this round
    int fullAckRound = 9;    // full ACK received 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
    int recoverPoint = 0; // highest seq sent before entering fast recovery
    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);
                recoverPoint = cwnd; // mark recovery point
                cwnd = ssthresh + 3;
                phase = FAST_RECOVERY;
                cout << "=> Triple duplicate ACKs: ssthresh=" << ssthresh 
                     << ", \n \t cwnd=" << cwnd << " (enter Fast Recovery)\n";
                cout << "=> Retransmit first lost packet\n";
                continue;
            }
        }
        // Simulate partial ACK in Fast Recovery
        if (r == partialAckRound && phase == FAST_RECOVERY)
        {
            cout << "=> Partial ACK received (multiple losses detected)\n";
            cout << "=> Retransmit next lost packet, remain in Fast Recovery\n";
            continue;
        }
        // Simulate full ACK to exit Fast Recovery
        if (r == fullAckRound && phase == FAST_RECOVERY)
        {
            cwnd = ssthresh;
            phase = CONGESTION_AVOIDANCE;
            cout << "=> Full ACK received: exit Fast Recovery," 
                 << "\n\tcwnd set to ssthresh=" << cwnd << "\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)
        {
            // Stay in fast recovery, inflate cwnd for each duplicate ACK
            cwnd = cwnd + 1;
        }
        // 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)
=> Retransmit first lost packet
8	Fast Recov	8	5
=> Partial ACK received (multiple losses detected)
=> Retransmit next lost packet, remain in Fast Recovery
9	Fast Recov	8	5
=> Full ACK received: exit Fast Recovery,
	cwnd set to ssthresh=5
10	Cong Avoid	5	5
11	Cong Avoid	6	5
12	Cong Avoid	7	5
13	Cong Avoid	8	5
14	Cong Avoid	9	5
15	Cong Avoid	10	5
16	Cong Avoid	11	5
17	Cong Avoid	12	5
18	Cong Avoid	13	5
public class Main {
    enum Phase { SLOW_START, CONGESTION_AVOIDANCE, FAST_RECOVERY }
    public static void main(String[] args) {
        int maxRTT = 18;            // total RTT rounds to run
        int lossRound = 7;          // first loss occurs in this round
        int partialAckRound = 8;    // partial ACK received in this round
        int fullAckRound = 9;       // full ACK received 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
        int recoverPoint = 0;       // highest seq sent before entering fast recovery
        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);
                    recoverPoint = cwnd; // mark recovery point
                    cwnd = ssthresh + 3;
                    phase = Phase.FAST_RECOVERY;
                    System.out.println("=> Triple duplicate ACKs: ssthresh=" + ssthresh
                                     + ", \n \t cwnd=" + cwnd + " (enter Fast Recovery)");
                    System.out.println("=> Retransmit first lost packet");
                    continue;
                }
            }
            // Simulate partial ACK in Fast Recovery
            if (r == partialAckRound && phase == Phase.FAST_RECOVERY) {
                System.out.println("=> Partial ACK received (multiple losses detected)");
                System.out.println("=> Retransmit next lost packet, remain in Fast Recovery");
                continue;
            }
            // Simulate full ACK to exit Fast Recovery
            if (r == fullAckRound && phase == Phase.FAST_RECOVERY) {
                cwnd = ssthresh;
                phase = Phase.CONGESTION_AVOIDANCE;
                System.out.println("=> Full ACK received: exit Fast Recovery,"
                                 + "\n\tcwnd set to ssthresh=" + cwnd);
                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) {
                // Stay in fast recovery, inflate cwnd for each duplicate ACK
                cwnd = cwnd + 1;
            }
            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)
=> Retransmit first lost packet
8	Fast Recov	8	5
=> Partial ACK received (multiple losses detected)
=> Retransmit next lost packet, remain in Fast Recovery
9	Fast Recov	8	5
=> Full ACK received: exit Fast Recovery,
	cwnd set to ssthresh=5
10	Cong Avoid	5	5
11	Cong Avoid	6	5
12	Cong Avoid	7	5
13	Cong Avoid	8	5
14	Cong Avoid	9	5
15	Cong Avoid	10	5
16	Cong Avoid	11	5
17	Cong Avoid	12	5
18	Cong Avoid	13	5
def simulate(max_rtt=18, loss_round=7, partial_ack_round=8, full_ack_round=9, loss_is_timeout=False):
    cwnd = 1   
    ssthresh = 8
    recover_point = 0  # highest seq sent before entering fast recovery
    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)
                recover_point = cwnd  # mark recovery point
                cwnd = ssthresh + 3
                phase = "FAST_RECOVERY"
                print(f"=> Triple duplicate ACKs: ssthresh={ssthresh}, \n \t cwnd={cwnd} (enter Fast Recovery)")
                print("=> Retransmit first lost packet")
                continue
        # Simulate partial ACK in Fast Recovery
        if r == partial_ack_round and phase == "FAST_RECOVERY":
            print("=> Partial ACK received (multiple losses detected)")
            print("=> Retransmit next lost packet, remain in Fast Recovery")
            continue
        # Simulate full ACK to exit Fast Recovery
        if r == full_ack_round and phase == "FAST_RECOVERY":
            cwnd = ssthresh
            phase = "CONGESTION_AVOIDANCE"
            print(f"=> Full ACK received: exit Fast Recovery,\n \t cwnd set to ssthresh={cwnd}")
            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":
            # Stay in fast recovery, inflate cwnd for each duplicate ACK
            cwnd = cwnd + 1
        if cwnd < 1:
            cwnd = 1
if __name__ == "__main__":
    simulate(max_rtt=18, loss_round=7, partial_ack_round=8, full_ack_round=9, 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)
=> Retransmit first lost packet
8	Fast Recov	8	5
=> Partial ACK received (multiple losses detected)
=> Retransmit next lost packet, remain in Fast Recovery
9	Fast Recov	8	5
=> Full ACK received: exit Fast Recovery,
 	 cwnd set to ssthresh=5
10	Cong Avoid	5	5
11	Cong Avoid	6	5
12	Cong Avoid	7	5
13	Cong Avoid	8	5
14	Cong Avoid	9	5
15	Cong Avoid	10	5
16	Cong Avoid	11	5
17	Cong Avoid	12	5
18	Cong Avoid	13	5

Pros and Cons of TCP New Reno Algorithm

The advantages of TCP New Reno algorithm are as follows −

  • It handles multiple packet losses efficiently within a single window.
  • It provides better throughput than TCP Reno in lossy networks.
  • It avoids multiple reductions of congestion window for losses in the same window.
  • It maintains higher network utilization during recovery.
  • It reduces unnecessary slow start invocations.

Here are the limitations of TCP New Reno algorithm

  • It still performs poorly when many packets are lost in a single window.
  • Slow recovery when too many packets are lost.
  • Complex to implement.

Difference between TCP Reno and New Reno Algorithm

The following table highlights the major differences between TCP Reno and New Reno algorithms −

TCP Reno Algorithm TCP New Reno Algorithm
It can only handle a single packet loss in same window. It can handles multiple packet losses in a single window.
Simple to implement. It is more complex than TCP reno.
It exits the fast recovery phase on receiving the first new ACK, even if other packets are still lost in the same window. It remains in fast recovery phase until all packets in the window are acknowledged. It uses partial ACKs to find packet losses.
It may enter slow start phase multiple times even for losses in the same window. It remains in fast recovery phase and avoids entering slow start phase multiple times.
Less throughput. Higher throughput than TCP Reno.

Conclusion

TCP New Reno is an improved congestion control algorithm that provides a reliable, ordered, and error-checked delivery of data packets with better performance than TCP Reno. The most important feature of TCP New Reno is its enhanced fast recovery phase that can handle multiple packet losses in a single window using partial acknowledgments.

Advertisements