Loading learning content...
While Nagle's Algorithm addresses the sender's tendency to transmit small segments, it does nothing to prevent the receiver from advertising small windows. If the receiver constantly advertises that it can accept only a few bytes, even a well-behaved sender will be forced to transmit tiny segments. Clark's Algorithm completes the solution by establishing rules for when receivers should—and should not—advertise window updates.
David D. Clark, who also named the Silly Window Syndrome in RFC 813 (1982), proposed a receiver-side policy that prevents the pathological behavior at its source: don't advertise small window openings, because doing so invites the sender to fill them with small, inefficient segments.
By the end of this page, you will understand Clark's Algorithm, its mathematical thresholds, implementation details, how it coordinates with Nagle's Algorithm to prevent SWS from both directions, and the edge cases that require special handling.
Recall from our discussion of receiver-induced SWS that the problem originates when:
Why Does the Receiver Advertise Small Windows?
The default TCP behavior is to advertise the current available buffer space in every ACK. This design ensures the sender always has accurate flow control information. However, 'accurate' and 'useful' are different things.
Receiver State Available Space Advertised Window Problem
───────────────── ─────────────── ───────────────── ───────
Buffer full 0 bytes rwnd = 0 Sender blocked
App reads 1 byte 1 byte rwnd = 1 Tiny window!
Data arrives 0 bytes rwnd = 0 Sender blocked
App reads 1 byte 1 byte rwnd = 1 Tiny window!
The receiver is being 'too helpful'—immediately reporting every byte of available space. But this helpfulness causes the very problem it tries to avoid: instead of the sender being blocked until meaningful space is available, the sender transmits inefficient tiny segments.
This is a critical insight: the receiver's window advertisements directly control the sender's behavior. A receiver that advertises tiny windows forces even a well-optimized sender to transmit tiny segments. The receiver bears responsibility for not creating inefficiency through its advertisements.
The Core Insight:
The receiver should suppress window advertisements until it can offer a 'worthwhile' amount of space. The question becomes: what constitutes 'worthwhile'?
┌──────────────────────────────────────┐
│ App reads data from receive buffer │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Is new available space 'large'? │
└──────────────────────────────────────┘
/ \
Yes No
│ │
▼ ▼
┌────────────────┐ ┌────────────────────┐
│ Advertise new │ │ Continue reporting │
│ window size │ │ window = 0 (lie!) │
└────────────────┘ └────────────────────┘
The 'lie' in the second branch is intentional. The receiver has space, but chooses not to advertise it because advertising would trigger inefficient behavior. This is a case where 'technically correct' (advertising actual space) is worse than 'approximately correct' (pretending the buffer is still full).
Clark's Algorithm establishes a threshold below which window updates are suppressed. The receiver will not advertise a window larger than zero until it can offer 'enough' space.
The Threshold Definition:
As specified in RFC 1122 (Requirements for Internet Hosts), the receiver should delay advertising a non-zero window until either:
...whichever is smaller.
Mathematical Formulation:
Let:
available_space = current available buffer space
MSS = Maximum Segment Size (typically 1460 bytes for Ethernet)
buffer_max = maximum receive buffer size
threshold = min(MSS, buffer_max / 2)
Advertise window update when:
available_space >= threshold
Otherwise:
Continue advertising window = 0 (or last advertised small value)
The dual threshold handles both common cases: For large buffers (e.g., 64KB), waiting for MSS (1460 bytes) makes sense. For small buffers (e.g., 2KB), waiting for MSS would mean waiting until the buffer is 73% empty—too long. Half the buffer (1KB) is a reasonable trigger for small buffers.
Threshold Examples:
Scenario 1: Large buffer (65,535 bytes), Ethernet MSS (1460 bytes)
─────────────────────────────────────────────────────────────────
threshold = min(1460, 65535/2) = min(1460, 32767) = 1460 bytes
→ Don't advertise window until 1460+ bytes are free.
This ensures sender can transmit a full segment.
Scenario 2: Small buffer (4,096 bytes), Ethernet MSS (1460 bytes)
─────────────────────────────────────────────────────────────────
threshold = min(1460, 4096/2) = min(1460, 2048) = 1460 bytes
→ Still wait for MSS, as buffer is large enough.
Scenario 3: Very small buffer (512 bytes), Ethernet MSS (1460 bytes)
───────────────────────────────────────────────────────────────────
threshold = min(1460, 512/2) = min(1460, 256) = 256 bytes
→ Wait for 256 bytes (half buffer) since MSS is larger than buffer.
This is 50% of buffer—a meaningful amount.
| Buffer Size | MSS | Half Buffer | Threshold | % of Buffer |
|---|---|---|---|---|
| 65,535 bytes | 1,460 | 32,767 | 1,460 | 2.2% |
| 32,768 bytes | 1,460 | 16,384 | 1,460 | 4.5% |
| 8,192 bytes | 1,460 | 4,096 | 1,460 | 17.8% |
| 4,096 bytes | 1,460 | 2,048 | 1,460 | 35.6% |
| 2,048 bytes | 1,460 | 1,024 | 1,024 | 50% |
| 512 bytes | 1,460 | 256 | 256 | 50% |
Clark's Algorithm is implemented in the TCP stack's receive path. The key decision point is when generating an ACK: should we advertise the actual available space, or suppress the update?
Pseudocode Implementation:
class TCPReceiver:
def __init__(self, buffer_size, mss):
self.buffer_size = buffer_size
self.mss = mss
self.buffer = bytearray(buffer_size)
self.used = 0
self.last_advertised_window = buffer_size
def calculate_advertised_window(self):
"""
Implements Clark's Algorithm for window advertisement.
Returns the window size to advertise in the next ACK.
"""
available = self.buffer_size - self.used
threshold = min(self.mss, self.buffer_size // 2)
# If we previously advertised 0 (buffer was full)
if self.last_advertised_window == 0:
# Only advertise non-zero if above threshold
if available >= threshold:
self.last_advertised_window = available
return available
else:
# Continue advertising 0
return 0
else:
# Normal case: advertise actual available space
# (Only suppress when transitioning from 0)
self.last_advertised_window = available
return available
def receive_data(self, data):
# ... receive and buffer data ...
pass
def application_read(self, bytes_to_read):
# Application reads data, freeing buffer space
actual_read = min(bytes_to_read, self.used)
self.used -= actual_read
# The next ACK will use calculate_advertised_window()
return actual_read
Importantly, Clark's Algorithm only applies when the window is transitioning from zero (closed) to non-zero (open). During normal operation when the window is already open, the receiver advertises the actual available space. The suppression prevents initiating the SWS cycle, not normal flow control.
Linux Kernel Implementation (Simplified):
In the Linux kernel, this logic appears in tcp_select_window() in net/ipv4/tcp_output.c:
/* Simplified from Linux kernel */
static u32 tcp_select_window(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
u32 cur_free_space = tcp_receive_window(tp);
u32 mss = tp->mss_cache;
u32 half_buffer = tp->rcv_wnd >> 1; /* divide by 2 */
u32 threshold = min(mss, half_buffer);
/*
* If window was zero and available space is less than threshold,
* continue advertising zero to prevent SWS
*/
if (tp->rcv_wnd == 0 && cur_free_space < threshold) {
return 0;
}
return cur_free_space;
}
Window Update Triggers:
Beyond the threshold check, modern TCP stacks also consider:
Let's trace through a detailed scenario comparing behavior with and without Clark's Algorithm.
Scenario:
WITHOUT Clark's Algorithm:
Time Received Buffer Used Available Advertised Sender Action
──── ──────── ─────────── ───────── ────────── ─────────────
0ms 8192B 8192 0 rwnd=0 Blocked
10ms (app reads 100B) 100 rwnd=100 Send 100B segment
20ms 100B 8192 0 rwnd=0 Blocked
30ms (app reads 100B) 100 rwnd=100 Send 100B segment
40ms 100B 8192 0 rwnd=0 Blocked
50ms (app reads 100B) 100 rwnd=100 Send 100B segment
...
Result: 6 segments per 50ms, each 100 bytes. Efficiency: 100/140 = 71%. But we're generating 120 segments/second for only 12,000 bytes/second throughput.
Without Clark's Algorithm, we're transmitting at only 12 KB/s on a link capable of much more, using 120 segments/second. Each segment generates an interrupt on both endpoints, consuming CPU resources far beyond what the actual data transfer requires.
WITH Clark's Algorithm:
Time Received Buffer Used Available Threshold Met? Advertised Sender
──── ──────── ─────────── ───────── ────────────── ────────── ──────
0ms 8192B 8192 0 N/A rwnd=0 Blocked
10ms (app reads 100B) 100 No (100<1460) rwnd=0 Blocked
20ms (app reads 100B) 200 No (200<1460) rwnd=0 Blocked
...
150ms (app reads 100B) 1500 Yes (>=1460) rwnd=1500 Send 1460B
160ms 1460B 8152 40 No rwnd=0 Blocked
170ms (app reads 100B) 140 No rwnd=0 Blocked
...
310ms (app reads 100B) 1500 Yes rwnd=1500 Send 1460B
Result:
| Metric | Without Clark | With Clark | Improvement |
|---|---|---|---|
| Segments per second | 120 | ~7 | 17x fewer |
| Bytes per segment | 100 | 1460 | 14.6x larger |
| Wire efficiency | 71% | 97% | 1.4x better |
| CPU interrupts/sec | 120 | ~7 | 17x fewer |
| Data throughput | 12 KB/s | 12 KB/s | Same (app-limited) |
Key Observation:
The data throughput is unchanged—it's limited by the application's read rate, not the network. But the efficiency of that throughput is dramatically better. The network carries the same data with far fewer packets, consuming less bandwidth for headers and generating fewer interrupts.
This matters especially on shared networks or when the endpoints have limited CPU. Clark's Algorithm converts an inefficient stream of tiny packets into an efficient stream of full-sized segments without changing the application's behavior.
Clark's Algorithm (receiver-side) and Nagle's Algorithm (sender-side) are designed to work together. Each addresses one half of the SWS problem, and together they provide comprehensive protection.
The Complete Picture:
SENDER RECEIVER
────── ────────
┌─────────────────┐ ┌─────────────────┐
App writes ────▶│ Nagle's │ │ Receive buffer │
small data │ Algorithm │ │ │
│ │ │ │
│ IF outstanding │ Segment │ │
│ AND < MSS │─────────────▶│ Accept data │
│ THEN buffer │ │ │
│ ELSE send │ │ │
└─────────────────┘ └────────┬────────┘
▲ │
│ ▼
│ ┌─────────────────┐
│ ACK │ Clark's │
│◀───────────────────── │ Algorithm │
│ │ │
│ │ IF window < min │
│ │ (MSS, buf/2) │
│ │ THEN rwnd = 0 │
│ │ ELSE real rwnd │
│ └─────────────────┘
▲
│
App reads data
Think of Nagle and Clark as defense in depth. Even if one endpoint doesn't implement its algorithm correctly, the other provides protection. A Clark-compliant receiver protects against a non-Nagle sender. A Nagle-compliant sender is efficient even with a non-Clark receiver (though less so).
Interaction Timeline:
Time Sender (Nagle) Network Receiver (Clark)
──── ────────────── ─────── ────────────────
T0 App writes 10B
No outstanding data
→ SEND 10B segment ───10B────────▶ Buffered, rwnd=0
(buffer was full)
Outstanding: 10B
T1 App writes 10B App reads 10B
Outstanding data exists Available: 10B
→ BUFFER (wait for ACK) 10B < threshold
→ rwnd=0 (Clark)
Still waiting...
T2 App writes 10B App reads 10B
→ BUFFER (still waiting) Available: 20B
20B < threshold
T3 → rwnd=0
...
T15 App reads 10B
Available: 150B
Still < 1460
T16 ◀───ACK, rwnd=0──
ACK received! (no window yet)
Buffer has 160B
→ SEND 160B ──160B─────────▶ Buffered
Outstanding: 160B ◀──ACK, rwnd=1460 Available ≥ MSS!
(Clark advertises)
Why Both Are Needed:
| Scenario | Nagle Only | Clark Only | Both |
|---|---|---|---|
| Fast sender, slow receiver | Sender buffers, but receiver induces SWS | Receiver waits, sender sends tiny | ✓ Optimal |
| Slow sender, fast receiver | N/A (no SWS risk) | N/A | ✓ N/A |
| Interactive (typing) | ✓ Immediate | ✓ Immediate | ✓ Immediate |
| Bulk transfer | ✓ Efficient | ✓ Efficient | ✓ Efficient |
Clark's Algorithm handles most scenarios well, but certain edge cases require special consideration.
Edge Case 1: Very Small Buffers
When the receive buffer is smaller than MSS, the threshold becomes half the buffer:
Buffer = 512 bytes, MSS = 1460 bytes
Threshold = min(1460, 256) = 256 bytes
Problem: We'll never advertise more than 512 bytes, so the sender
can never send full MSS segments anyway.
Solution: This is a configuration issue. TCP with buffers smaller
than MSS is inherently inefficient. Modern systems use
buffers of 64KB or more.
Clark's Algorithm cannot compensate for undersized buffers. If your receive buffer is smaller than the MSS, you're limited to small segments regardless of any algorithm. Modern TCP stacks default to large buffers (often 128KB+) precisely to avoid this limitation.
Edge Case 2: Connection in Persist Mode
When the receiver advertises rwnd=0, the sender enters persist mode, periodically sending zero-window probes to check if space has opened up:
Sender Receiver
────── ────────
Data blocked (rwnd=0) Buffer full
App reads 100B (< threshold)
Don't advertise (Clark)
Zero Window Probe ─────────▶
◀─────────── ACK, rwnd=0 (still suppressed)
App reads 100B
Total: 200B (< threshold)
Zero Window Probe ─────────▶
◀─────────── ACK, rwnd=0
...
Total: 1500B (≥ threshold)
Zero Window Probe ─────────▶
◀─────────── ACK, rwnd=1500 (finally opened!)
Transmit data ─────────────▶
Important: Zero-window probes are essential when Clark's Algorithm is in effect. Without them, the sender might never learn that the window has opened (if no other ACKs are generated).
Edge Case 3: Urgent Data (URG flag)
TCP's urgent data mechanism complicates window management:
- Urgent data should be delivered immediately to the application
- This may free buffer space rapidly
- Clark's suppression might delay advertising this space
Modern Approach: URG is rarely used; most stacks treat urgent data
specially and may bypass normal window suppression.
Edge Case 4: Window Scale Factor (Large Windows)
With window scaling (RFC 7323), receive windows can be much larger than 64KB. The threshold calculation adapts:
# Window Scaling example: scale factor 7 (multiply by 128)
Actual buffer = 8 MB (8,388,608 bytes)
MSS = 1460 bytes
Half buffer = 4,194,304 bytes
Threshold = min(1460, 4194304) = 1460 bytes
→ Threshold is still MSS-based for large buffers.
The percentage becomes tiny (0.017%) but that's fine.
Clark's Algorithm is not merely a suggestion—it's a requirement for compliant TCP implementations as specified in RFC 1122 (Requirements for Internet Hosts).
RFC 1122 Section 4.2.3.3 — Window Management:
The receiver SHOULD NOT shrink the window, i.e., move the right window edge to the left.
The receiver SHOULD NOT advertise a zero window and then advertise a non-zero window of size less than MAX(MSS, rcv_buff/2).
The use of 'SHOULD NOT' indicates strong recommendation per RFC 2119. Implementations that violate these guidelines risk SWS and are considered non-conformant.
RFC 9293 (TCP Specification, 2022):
The latest TCP specification (replacing RFC 793) reinforces these requirements:
A TCP receiver SHOULD NOT shrink the window, i.e., move the right window edge to the left. However, a sending TCP peer MUST be robust against window shrinking, which may cause the "usable window" (see Section 3.8.6.2.1) to become negative.
A TCP implementation SHOULD implement the SWS avoidance algorithm for receivers...
rwnd=0, wait for threshold before advertising non-zeroTCP conformance test suites (like ANVL, Packetdrill) include tests for SWS avoidance. Production TCP stacks from all major operating systems (Linux, Windows, macOS, BSD) implement both Nagle and Clark algorithms. However, embedded or specialized stacks may have incomplete implementations.
Verification with Packet Capture:
To verify Clark's Algorithm compliance:
# Capture TCP handshake and window advertisements
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -w test.pcap
# Analyze window transitions
tshark -r test.pcap -T fields -e tcp.window_size_value | \
awk '{if(prev==0 && $1>0 && $1<1000) print "SWS: " $1; prev=$1}'
# If this prints small values, Clark's may not be implemented
A compliant implementation will never show window transitions like:
rwnd=0 → rwnd=50 → rwnd=0 → rwnd=75 → ...
Instead, you should see:
rwnd=0 → rwnd=0 → rwnd=0 → rwnd=1460 → rwnd=1380 → ...
Clark's Algorithm completes the defense against Silly Window Syndrome by addressing the receiver's role in the pathology.
What's Next:
We've covered both sender-side (Nagle) and receiver-side (Clark) SWS prevention. The next page examines Delayed Acknowledgments, a timing mechanism that reduces ACK traffic and works in concert with Nagle and Clark to optimize TCP efficiency.
You now understand Clark's Algorithm comprehensively—its threshold calculation, implementation, coordination with Nagle, edge cases, and RFC requirements. Next, we'll examine delayed acknowledgments and their role in the overall SWS prevention strategy.