Transport layer II: flow control, congestion control and RTT estimation
Flow control with rwnd, cwnd traces with slow start and AIMD (Tahoe and Reno), average TCP throughput, and timeout calculation with EWMA. The round-by-round cwnd table and a timeout question are certain to appear on the midterm.
Last week we saw how TCP numbers segments. This week we talk about how fast it sends. There are two separate brakes: flow control so as not to overwhelm the receiver, and congestion control so as not to overwhelm the network. The exam almost always has a round-by-round cwnd table and a timeout calculation. Golden rules:
- Sender window = min(cwnd, rwnd). One comes from the receiver, one from the network.
- Slow start doubles, congestion avoidance adds 1. The switch point is ssthresh.
- On loss, ssthresh = cwnd / 2. In both versions, for both kinds of loss.
- On a timeout, cwnd = 1. Tahoe drops to 1 on a triple duplicate ACK too; Reno drops to ssthresh + 3.
- Timeout = EstimatedRTT + 4 · DevRTT.
Flow control
The receiver has a buffer (RcvBuffer). Bytes arriving from the network are written here, and the buffer empties as the application reads. If the application reads slowly, the buffer fills and new arrivals are lost. To prevent this, TCP sends the amount of free space, the receive window (rwnd) field, in the header of every segment:
The sender in turn keeps the data in flight below this value:
Worked example: RcvBuffer = 20000 bytes, LastByteRcvd = 15000, LastByteRead = 9000.
unread data in the buffer = 15000 − 9000 = 6000 bytes
rwnd = 20000 − 6000 = 14000 bytes
PITFALL · the rwnd = 0 deadlock. If the receiver said rwnd = 0 and has no data to send, it doesn’t announce the new rwnd. The sender could wait forever. That is why the sender sends 1-byte probe segments (zero window probes) while the window is zero.
The window is also a rate limit: at most one window’s worth of data is sent per RTT. Without window scaling, rwnd is at most 65535 bytes, and with RTT 50 ms the rate is at most Mbps.
Congestion control: cwnd and ssthresh
When the queues of the routers in the middle of the network fill up, packets are dropped. TCP gets no explicit signal from the network (classic TCP, the end-to-end approach). It treats loss as a sign of congestion and adjusts the congestion window (cwnd) on the sender side. The rate becomes roughly .
There are three phases:
| Phase | Rule | Change in one RTT |
|---|---|---|
| Slow start | cwnd += 1 MSS per ACK | ×2 |
| Congestion avoidance | cwnd += MSS·(MSS/cwnd) per ACK | +1 MSS |
| Fast recovery (Reno) | After three duplicate ACKs | cwnd = ssthresh + 3, then linear |
Reactions to loss:
| Event | ssthresh | Tahoe cwnd | Reno cwnd |
|---|---|---|---|
| 3 duplicate ACKs (triple duplicate ACK) | cwnd / 2 | 1 MSS | ssthresh + 3 MSS |
| Timeout | cwnd / 2 | 1 MSS | 1 MSS |
When three duplicate ACKs arrive, the sender resends the missing segment immediately without waiting for the timeout. This is called fast retransmit. Why three? One or two duplicate ACKs can also come from mere reordering.
Worked example (fast retransmit): the sender sends segments 1 through 6, and segment 2 is lost. When segment 1 arrives, the receiver indicates that it wants segment 2. As segments 3, 4 and 5 arrive, the same ACK is repeated three times:
arriving seg ACK (expected) duplicate count at sender
1 2 -
3 2 1st duplicate
4 2 2nd duplicate
5 2 3rd duplicate → seg 2 is resent immediately
There is no need to wait for segment 6 to arrive or for the timer to expire. The loss is repaired in about one RTT.
AIMD (additive increase, multiplicative decrease): linear increase when there is no loss, halving on loss. Two AIMD connections sharing the same bottleneck approach an equal share over time. This is TCP’s fairness property.
TIP · the table rule. On this page, traces are written per round (RTT). When doubling cwnd in slow start, if you would exceed ssthresh, we stop at ssthresh: cwnd = min(2·cwnd, ssthresh). Some instructors skip the +3 in Reno and write cwnd = ssthresh. On the exam, read the question’s rule.
Worked example: a Tahoe and Reno trace
Start: cwnd = 1 MSS, ssthresh = 8 MSS. At the end of round 12, three duplicate ACKs are seen.
round : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Tahoe : 1 2 4 8 9 10 11 12 13 14 15 16 1 2 4 8 9 10 11 12
Reno : 1 2 4 8 9 10 11 12 13 14 15 16 11 12 13 14 15 16 17 18
ssth : 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
Step by step:
- Rounds 1-4: slow start, 1 → 8. In round 4, cwnd = ssthresh = 8, and we are now in congestion avoidance.
- Rounds 5-12: +1 per round, 9 → 16.
- Three duplicate ACKs at the end of round 12: ssthresh = 16 / 2 = 8 (it stays the same).
- Tahoe: cwnd = 1, slow start again: 1, 2, 4, 8 (rounds 13-16), then 9, 10, 11, 12.
- Reno: cwnd = 8 + 3 = 11 (round 13), then linear: 12, 13, … 18.
Eight rounds later Reno is at 18 and Tahoe at 12. That is the gain of fast recovery.
Typical things the questions ask: “In which rounds is it slow start?” (the rounds where cwnd < ssthresh), “Is the loss detection a triple duplicate ACK or a timeout?” (if cwnd drops to 1 and it is Reno, it is a timeout; if it is halved, it is a triple duplicate ACK), “What is ssthresh in round n?” (half of the cwnd at the last loss).
Average TCP throughput
In congestion avoidance the window rises to the value W at the moment of loss, halves, and rises again. The average of the sawtooth is :
Worked example: W = 40 segments, MSS = 1500 bytes, RTT = 100 ms.
W = 40 × 1500 × 8 = 480000 bit
0.75 × 480000 / 0.1 s = 3600000 bit/s = 3.6 Mbps
RTT estimation and timeout
If the timeout is too short, there are unnecessary retransmissions; if it is too long, the reaction to loss is late. TCP smooths the measured round-trip times (SampleRTT) with an exponentially weighted moving average (EWMA):
Worked example: EstimatedRTT = 100 ms, DevRTT = 10 ms, new SampleRTT = 140 ms.
Est = 0.875 × 100 + 0.125 × 140 = 87.5 + 17.5 = 105 ms
Dev = 0.75 × 10 + 0.25 × |140 − 105| = 7.5 + 8.75 = 16.25 ms
Timeout = 105 + 4 × 16.25 = 170 ms
PITFALL · the update order. The order in the textbook: first EstimatedRTT, then DevRTT with the new Est. RFC 6298, on the other hand, updates DevRTT first with the old Est: Dev = 0.75·10 + 0.25·|140 − 100| = 17.5, Timeout = 175 ms. If the question doesn’t specify the order, use the textbook order and state the order you used.
If several samples arrive, chain the same step. Starting with Est = 80, Dev = 8 and samples 120, 60, 96 ms:
sample EstimatedRTT DevRTT Timeout
120 85 14.75 144
60 81.875 16.53125 148
96 83.640625 15.48828125 145.59375
A single large sample (120) moves Est by only 5 ms but increases Dev immediately. What really protects the timeout is the Dev term.
Two additional rules:
- Karn’s algorithm: no SampleRTT is taken from the ACK of a retransmitted segment, because it is unclear which copy the ACK belongs to.
- Exponential backoff: on a timeout, the next timeout doubles (1.25 s → 2.5 s → 5 s). When a new ACK arrives, return to the formula.
Common mistakes
| Wrong idea | Correct |
|---|---|
| Slow start grows slowly (linearly) | It is exponential, ×2 every RTT |
| Flow control = congestion control | Flow: the receiver (rwnd). Congestion: the network (cwnd) |
| Window = cwnd | Window = min(cwnd, rwnd) |
| On loss ssthresh is half the old ssthresh | Half of the cwnd at the moment of loss |
| Reno halves on a timeout too | On a timeout Reno also goes to cwnd = 1 |
| Tahoe halves on three duplicate ACKs | Tahoe goes to cwnd = 1 on every loss |
| Average rate = W / RTT | ≈ 0.75 · W / RTT (sawtooth) |
| Timeout = EstimatedRTT | A safety margin of 4·DevRTT is added |
| RTT is also measured from a retransmitted segment | Karn: it is not measured |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
A TCP receiver has RcvBuffer = 16384 bytes, LastByteRcvd = 31000 and LastByteRead = 22000. What rwnd does it advertise?
Show answer
Data waiting in the buffer that the application has not yet read: 31000 − 22000 = 9000 bytes.
rwnd = RcvBuffer − (LastByteRcvd − LastByteRead) = 16384 − 9000 = 7384 bytes.
A sender has LastByteSent = 50000 and LastByteAcked = 42000. Its cwnd is 12000 bytes and the last advertised rwnd is 10000 bytes. How many more bytes may it send right now?
Show answer
Data in flight (unacknowledged): 50000 − 42000 = 8000 bytes.
Effective window = min(cwnd, rwnd) = min(12000, 10000) = 10000 bytes.
Additional bytes that can be sent: 10000 − 8000 = 2000 bytes.
With no window scaling, rwnd is at most 65535 bytes. What is the maximum TCP throughput on a path with RTT = 50 ms?
Show answer
At most one window is sent per RTT: throughput . bit/s ≈ 10.49 Mbps. Whatever the capacity of the line, you can't exceed this limit.
During slow start (no losses), how does cwnd change?
- It increases by 1 MSS per RTT
- It doubles every RTT (increases by 1 MSS per ACK)
- It halves every RTT
- It stays constant until a timeout
Show answer
In slow start each arriving ACK increases cwnd by 1 MSS. Since cwnd ACKs arrive in one RTT, the window doubles every RTT: 1, 2, 4, 8... The name says "slow" but the growth is exponential.
A connection starts with cwnd = 1 MSS and ssthresh = 64 MSS. With no losses, after how many RTTs does cwnd first reach 32 MSS?
Show answer
Round 1: 1, round 2: 2, round 3: 4, round 4: 8, round 5: 16, round 6: 32.
From round 1 to round 6, 5 RTTs pass. In general MSS is reached in RTTs: .
TCP Reno starts with cwnd = 1 MSS, ssthresh = 16 MSS. A timeout occurs at the end of round 9, and a triple duplicate ACK at the end of round 16. Using cwnd = min(2·cwnd, ssthresh) in slow start and cwnd = ssthresh + 3 after a triple duplicate ACK, give cwnd in rounds 10, 14 and 17, and ssthresh in round 17.
Show answer
Rounds 1-5 slow start: 1, 2, 4, 8, 16. Rounds 6-9 congestion avoidance: 17, 18, 19, 20.
Timeout at the end of round 9: ssthresh = 20/2 = 10, cwnd = 1. Round 10: 1.
Rounds 11-14: 2, 4, 8, then min(16, 10) = 10. Round 15: 11, round 16: 12.
Triple duplicate ACK at the end of round 16: ssthresh = 12/2 = 6, Reno cwnd = 6 + 3 = 9 (round 17), then it grows linearly.
Same scenario as before but with TCP Tahoe. What is cwnd in round 20?
Show answer
Up to round 16 it is the same as Reno (on a timeout both behave the same): cwnd = 12 in round 16.
Tahoe drops cwnd to 1 on a triple duplicate ACK too, and ssthresh = 6.
Round 17: 1, round 18: 2, round 19: 4, round 20: min(8, 6) = 6.
Why does TCP Reno react more mildly to a triple duplicate ACK than to a timeout?
- Duplicate ACKs mean the receiver's buffer is full
- Duplicate ACKs show later segments are still getting through, so congestion is mild; a timeout suggests heavier congestion
- A timeout only happens with UDP
- Duplicate ACKs are ignored by Reno
Show answer
The arrival of duplicate ACKs shows that segments after the lost one reached the receiver: the network is still carrying traffic. So Reno halves the window (fast recovery) and continues. At a timeout no ACK has arrived at all, the situation is considered worse, and cwnd drops to 1 MSS.
Using the approximation average throughput = 0.75 · W / RTT, find the average throughput for W = 20 segments, MSS = 1460 bytes, RTT = 80 ms.
Show answer
W in bytes: 20 × 1460 = 29200 bytes = 233600 bits. bit/s ≈ 2.19 Mbps.
Where 0.75 comes from: under AIMD the window traces a sawtooth between W/2 and W, with average 3W/4.
EstimatedRTT = 200 ms and DevRTT = 20 ms. A new SampleRTT of 160 ms arrives. With α = 0.125 and β = 0.25, updating EstimatedRTT first and then DevRTT with the new EstimatedRTT, find the new TimeoutInterval.
Show answer
EstimatedRTT = 0.875 × 200 + 0.125 × 160 = 175 + 20 = 195 ms.
DevRTT = 0.75 × 20 + 0.25 × |160 − 195| = 15 + 8.75 = 23.75 ms.
TimeoutInterval = 195 + 4 × 23.75 = 195 + 95 = 290 ms.
EstimatedRTT = 80 ms, DevRTT = 8 ms. Two samples arrive in order: 120 ms then 60 ms. Using α = 0.125, β = 0.25 (EstimatedRTT updated first), give EstimatedRTT, DevRTT and TimeoutInterval after both samples.
Show answer
Sample 1 (120): Est = 0.875·80 + 0.125·120 = 70 + 15 = 85. Dev = 0.75·8 + 0.25·|120 − 85| = 6 + 8.75 = 14.75. (Timeout 85 + 59 = 144.)
Sample 2 (60): Est = 0.875·85 + 0.125·60 = 74.375 + 7.5 = 81.875. Dev = 0.75·14.75 + 0.25·|60 − 81.875| = 11.0625 + 5.46875 = 16.53125.
Timeout = 81.875 + 4·16.53125 = 81.875 + 66.125 = 148 ms.
A TCP sender's TimeoutInterval is 1.25 s. The same segment times out twice in a row. What is the timeout value used for the next retransmission?
Show answer
After every timeout TCP doubles the interval (exponential backoff) and does not recompute it from the EWMA: 1.25 → 2.5 (after the 1st timeout) → 5 s (after the 2nd timeout). When a new ACK arrives, the value is again computed from the EWMA formula.
Verification: rwnd and sendable-byte calculations, round-by-round cwnd/ssthresh traces (a simulator written for Tahoe and Reno, using the rule cwnd = min(2·cwnd, ssthresh) in slow start), 0.75·W/RTT throughputs and EWMA/DevRTT/Timeout values were computed with node (verify/SEN2008/08.mjs).