Open In App

Proportional Rate Reduction – TCP Loss Recovery Algorithm

Last Updated : 04 Mar, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

TCP CUBIC is becoming the default in Linux Kernel and the Rate Halving technique for congestion avoidance was becoming the default in Linux Kernel. So, there was a mismatch between these two. Because Rate Halving was good for TCP Reno, Tahoe, and NewReno. It is good for those TCPs only which reduce the cwnd by half when packet loss occurs. But TCP Cubic reduces the cwnd by 30%, unlike other TCPs. Thus, Rate Halving and CUBIC couldn’t fit as default in Linux Kernel. This was the motivation of Google and they invented a new algorithm known as Proportional Rate Reduction. This is covered in RFC 6937.

Proportional Rate Reduction:

PRR calculates the number of new packets to be sent when an ACK arrives using some equations. Some new terms are coined for these equations, the new terminologies are as follows:

1. sndcnt (read as ‘send count’): the amount of data to be sent on the arrival of a DupACK. This is the number of new packets that the sender sends on the arrival of a new ACK. Unlike Fast Recovery, it is not equal to 2 and unlike Rate Halving it does not send 1 new packet on the arrival of two DUP-ACKs. It can send any number of new packets which is calculated using a formula.

2. RecoverFS: the value of pipe at the beginning of the recovery phase

3. prr_delivered: the amount of data delivered ‘during’ the recovery phase. This is the count of accumulated data delivered so far to the receiver ever since the recovery phase has begun. This is equal to the number of DUP-ACKs arrived during the recovery phase. How many packets are delivered before the recovery phase is not important? It only counts packets delivered after packet loss.

4. prr_out: the amount of data transmitted ‘during’ the recovery phase. This counts the accumulated number of new packets sent by the sender once it has entered the recovery phase.

5. DeliveredData: the amount of data delivered as indicated by an ACK packet (look at sequence numbers). Don’t confuse this with “prr_delivered”. DeliveredData indicates the number of packets delivered by this DUP_ACK. It can be 1 or more if ACKs arrive out-of-order.

6. limit: data sending limit (calculated in PRR algorithm). This can be considered as the threshold. It is used inside the equation and calculated in intermediary steps.

7. MSS: Maximum Segment Size. This is the typical size of one segment. Since we will be using the granularity of all terms as segments, MSS becomes 1.

Mode of Proportional Rate Reduction:

Proportional Rate Reduction (PRR) has two modes, which are as follows:

  1. Conservative Reduction Bound (CRB)
  2. Slow Start Reduction Bound (SSRB) [this one is enabled by default in Linux]

We can only use one mode at a time, both modes can’t be used simultaneously.

Working of PRR:

When the recovery phase begins, the pipe value is greater than cwnd. But there can be the case when SACK blocks confirm the packet loss and pipe value becomes lesser than cwnd. All these cases are handled differently by the PRR algorithm.

CASE 1: PRR (Case: pipe > ssthresh)

When sender enters the recovery phase:

pipe = 10 segments, 
RecoverFS = 10 segments
ssthresh = 7 segments,

cwnd is calculated by PRR:

cwnd = pipe + sndcnt
sndcnt = CEIL(p_d * ssthresh / RecoverFS) - p_o, {p_d=prr_delivered, p_o=prr_out}

Step 1: one DupACK comes, pipe=10-1=9, p_d=1, p_o=0, (Pipe reduces by one). DupACK tells that 1 packet was delivered. So,  p_d=1, no new packet has been sent yet, so p_o=0

sndcnt= CEIL(1*7/10)-0 = CEIL(0.7)-0 = 1
sender sends one new packet and pipe becomes =9+1=10 again.

Step 2: one DupACK comes, pipe=10-1=9, p_d=2, p_o=1, (Pipe reduces by one). DupACK tells that one packet was delivered, so p_d=2 (cumulative). One new packet has been sent yet, so, p_o=1.

sndcnt= CEIL(2*7/10)-1 = CEIL(1.4)-1= 1
sender sends one new packet and pipe becomes 9 + 1 = 10 again.

This is how it continues.

PRR (Case: pipe > ssthresh)” srcset=”https://media.geeksforgeeks.org/wp-content/uploads/20220225160117/prrcase12.jpg 509w, https://media.geeksforgeeks.org/wp-content/uploads/20220225160117/prrcase12-100×94.jpg 100w, https://media.geeksforgeeks.org/wp-content/uploads/20220225160117/prrcase12-200×188.jpg 200w, https://media.geeksforgeeks.org/wp-content/uploads/20220225160117/prrcase12-300×282.jpg 300w” sizes=”100vw” width=”509″></figure>
<h4 style=Implementation:

