Skip to content

TCP Reno

Slow start answered how a connection ramps up; what it deliberately left open was what happens once loss actually occurs, and how a connection tells the difference between "one segment got lost on an otherwise fine path" and "the whole path is in trouble." TCP Reno is the algorithm that answers that — it's the original, canonical congestion-avoidance scheme most people mean when they picture "classic TCP" reacting to loss, and every algorithm covered later in this module (CUBIC, BBR) exists specifically to improve on some limitation Reno has.

Congestion avoidance: the cautious phase after slow start

Once the congestion window reaches the slow-start threshold, Reno switches from slow start's exponential doubling to congestion avoidance: increase the congestion window by roughly one maximum segment size per full round trip, regardless of how many segments or acknowledgments that round trip contained. Where slow start effectively doubles the window every round trip, congestion avoidance adds a small, fixed increment — often described as additive increase. The practical effect is a connection that keeps probing for a little more available capacity, but slowly enough that if it does overshoot, the overshoot itself is small.

That deliberate slowness is the point. A connection near the actual limit of what the path can carry is walking a line between "not using all the capacity that's genuinely available" and "pushing past it and causing loss." Growing by a large amount per round trip would find that line faster, but overshoot it by more when it happens. Reno's congestion avoidance accepts slower discovery in exchange for a smaller, more controlled overshoot.

Reacting to loss: two signals, two very different responses

