1. Why This Topic Matters
The last post covered what's inside a TCP segment and how a connection opens and closes — but it never asked how big a segment is allowed to be, how many can be sent before the sender has to wait, or what stops a sender from overwhelming the network entirely. Those three questions turn out to be one continuous story: segment size (MTU/MSS/PMTUD) → how much data can be in flight based on the receiver (Flow Control) → how much data can be in flight based on the network itself (Congestion Control). This post walks through all three, in the order they actually build on each other.
2. Source Material
Video: MTU, MSS, and PMTUD
Video: TCP Flow Control
Video: TCP Congestion Control
3. What I Learned
MTU, MSS, PMTUD
MTU is a link-level ceiling on IP packet size; the Ethernet header sits outside it entirely.
MSS is TCP-only — the payload that fits once IP and TCP headers are subtracted from MTU (1500 → 1460 is the standard pair).
Fragmentation duplicates header overhead per fragment, which is exactly why TCP avoids it via MSS negotiation.
MSS negotiated at the handshake only reflects the two endpoints — not every router's MTU along the path. PMTUD closes that gap using the DF bit and ICMP "Fragmentation Needed" messages.
Flow Control
Stop-and-wait costs one round trip per segment; cumulative ACKs let several segments be sent and acknowledged together.
The receiver's buffer size is the advertised window — a full buffer means Window Size = 0, and a window update event (fresh ACK, non-zero window) tells the sender it's safe to resume.
The sender's sliding window is shaped directly by what the receiver last advertised.
The Window Size field is only 16 bits (max 65,535 bytes) — Window Scaling, negotiated at the handshake, multiplies it by a power of 2 for high-bandwidth links.
Congestion Control
Flow control only protects the receiver; congestion control protects the network — the sender's real window is
MIN(RWND, CWND).Slow Start doubles CWND every RTT until it hits ssthresh, then Congestion Avoidance takes over and grows it linearly instead.
A triple duplicate ACK (moderate signal) halves ssthresh and CWND, then resumes in Congestion Avoidance. An RTO (severe signal) halves ssthresh but resets CWND all the way to 1, restarting Slow Start.
ECN lets routers flag congestion before dropping packets, using ECN → ECE → CWR flags across the IP and TCP headers.
4. Key Concepts
MTU (Maximum Transmission Unit) — the largest IP packet a link can carry without fragmenting it: MTU = IP Header + TCP/UDP Header + Payload.
MSS (Maximum Segment Size) — the TCP payload that fits in one segment: MSS = MTU − IP Header − TCP Header (1500 − 20 − 20 = 1460).
PMTUD (Path MTU Discovery) — finds the smallest MTU across the entire path, not just the two endpoints.
Flow control — governs how much data the sender can have in flight, bounded by the receiver's buffer (the advertised window size ).
Sliding window — the sender's own buffer of unacknowledged data, resized to match the receiver's last advertised window.
Window Scaling — a handshake-negotiated multiplier (factor 0–14) that extends the window past the 16-bit field's 65,535-byte ceiling.
CWND (Congestion Window) — the maximum unacknowledged data TCP allows based on what the network can handle, distinct from the receiver-driven RWND.
ssthresh — the CWND value where TCP switches from exponential Slow Start growth to linear Congestion Avoidance growth.
ECN (Explicit Congestion Notification) — router-to-sender congestion warning that happens before any packet loss.
5. How It Works
Segment Sizing: Fragmentation and PMTUD
With MTU = 1500 bytes but a 1600-byte packet to send (20-byte header + 1580 payload):
Fragment 1: IP Header 20B + Payload 1480B = 1500B (matches MTU exactly)
Fragment 2: IP Header 20B + Payload 100B = 120B (duplicated header for the leftovers)
MSS negotiation avoids this at the source. With MSS = 1460 negotiated at the handshake, a 3000-byte send splits into 1460 + 1460 + 80 — Segment 1 checks out as 1460 + 20 + 20 = 1500, landing exactly at the MTU. Skip that negotiation and send 1500 raw payload bytes instead, and the final packet becomes 1500 + 20 + 20 = 1540, blowing past the MTU and forcing fragmentation anyway.
When endpoints agree on MSS but a router in between has a smaller MTU, PMTUD finds it:
Client(1500) ── R1(460) ── R2(1420) ── R3(512) ── Server(1500)
1. Client sends 1500B with DF (Don't Fragment) set.
2. R1's link MTU (460) is smaller → can't fragment (DF set) → drops packet,
replies with ICMP "Fragmentation Needed, MTU = 460."
3. Client resizes to 460B and resends.
4. 460B fits under R2 (1420) and R3 (512) too → reaches the server.
5. Discovered Path MTU = 460 bytes.
Flow Control: Pacing to the Receiver
Stop-and-Wait: 3 segments → 3 round trips
Cumulative ACK: 3 segments sent back-to-back → 1 round trip (single ACK covers all 3)
Receiver buffer = 3 bytes. Sender fills it with Seq 1, 2, 3:
Server sends ACK, Window Size = 0 (buffer full)
Client stops sending
...app reads Seq 1–3, freeing the buffer...
Server sends ACK + Window Size = 3 (window update event)
Client resumes sending
The sender's sliding window tracks this directly — window shrinks to 0 when the receiver is full, and reopens the moment a window update arrives. And because the Window Size field is only 16 bits (max 65,535 bytes), high-bandwidth links negotiate Window Scaling at the handshake:
Advertised Window = 65,000 bytes, Scaling Factor = 8
Effective Window = 65,000 × 2^8 = 65,000 × 256 ≈ 16,640,000 bytes (~16 MB)
Congestion Control: Pacing to the Network
The receiver's window alone isn't enough — a router in the middle might have less buffer than either endpoint. Assume MSS = 1 byte, RTT = 1 sec:
Sender buffer: 4 segments | Router buffer: 2 segments | Receiver buffer: 4 segments
Sending 4 segments based only on the receiver's window overflows the router's 2-segment buffer, and the excess gets dropped. Hence: Sender Window = MIN(RWND, CWND).
Slow Start , with ssthresh = 64:
CWND: 1 → 2 → 4 → 8 → 16 → 32 → 64 (doubles every RTT until it hits ssthresh)
Congestion Avoidance then takes over: CWND = CWND + 1/CWND, applied once per RTT — versus Slow Start's +1 per single ACK.
Reacting to loss , starting from CWND = 64:
Triple Duplicate ACK (moderate): RTO (severe):
new ssthresh = 64 / 2 = 32 new ssthresh = 64 / 2 = 32
new CWND = 64 / 2 = 32 new CWND = 1 (full reset)
→ resumes in Congestion Avoidance → restarts from Slow Start
ECN , detecting congestion before any loss:
1. Router nearing overload → sets ECN flag = 1 in the IP header (instead of dropping)
2. Receiver sees it → sets ECE = 1 in its next ACK
3. Sender sees ECE = 1 → halves ssthresh and CWND, moves into Congestion Avoidance
4. Sender sets CWR = 1 on its next segment, confirming the reduction to the receiver
6. Things That Confused Me
I expected Ethernet framing to count toward MTU, since it's physically part of what goes out on the wire — it doesn't; MTU is strictly an IP-layer number. I also assumed Window Scaling somehow expanded the Window Size field itself — it doesn't; the field stays 16 bits forever, and scaling just changes how both sides interpret the number, agreed once at the handshake. And I expected RTO and triple duplicate ACK to trigger the same reaction, since both mean "something got lost" — they don't. A triple duplicate ACK means later segments are still arriving, so it's treated as moderate congestion. An RTO means nothing got through at all, which is why TCP throws away all its progress and restarts from CWND = 1 instead of just halving it.
7. My Explanation in Simple Words
Think of sending data as shipping packages down a delivery route. MTU is the biggest box any truck on the route can carry, and MSS is how much you're allowed to pack inside once you've set aside room for the shipping label (the headers). PMTUD is sending a test package first to find the smallest box size used by any truck along the whole route, so you never have to repack halfway.
Flow control is pouring water into a cup while watching how full it already is — stop when it's full, resume once someone's had a sip. Congestion control is watching the pipe between you and the cup, not just the cup — even if the cup has room, you slow down if the pipe itself is straining. You start pouring cautiously and speed up quickly (Slow Start), then ease off your rate of speeding up once you sense the pipe filling (Congestion Avoidance). If you hear it actually overflowed somewhere (a timeout), you go back to pouring from scratch. ECN is the pipe tapping you on the shoulder to say "getting full" before anything actually spills.
8. Key Takeaways
MTU is the link-level packet ceiling; MSS is TCP's payload budget after headers; PMTUD finds the smallest MTU across the whole path so fragmentation never has to happen mid-route.
Flow control paces the sender to the receiver's buffer — a full buffer means Window Size = 0, and a window update event reopens it; Window Scaling extends the 16-bit field's 65,535-byte ceiling for high-bandwidth links.
Congestion control paces the sender to the network's capacity —
Sender Window = MIN(RWND, CWND)— using Slow Start's exponential growth up to ssthresh, then Congestion Avoidance's linear growth.Triple duplicate ACK (moderate loss signal) halves the window and resumes in Congestion Avoidance; RTO (severe signal) resets CWND to 1 and restarts Slow Start entirely.
ECN lets the network warn the sender before any packet is actually dropped, via ECN → ECE → CWR across the IP and TCP headers.
9. Next Topic
Next up: DNS — how a name typed into a browser turns into the IP address that everything in this post has been routing to.
Top comments (0)