Below is the code implementation for Case 1:

C++




// pipe>cwnd, PRR
// PRR, case-1
#include <bits/stdc++.h>
using namespace std;
  
// utility function
void prr(float pipe, float cwnd, float recoverFS,
         float ssthresh)
{
    int i;
    float prr_delivered = 0, prr_out = 0, snd_cnt;
  
    for (i = 1;; i++) {
        // one DUP-ACK arrived
        pipe -= 1;
  
        // terminating condition.
        if (pipe < ssthresh)
            break;
  
        // Calculation
        cout << "Iteration " << i << " => "
             << "pipe= " << pipe;
        prr_delivered = i;
        cout << " p_d= " << prr_delivered;
        snd_cnt = ceil(prr_delivered * ssthresh / recoverFS)
                  - prr_out;
        cout << " snd_cnt= " << snd_cnt;
        cout << " p_o= " << prr_out << "\n";
        prr_out += snd_cnt;
        cwnd = pipe + snd_cnt;
        pipe = cwnd;
    }
}
  
// main function
int main()
{
    float pipe, cwnd, recoverFS, ssthresh;
    pipe = 10;
    ssthresh = 7;
    cwnd = pipe;
    recoverFS = pipe;
    prr(pipe, cwnd, recoverFS, ssthresh);
}


Output

Iteration 1 => pipe= 9 p_d= 1 snd_cnt= 1 p_o= 0
Iteration 2 => pipe= 9 p_d= 2 snd_cnt= 1 p_o= 1
Iteration 3 => pipe= 9 p_d= 3 snd_cnt= 1 p_o= 2
Iteration 4 => pipe= 9 p_d= 4 snd_cnt= 0 p_o= 3
Iteration 5 => pipe= 8 p_d= 5 snd_cnt= 1 p_o= 3
Iteration 6 => pipe= 8 p_d= 6 snd_cnt= 1 p_o= 4
Iteration 7 => pipe= 8 p_d= 7 snd_cnt= 0 p_o= 5
Iteration 8 => pipe= 7 p_d= 8 snd_cnt= 1 p_o= 5
Iteration 9 => pipe= 7 p_d= 9 snd_cnt= 1 p_o= 6
Iteration 10 => pipe= 7 p_d= 10 snd_cnt= 0 p_o= 7

CASE 2: PRR (Case: pipe ≤ ssthresh, with SSRB)

When recovery phase begins:

pipe = 4 segments
RecoverFS = 10 segments
ssthresh = 5 segments

cwnd is calculated by PRR:

cwnd = pipe + sndcnt
sndcnt = MIN (ssthresh - pipe, limit) and 
limit = MAX (p_d - p_o, DeliveredData) + MSS

Step 1: One DupACK comes, pipe reduces by 1, pipe=4-1=3, DupACK confirms that 1 packet got delivered, so p_d=1, no new packet is sent yet, so p_o=0

limit = MAX(1-0, 1)+1 = 1+1 = 2
sndcnt= MIN(5-3, 2) = MIN(2, 2) = 2
So, the sender sends 2 new packets. Thus, pipe=3+2=5

Step 2: One DupACK comes, pipe reduces by 1, pipe=5-1=4, DupACK confirms that 1 packet got delivered, so p_d=2 (cumulative) one new packet is sent yet, so p_o=1

limit = MAX(2-1, 1)+1 = 1+1 = 2
sndcnt= MIN(5-4, 2) = MIN(1, 2) = 1
So, the sender sends 1 new packet. Thus, pipe=4+1=5

This is how it continues.

Implementation:

Below is the code implementation for CASE 2:

C++




// pipe<cwnd with Slow Start Reduction Bound, PRR
  
#include <bits/stdc++.h>
using namespace std;
  
// utility function
void prr_ssrb(float pipe, float cwnd, float recoverFS,
              float ssthresh)
{
    int i;
    float prr_delivered = 0, prr_out = 0, limit, snd_cnt;
  
    for (i = 1; i < 10; i++) {
        // one DUP-ACK arrived
        pipe -= 1;
  
        // Calculation
        cout << "Iteration " << i << " => pipe= " << pipe;
        prr_delivered = i;
        cout << " p_d= " << prr_delivered;
        cout << " p_o= " << prr_out;
        limit = max(prr_delivered - prr_out, float(1)) + 1;
        cout << " limit= " << limit;
        snd_cnt = min(ssthresh - pipe, limit);
        cout << " snd_cnt= " << snd_cnt << "\n";
        prr_out += snd_cnt;
  
        cwnd = pipe + snd_cnt;
        pipe = cwnd;
  
        // terminating condition.
        if (pipe > ssthresh)
            break;
    }
}
  