Congestion control already introduced the two ways a Reno-based connection detects loss — a retransmission timeout and duplicate ACKs — and flagged that they trigger meaningfully different reactions. Reno's specific behavior for each:

  • Duplicate ACKs (three, conventionally) trigger fast retransmit and fast recovery. The specific missing segment is resent immediately, without waiting for its timer to expire. The slow-start threshold is set to roughly half the amount of data that was in flight at the time (RFC 5681 formalizes this as half the flight size, not half the whole congestion window — a distinction worth getting right, since flight size can be smaller than the window if the sender wasn't using its full allowance). The congestion window is set to this new, lower threshold, and the connection resumes directly in congestion avoidance — no full restart of slow start.
  • A retransmission timeout triggers a full reset. This is treated as a much stronger signal that the network is genuinely struggling, not just that one segment got unlucky. The congestion window collapses all the way back down to its starting value, ssthresh is set the same way (roughly half the outstanding flight size), and the connection re-enters slow start from near zero, climbing back toward the newly lowered threshold before switching back to congestion avoidance once it arrives.

The shape that produces, plotted over time, is the classic sawtooth: window grows (exponentially in slow start, then linearly in congestion avoidance), loss is detected, the window drops sharply, and growth resumes from the lower point.

CWND
  |                     /\
  |                 /\ /  \
  |             /\ /  X    \
  |         /\ /    (loss,        \
  |     /\ /       ssthresh drops)  \
  |   / |
  +----------------------------------------→ time
      slow    congestion   loss    slow    congestion
      start   avoidance   event    start   avoidance

Each sawtooth peak is a loss event; each drop resets both the window and the threshold to a new, usually lower, ceiling — and the pattern repeats indefinitely as the connection continually probes for, and occasionally slightly exceeds, the path's real capacity.

The gap Reno's original design had, and what NewReno fixed

Reno's original fast-recovery algorithm assumed, implicitly, that only one segment would typically be lost per window. When exactly one segment is lost, one round of duplicate ACKs and one fast retransmit resolves it cleanly. But if multiple segments are lost within the same window — more likely exactly when the network is genuinely congested, which is precisely the scenario congestion control cares most about — original Reno's fast recovery could exit prematurely after recovering just the first lost segment, then fall through to a full timeout for the remaining ones anyway, largely losing the benefit fast recovery was meant to provide.

NewReno, specified in RFC 6582, fixes this with a more careful rule around partial acknowledgments — an ACK that acknowledges some, but not all, of the data that was outstanding when fast recovery began. Where original Reno treated any new ACK during recovery as reason to exit the recovery state, NewReno recognizes a partial ACK as evidence that at least one additional segment was also lost, and stays in fast recovery, retransmitting the next unacknowledged segment immediately rather than falling back to a timeout. This single refinement is why "Reno" in a modern context — including in the Linux kernel's default configuration path today — almost always actually means NewReno; the plain original algorithm is rarely deployed on its own anymore.

Reno's real limitation: it treats all loss as congestion

Reno's model has a foundational assumption baked into it: any packet loss means the network is congested, full stop. That assumption held up reasonably well on the network links TCP was originally designed around, but it breaks down on two increasingly common kinds of paths:

  • High-bandwidth, high-latency links — a transcontinental or satellite connection with a large bandwidth-delay product — where Reno's linear, one-segment-per-round-trip growth in congestion avoidance takes a very long time to climb back to a large window after any loss event, even though the path's actual capacity hasn't changed at all.
  • Wireless links, where a meaningful fraction of packet loss comes from radio interference or signal degradation rather than an actual congested buffer anywhere. Reno has no way to distinguish "lost because a router's buffer overflowed" from "lost because of a burst of Wi-Fi interference," and reacts identically — shrinking its window — even when the network genuinely had capacity to spare.

Both limitations come from the same root cause: Reno's only signal is loss, and loss is a blunt, binary, and sometimes misleading proxy for "the network is congested." TCP CUBIC addresses the first limitation directly, with a growth function purpose-built for exactly the high-bandwidth, high-latency case Reno handles poorly. TCP BBR goes further and largely abandons loss as the primary signal altogether, in favor of directly measuring bandwidth and round-trip time.

A company running database replication between an on-premises data center and a cloud region notices throughput that climbs steadily for tens of seconds after any network hiccup, then suddenly halves and starts climbing again — a repeating pattern, never settling at a stable rate even though the underlying link's actual capacity, confirmed separately with iperf3, is consistent and hasn't changed.

Capturing the replication connection during one of these dips shows a retransmission followed immediately by a sharp drop in the segments-per-round-trip the sender is willing to push — consistent with a Reno-style congestion window halving after loss, then climbing back linearly. Given that the link crosses a genuinely long, high-latency path (confirmed with ping, showing a round-trip time in the range of 120–150ms), the sawtooth's climb-back phase is unusually slow in absolute terms: congestion avoidance adds roughly one segment per round trip, and at 130ms per round trip, rebuilding a congestion window from a halved point back to its previous size can take many seconds even though nothing else on the path has changed.

This is the textbook symptom of Reno's high-bandwidth, high-latency limitation described above, not a sign of a flaky link. The relevant fix isn't a retry or a firewall change — it's checking, and likely changing, which congestion-control algorithm the sending host is actually using:

sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = reno

Confirm before changing a production host's congestion-control algorithm

Switching the default algorithm (commonly to cubic, covered next) is a per-host kernel setting, not a per-connection one — it affects every new TCP connection the host establishes afterward. Confirm the available algorithms first with sysctl net.ipv4.tcp_available_congestion_control, test the change on a non-critical host or during a maintenance window, and re-run the same throughput and latency checks afterward to confirm the sawtooth pattern has actually improved rather than assuming it has.

Practice exercises

  1. Explain, using the flight-size distinction in this article, why halving the slow-start threshold based on flight size rather than the full congestion window matters — construct a scenario where the sender's congestion window is larger than the amount of data actually in flight, and explain what would go wrong if ssthresh were set to half the window instead.
  2. A connection experiences two segment losses within the same round trip. Walk through, step by step, what original Reno's fast recovery would do versus what NewReno's partial-acknowledgment handling would do differently.
  3. Using the sawtooth diagram, explain why a Reno-based connection's average throughput over time is always somewhat below the network path's true peak capacity, even on a perfectly stable link with no external cause of congestion at all.
  4. In the database replication scenario, explain why confirming the link's capacity with iperf3 first was an important step before concluding the congestion-control algorithm was the culprit — what alternative explanation would that test have ruled out?

Sources