// main function
int main()
{
    float pipe, cwnd, recoverFS, ssthresh;
    pipe = 4, recoverFS = 10, ssthresh = 5;
    prr_ssrb(pipe, cwnd, recoverFS, ssthresh);
}


Output

Iteration 1 => pipe= 3 p_d= 1 p_o= 0 limit= 2 snd_cnt= 2
Iteration 2 => pipe= 4 p_d= 2 p_o= 2 limit= 2 snd_cnt= 1
Iteration 3 => pipe= 4 p_d= 3 p_o= 3 limit= 2 snd_cnt= 1
Iteration 4 => pipe= 4 p_d= 4 p_o= 4 limit= 2 snd_cnt= 1
Iteration 5 => pipe= 4 p_d= 5 p_o= 5 limit= 2 snd_cnt= 1
Iteration 6 => pipe= 4 p_d= 6 p_o= 6 limit= 2 snd_cnt= 1
Iteration 7 => pipe= 4 p_d= 7 p_o= 7 limit= 2 snd_cnt= 1
Iteration 8 => pipe= 4 p_d= 8 p_o= 8 limit= 2 snd_cnt= 1
Iteration 9 => pipe= 4 p_d= 9 p_o= 9 limit= 2 snd_cnt= 1

CASE 3: PRR (Case: pipe ≤ ssthresh, with CRB)

Initially when sender enters recovery phase:

pipe = 4 segments
RecoverFS = 10 segments
ssthresh = 5 segments

cwnd is calculated by PRR:

cwnd = pipe + sndcnt
sndcnt = MIN (ssthresh - pipe, limit), and 
limit = p_d - p_o

Step 1: One DupACK arrives, pipe reduces by 1, pipe=4-1=3, DupACK confirms that one packet got delivered, so p_d=1, no new packet is sent yet, so p_o=0.

limit = 1-0 = 1,
sndcnt = MIN(5-3, 1) = MIN(2, 1) = 1
Sender sends one new packet, thus pipe = 3+1 = 4

Step 2: One DupACK arrives, pipe reduces by 1, 

pipe= 4-1=3,
p_d=2, p_o=1
limit = 2-1 = 1
sndcnt = MIN(5-3, 1) = MIN(2, 1) = 1
Sender sends one new packet, thus pipe = 3+1 = 4

This is how it continues.

 PRR (Case: pipe ≤ ssthresh, with CRB)

Implementation:

Below is the code implementation for CASE 3:

C++




// pipe<cwnd with Conservative Reduction Bound, PRR
  
#include <bits/stdc++.h>
using namespace std;
  
// utility function
void prr_crb(float pipe, float cwnd, float recoverFS,
             float ssthresh)
{
    int i;
    float prr_delivered = 0, prr_out = 0, limit, snd_cnt;
  
    for (i = 1; i < 10; i++) {
        // one DUP-ACK arrived
        pipe -= 1;
  
        // Calculation
        cout << "Iteration " << i << " => pipe= " << pipe;
        prr_delivered = i;
        cout << " p_d= " << prr_delivered;
        cout << " p_o= " << prr_out;
        limit = prr_delivered - prr_out;
        cout << " limit= " << limit;
        snd_cnt = min(ssthresh - pipe, limit);
        cout << " snd_cnt= " << snd_cnt << "\n";
        prr_out += snd_cnt;
  
        cwnd = pipe + snd_cnt;
        pipe = cwnd;
  
        // terminating condition.
        if (pipe > ssthresh)
            break;
    }
}
  
// main function
int main()
{
    float pipe = 4, cwnd, recoverFS = 10, ssthresh = 5;
  
    prr_crb(pipe, cwnd, recoverFS, ssthresh);
}


Output

Iteration 1 => pipe= 3 p_d= 1 p_o= 0 limit= 1 snd_cnt= 1
Iteration 2 => pipe= 3 p_d= 2 p_o= 1 limit= 1 snd_cnt= 1
Iteration 3 => pipe= 3 p_d= 3 p_o= 2 limit= 1 snd_cnt= 1
Iteration 4 => pipe= 3 p_d= 4 p_o= 3 limit= 1 snd_cnt= 1
Iteration 5 => pipe= 3 p_d= 5 p_o= 4 limit= 1 snd_cnt= 1
Iteration 6 => pipe= 3 p_d= 6 p_o= 5 limit= 1 snd_cnt= 1
Iteration 7 => pipe= 3 p_d= 7 p_o= 6 limit= 1 snd_cnt= 1
Iteration 8 => pipe= 3 p_d= 8 p_o= 7 limit= 1 snd_cnt= 1
Iteration 9 => pipe= 3 p_d= 9 p_o= 8 limit= 1 snd_cnt= 1


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads