Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT

TCP Deep Dive

A complete exploration of TCP — from the 3-way handshake and sequence number mechanics to congestion control algorithms, flow control, TIME_WAIT, TCP options, performance tuning, and the subtle failure modes that make TCP connections mysteriously hang.

28–38 min May 2026

// CHAPTER 01

The Contract That Makes the Internet Work

// REAL-WORLD SCENARIOThe internet's physical infrastructure drops packets. Routers get congested and discard frames. Optical links have bit error rates. Switches lose packets during buffer overflow. IP itself is explicitly defined as "best effort" — it makes no delivery guarantees whatsoever.

Yet when you download a file, you get every byte in order with no corruption. When you stream video, the player does not stutter from random reordering. When you send an email, it arrives complete.

This reliability is not a property of the network — it is a property of TCP. TCP builds a reliable, ordered, bidirectional byte stream on top of an unreliable packet network. It does this by tracking every byte sent, acknowledging every byte received, retransmitting what was lost, and ordering what arrived out of sequence. TCP is the contract that transforms an unreliable network into a reliable data transport.

TCP (Transmission Control Protocol) is defined in RFC 793 (1981) with significant extensions in RFC 1122, RFC 2581, RFC 5681, and many others. It provides: reliability (guaranteed delivery via acknowledgment and retransmission), ordering (sequence numbers ensure bytes are delivered in transmission order), flow control (receive window prevents sender from overwhelming receiver), congestion control (adaptive sending rate prevents network saturation), and error detection (checksum over header and data).

50 Years Unchanged — TCP's Enduring Design
TCP was designed in 1974 by Vint Cerf and Bob Kahn for a network of perhaps a few hundred nodes. Yet the same core protocol — with only modest extensions — now carries petabytes per second across a global network of billions of devices. No redesign, no replacement, no breaking change in 50 years. The congestion control algorithms added in the 1980s by Van Jacobson still run on every TCP implementation today.

TCP vs. UDP — When to Choose Each

TCP's reliability comes at a cost: latency, complexity, and head-of-line blocking. Choosing between TCP and UDP requires understanding these trade-offs:

Use TCP: HTTP, HTTPS, email (SMTP/IMAP/POP), file transfers, database connections, anything where correctness matters more than latency.

Use UDP: DNS queries (short request-response, timeout-and-retry is sufficient), video streaming (a dropped frame is better than pausing to retransmit), games (old state is worthless, just send new state), DHCP.

Use QUIC (UDP-based with TLS): HTTP/3, modern video conferencing (WebRTC data channels). QUIC recovers TCP's reliability in user space while eliminating head-of-line blocking.

// CHAPTER 02

The Three-Way Handshake

// REAL-WORLD SCENARIOTwo processes on different machines want to exchange data. Before a single byte of application data can flow, they need to agree on: starting sequence numbers (so they can detect reordering and track delivery), initial window sizes (so neither side overwhelms the other), and TCP options (MSS, window scaling, SACK). All of this happens in three packets — the three-way handshake.

The three-way handshake is elegant because it solves the two-army problem: how do you get two parties to agree on a shared state when messages can be lost? The answer: you need three messages minimum. Two is not enough — one side can't know if the other received the final confirmation. With three messages, both sides have sent and received a confirmation.

TCP Connection Lifecycle Simulator

Step through the 3-way handshake (connect) or 4-way teardown (close) with sequence numbers and state transitions.

Step 1: SYNClient → Server

FLAGS

SYN

SEQ

ISN_c = 1000 (random)

ACK

0 (none)

CLIENT STATE

SYN_SENT

SERVER STATE

LISTEN → SYN_RECEIVED

Client initiates connection. Picks a random Initial Sequence Number (ISN) — in practice a 32-bit random value. The SYN flag signals "I want to establish a connection." No data yet.

SYN Cookies and SYN Flood Defense

A SYN flood attack sends thousands of SYN packets per second with spoofed source IPs. The server responds to each with SYN-ACK and creates a half-open connection entry, consuming memory. If enough SYN-ACKs are sent with no ACK completing the handshake, the server's connection table fills and legitimate connections are rejected.

SYN cookies (RFC 4987) eliminate the need to store state for half-open connections. Instead of storing connection state after receiving a SYN, the server encodes all necessary connection information (client IP, port, ISN, MSS, timestamp) in the initial sequence number of the SYN-ACK. When the legitimate ACK arrives, the server decodes the ISN to reconstruct the connection. No state stored, no memory exhaustion — SYN flood mitigation without resource consumption.

# Check SYN cookie status (Linux)
sysctl net.ipv4.tcp_syncookies          # Should be 1 (enabled)

# Monitor SYN flood activity
netstat -an | grep SYN_RECV | wc -l    # Count half-open connections
ss -n state syn-recv | wc -l           # Alternative

# Full connection state counts
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

# tuning
sysctl -w net.ipv4.tcp_max_syn_backlog=65536   # Increase SYN backlog queue
sysctl -w net.ipv4.tcp_synack_retries=2         # Reduce SYN-ACK retries (flood mitigation)

// CHAPTER 03

TCP Header — Every Bit Counts

// REAL-WORLD SCENARIOThe TCP header is 20 bytes minimum — less than one millisecond to transmit on a 1 Gbps link. Yet those 20 bytes contain the entire machinery for reliable delivery: sequence tracking, acknowledgment, flow control, connection state, and checksum. Every field is load-bearing. Understanding each field is the difference between reading a packet capture like a book and seeing random hex.

TCP Header Field Inspector

Click any field in the header layout to learn its purpose and operational significance.

Source Port

16 bits

54321

Destination Port

16 bits

443

Sequence Number

32 bits

0x3A1B2C4D

Acknowledgment Number

32 bits

0x1D2E3F50

Data Offset

4 bits

5 (20 bytes)

Flags

9 bits

ACK | PSH

Window Size

16 bits

65535 (× scale)

Checksum

16 bits

0x1A2B

Options

Variable (0–40B)

MSS=1460, WScale=7, SACK

Click any field above to inspect it

TCP Flags Deep Dive

TCP flags occupy 9 bits in the header. The operationally significant flags:

SYN: synchronize sequence numbers. Only set during connection establishment. A SYN with ACK is the server's half of the handshake.

ACK: acknowledgment number is valid. Set in virtually every packet after the initial SYN. The absence of ACK in a non-SYN packet indicates something unusual.

FIN: no more data from sender. Initiates graceful close. Both sides must send FIN to fully close.

RST: reset — abort connection immediately. No graceful close. Used when a packet arrives for a closed port, when the connection is aborted due to error, or explicitly by applications using SO_LINGER with timeout=0.

PSH: push data to application immediately without buffering. Used for interactive applications (SSH, telnet) where each keystroke should be delivered immediately, not wait for a full buffer.

URG: urgent pointer field is significant. Rarely used in modern protocols — superceded by application-layer priority mechanisms. Old telnet break signal used this.

ECE + CWR: Explicit Congestion Notification (ECN). When a router experiences congestion, it sets the ECN codepoint in the IP header. The receiver echoes this to the sender via ECE. The sender confirms action taken via CWR. This avoids packet loss as the congestion signal, improving performance.

// CHAPTER 04

Sequence Numbers and Reliability

// REAL-WORLD SCENARIOTCP's reliability mechanism is built on one key insight: every byte in the data stream has a unique number. By numbering bytes, not packets, TCP can handle packet fragmentation, reordering, and loss transparently. A sender can retransmit a lost segment. A receiver can reorder out-of-sequence segments. The application layer sees a clean byte stream — the network messiness is completely hidden.

The Initial Sequence Number (ISN) is the starting point for each direction's byte numbering. Modern OSes choose ISNs using a time-based pseudo-random algorithm (RFC 6528: ISN = MD5(src_ip, src_port, dst_ip, dst_port, secret) + clock_offset). This prevents TCP sequence prediction attacks where an attacker could inject data into an existing connection by guessing the sequence number.

Cumulative vs. Selective Acknowledgment

Basic TCP uses cumulative acknowledgment: ACK=N means "I have received all bytes up to N-1 successfully." If segment 1001–2000 arrives but 2001–3000 is lost, ACK=1001 is sent. When 3001–4000 arrives (out of order), ACK=1001 is still sent (three duplicate ACKs). The sender must retransmit from 2001 onward — even though 3001–4000 was received.

SACK (Selective Acknowledgment, RFC 2018) allows the receiver to inform the sender exactly which segments are received and which are missing. The SACK option contains block pairs (left_edge, right_edge) for each out-of-order segment received. The sender can retransmit only the specific missing segments — not everything after the loss. This dramatically improves performance over lossy links (Wi-Fi, satellite, mobile).

# Check TCP options negotiated in a connection (Linux)
ss -ti dst 8.8.8.8       # Show TCP internals: cwnd, ssthresh, retrans, RTT, MSS, SACK

# Example output from ss -ti:
# cubic wscale:7,7 rto:204 rtt:4.121/1.052 ato:40 mss:1448 pmtu:1500 rcvmss:1448
# rcvbuf:131072 sndbuf:87380 lastsnd:68 lastrcv:68 lastack:68
# pacing_rate 36.8Mbps delivery_rate 25.2Mbps unacked:0 retrans:0/0 dsack_dups:0
# rcv_space:14480 rcv_ssthresh:64448 minrtt:3.5

# Check SACK is enabled
sysctl net.ipv4.tcp_sack           # Should be 1

# Capture SACK options in tcpdump
tcpdump -i eth0 'tcp[tcpflags] & tcp-ack != 0' -vvv | grep SACK
Disabling TCP timestamps also disables PAWS sequence number protectionThe PAWS (Protection Against Wrapped Sequence Numbers) mechanism uses TCP timestamps (RFC 7323) to prevent old duplicate segments from being accepted when sequence numbers wrap around. At 10 Gbps, a 32-bit sequence number wraps in ~3.4 seconds. Without PAWS, a delayed segment from a previous connection could arrive and corrupt the current stream. PAWS uses the timestamp option to detect and discard these wrapped duplicates. Disabling TCP timestamps (net.ipv4.tcp_timestamps=0) disables PAWS — safe only on networks with RTTs > wrap-around period (nearly impossible at high bandwidth).

// CHAPTER 05

Flow Control and the Receive Window

// REAL-WORLD SCENARIOA 1 Gbps server is sending data to a 10 Mbps client. Without flow control, the server would blast data a hundred times faster than the client can process it. The client's receive buffer would fill, overflow, and start dropping packets — causing the server to retransmit, making the situation worse.

TCP's receive window solves this by allowing the receiver to tell the sender exactly how much buffer space it has available. The sender cannot transmit more than window bytes of unacknowledged data. As the receiver's application reads data from the buffer, it increases the window advertisement. If the receiver's buffer fills, the window shrinks to zero — the sender must pause.

The receive window (rwnd) is a 16-bit field in the TCP header — originally limiting maximum window size to 65,535 bytes (65 KB). On modern networks with 100+ ms round-trip times, the bandwidth-delay product can be hundreds of megabytes — far exceeding 65 KB. Enter TCP Window Scaling (RFC 7323): negotiated during the handshake via a scale factor (0–14 bits), making the effective window up to 1 GB (65535 × 2¹⁴).

The Zero Window condition occurs when rwnd = 0. The sender pauses. The receiver sends a Window Update (pure ACK with non-zero rwnd) when space becomes available. If this update is lost, the sender waits indefinitely — deadlock. TCP prevents this with the Persist Timer: the sender periodically sends a Window Probe to check if the window has reopened.

Nagle Algorithm and PSH

The Nagle algorithm (RFC 896) buffers small writes: if there is unacknowledged data in flight, hold small new segments until either the buffer fills to MSS or all previous data is acknowledged. This coalesces many small writes (interactive typing) into fewer larger segments, dramatically improving efficiency. Side effect: latency. For interactive applications (SSH, gaming), disable Nagle with TCP_NODELAY socket option.

# Disable Nagle algorithm in code (Go example)
conn, _ := net.Dial("tcp", "server:port")
tcpConn := conn.(*net.TCPConn)
tcpConn.SetNoDelay(true)   // TCP_NODELAY — disables Nagle

# Python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

# Check Nagle on existing connections
ss -ti | grep nodelay   # Shows if TCP_NODELAY is set

# Diagnose delayed ACK + Nagle interaction (a common performance problem)
# Symptom: 40ms delays on small write-read interactions
# Cause: sender holds small write (Nagle), receiver holds ACK (delayed ACK timer 40ms)
# Fix: TCP_NODELAY on sender OR TCP_QUICKACK on receiver (or both)

// CHAPTER 06

Congestion Control — TCP's Self-Regulation

// REAL-WORLD SCENARIOIn 1986, the internet experienced its first congestion collapse. TCP at the time had no mechanism to back off when the network was congested. Routers dropped packets. TCP senders retransmitted. More retransmissions created more congestion. Throughput fell by a factor of 1000 on some paths.

Van Jacobson at Lawrence Berkeley Laboratory had 32 kbps of leased line to the internet. On a good day, it ran at 32 kbps. During congestion collapse, he measured 40 bps — 800× degradation. He went home that weekend and invented TCP congestion control. By Monday, throughput had recovered to 32 kbps. The same algorithms run on every TCP implementation today.

TCP congestion control is the mechanism by which senders adapt their transmission rate to avoid overloading the network. It uses packet loss and ECN as congestion signals, and maintains a congestion window (cwnd) that limits how much data can be in flight. The actual sending rate is limited by min(cwnd, rwnd).

TCP Congestion Control Visualizer

Step through cwnd evolution: Slow Start → Congestion Avoidance → Fast Recovery → Timeout recovery.

ssthresh=16
RTT (time) →     Click a bar to inspect
Slow StartRTT #1

cwnd=1 (doubles per RTT)

Congestion Control Phases

Slow Start: begins at cwnd=1 MSS. For each ACK received, cwnd increases by 1 MSS (exponential growth). Continues until cwnd reaches ssthresh (slow start threshold) or packet loss occurs. Despite the name, this is the fastest growth phase.

Congestion Avoidance: once cwnd reaches ssthresh, growth becomes additive — 1 MSS per RTT (linear). This is the AIMD (Additive Increase, Multiplicative Decrease) algorithm: cautious probing for available bandwidth.

Fast Retransmit / Fast Recovery: three duplicate ACKs signal a lost segment (not timeout — the network is still delivering later segments). ssthresh = cwnd/2. cwnd = ssthresh + 3. Retransmit the lost segment. Resume from Congestion Avoidance at ssthresh — not Slow Start. This avoids the performance penalty of dropping to cwnd=1.

Timeout: retransmission timer expires — much more severe signal. ssthresh = cwnd/2. cwnd = 1 MSS. Restart Slow Start from scratch.

Modern Congestion Control Algorithms

Classic Reno and CUBIC are loss-based: they reduce cwnd only when packet loss occurs. This works well on wired networks but is aggressive on shared links and slow to converge on high-bandwidth links (100G+ WANs):

CUBIC (Linux default): uses a cubic function to grow cwnd, allowing faster recovery from loss events on high-bandwidth-delay product (BDP) networks. Standard on Linux since kernel 2.6.19.

BBR (Bottleneck Bandwidth and RTT): Google's delay-based algorithm. Instead of reacting to loss, BBR models network state (bandwidth and RTT) and sends at the estimated optimal rate. Dramatically improves performance on lossy links (mobile, intercontinental). Enabled on YouTube's servers since 2016.

QUIC's congestion control: QUIC (HTTP/3) implements congestion control in user space, allowing per-connection algorithm selection. Different connections from the same app can use different algorithms simultaneously.

# Check and change congestion control algorithm (Linux)
sysctl net.ipv4.tcp_congestion_control       # Show current algorithm
sysctl net.ipv4.tcp_available_congestion_control  # Show available algorithms

# Switch to BBR
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq          # BBR works best with fair queueing

# Per-connection in code (Linux):
# setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "bbr", strlen("bbr") + 1)

# Monitor congestion window live
watch -n 0.5 'ss -ti | grep -A1 ESTABLISHED'

// CHAPTER 07

Retransmission and RTO

// REAL-WORLD SCENARIOTCP sends a segment and starts a timer. If no ACK arrives before the timer expires, it retransmits. Simple in concept — but setting the timer correctly is one of the hardest problems in distributed systems. Set it too short and you retransmit unnecessarily, wasting bandwidth. Set it too long and you wait too long after a loss, wasting time.

The solution is adaptive measurement. TCP measures the RTT of each segment (using timestamps or manual timing), computes a smoothed RTT estimate (SRTT), tracks variance (RTTVAR), and sets the timeout as SRTT + 4 × RTTVAR. This Jacobson algorithm adapts to changing network conditions automatically — a key insight from 1988 that remains state-of-the-art.

The Retransmission Timeout (RTO) is computed using the Jacobson algorithm:

SRTT = (1 - α) × SRTT + α × RTTsample (α = 1/8)

RTTVAR = (1 - β) × RTTVAR + β × |RTTsample - SRTT| (β = 1/4)

RTO = SRTT + max(G, 4 × RTTVAR) (G = clock granularity, typically 1ms)

On each retransmission, RTO is doubled (exponential back-off) up to a maximum (typically 60–120 seconds). This prevents retransmission storms during severe congestion. The RTO resets when a segment is successfully acknowledged.

⏱️Karn's Algorithm and TCP Timestamps
TCP Timestamps option (RFC 7323) enables precise per-segment RTT measurement. Without timestamps, TCP can only measure RTT from ACK timing — which is ambiguous for retransmitted segments (Karn's Algorithm: don't update RTT estimate for retransmitted segments, since you don't know if the ACK is for the original or the retransmission). Timestamps uniquely identify each segment, resolving the ambiguity and allowing precise RTT measurement for every segment including retransmits.

// CHAPTER 08

TIME_WAIT — The State Everyone Wants to Fix

// REAL-WORLD SCENARIOA high-traffic load balancer processes 50,000 connections per second. Each connection after close enters TIME_WAIT for 60–120 seconds (2 × MSL, Maximum Segment Lifetime). At steady state, the load balancer has 3–6 million sockets in TIME_WAIT. The OS runs out of ephemeral ports. New connections fail with "Address already in use." The operations team wants to reduce TIME_WAIT to 5 seconds to fix the problem. This makes things worse.

TIME_WAIT exists for a reason. Eliminating it or reducing it too aggressively introduces subtle, catastrophic bugs.

TIME_WAIT serves two purposes:

1. Delayed segment absorption: delayed segments from the closed connection could arrive after a new connection reuses the same 4-tuple. TIME_WAIT (2 × MSL) ensures all delayed segments from the old connection have expired before the 4-tuple can be reused, preventing them from corrupting the new connection's data stream.

2. Reliable FIN-ACK delivery: the final ACK may be lost. The passive closer (server) retransmits its FIN. The active closer must be in TIME_WAIT to respond with ACK — if it were in CLOSED, it would send RST, confusing the server.

TIME_WAIT Reduction Techniques

TCP_REUSE (net.ipv4.tcp_tw_reuse=1): allow reuse of TIME_WAIT sockets for new outbound connections when safe (requires TCP Timestamps to disambiguate segments). Safe to enable for outbound connections on busy servers.

SO_REUSEADDR: allows binding to a port that has sockets in TIME_WAIT. Necessary for server restart without waiting for TIME_WAIT expiry.

Architectural: use connection pooling so connections are reused rather than closed and reopened. The best fix is fewer connection close events.

tcp_tw_recycle is removed from Linux — it breaks NAT'd connectionstcp_tw_recycle (net.ipv4.tcp_tw_recycle) was removed from Linux kernel 4.12. It caused connection failures for NAT'ed clients (multiple clients behind NAT share the same public IP, so their timestamps appear to go backwards from the server's perspective). Never use it — the feature was fundamentally broken. Use tcp_tw_reuse instead, which is safe because it only reuses TIME_WAIT sockets for NEW connections, not for the same 4-tuple.

// CHAPTER 09

TCP Performance Tuning

// REAL-WORLD SCENARIOA cloud storage application is transferring files between two servers 50ms apart (New York to London). The measured throughput is 5 Mbps on a 1 Gbps link — 0.5% utilization. The engineer assumes packet loss. But packet loss is zero. The bottleneck is the receive window: 65,535 bytes / 0.05 seconds = ~10 Mbps theoretical maximum. The application uses default socket buffer sizes. By increasing the socket receive buffer to 4 MB, throughput jumps to 80 Mbps. A single parameter change, a 16× improvement.

TCP throughput is bounded by the bandwidth-delay product (BDP): the maximum data "in flight" at any moment. For a 1 Gbps link with 100ms RTT, BDP = 1,000,000,000 bits/second × 0.1 seconds = 100 Mb = 12.5 MB. The TCP window must be at least 12.5 MB to fully utilize the link. Default Linux socket buffers (4 MB) cannot saturate a 1 Gbps intercontinental path.

# TCP buffer tuning for high-BDP paths (Linux)
# View current settings
sysctl net.ipv4.tcp_rmem              # [min, default, max] receive buffer
sysctl net.ipv4.tcp_wmem              # [min, default, max] send buffer

# Increase for high-bandwidth, high-latency paths
sysctl -w net.ipv4.tcp_rmem="4096 131072 67108864"   # max 64 MB receive
sysctl -w net.ipv4.tcp_wmem="4096 65536 67108864"    # max 64 MB send
sysctl -w net.core.rmem_max=67108864
sysctl -w net.core.wmem_max=67108864

# Enable auto-tuning (default on modern Linux — should already be on)
sysctl -w net.ipv4.tcp_moderate_rcvbuf=1

# Enable TCP window scaling (should be default)
sysctl -w net.ipv4.tcp_window_scaling=1

# Calculate required buffer for your BDP:
# bandwidth_bits/s × RTT_seconds / 8 = bytes
# Example: 10 Gbps × 0.1s / 8 = 125 MB minimum buffer for full utilization

TCP Offload — Moving Work to Hardware

Modern NICs offload TCP processing from the CPU:

TSO (TCP Segmentation Offload): the kernel hands the NIC a large buffer; the NIC splits it into MSS-sized segments and computes checksums. Saves CPU cycles for segmentation and checksum calculation.

GRO (Generic Receive Offload): the NIC aggregates small incoming segments into larger buffers before passing to the kernel. Reduces per-packet interrupt overhead.

RSS (Receive Side Scaling): distributes incoming connections across multiple CPU cores using hardware hashing, enabling multi-core TCP processing.

// CHAPTER 10

TCP Options — The Protocol Extension Mechanism

// REAL-WORLD SCENARIOTCP Options are the evolutionary mechanism that has kept TCP relevant for five decades. The base protocol from 1981 has 20 bytes of fixed header. Options in the remaining 40 bytes have enabled window scaling, SACK, timestamps, fast open, multipath, authentication, and dozens of other features — all while remaining backward compatible with implementations from 1981 that ignore options they don't understand.

Key TCP options and their operational importance:

MSS (Maximum Segment Size, Option 2): each side advertises the maximum segment it can receive in the SYN. Default TCP MSS = 536 bytes; Ethernet default = 1460 bytes (1500 MTU - 20 IP - 20 TCP). MSS is NOT negotiated — each side independently declares its limit; the sender uses the minimum.

Window Scale (Option 3): scale factor for the window field. Negotiated in SYN/SYN-ACK only. If one side doesn't include it, window scaling is disabled for the connection. Always present on modern systems.

SACK (Option 4 — SACK Permitted) + Option 5 (SACK Blocks): SACK Permitted advertised in SYN/SYN-ACK. SACK blocks (up to 4 ranges) carried in ACKs to report out-of-order receipt. Critical for performance over lossy links.

Timestamps (Option 8): TSval (timestamp value) and TSecr (timestamp echo reply). Enables precise RTT measurement, PAWS protection, and improved retransmission decisions.

TCP Fast Open, TFO (Option 34): allows data to be sent in the SYN packet on subsequent connections, eliminating one RTT of setup latency. Uses a cookie mechanism to prevent SYN data amplification.

Multipath TCP, MPTCP (RFC 8684): multiple subflows over different paths (e.g., WiFi + cellular), transparent to applications. Used on iOS for Siri and FaceTime for seamless handoff between networks.

// CHAPTER 11

TCP Connection Failures and Debugging

// REAL-WORLD SCENARIOAn application connects to a database. The connection succeeds. It sends a query. Silence. 30 seconds later: "connection timeout." The database is running. The network is up. Ping works. Ports are open. What happened?

A packet capture reveals the answer: the query packet (1500 bytes with DF bit) reaches a VPN tunnel interface with MTU 1400. The VPN gateway sends ICMP Fragmentation Needed back to the application server — but the firewall between the app server and VPN gateway blocks all ICMP. The app server never learns about the MTU constraint. It keeps sending 1500-byte packets that silently disappear at the VPN gateway. The database never receives the query.

MTU mismatch + ICMP filtering = the invisible silent killer of TCP connections.
# TCP connection debugging toolkit

# 1. Check connection state
ss -tn dst 10.0.0.5:5432               # Specific connection state
ss -tn state established               # All established connections
ss -tn state syn-sent                  # Connections waiting for SYN-ACK (connect timeout)
ss -tn state time-wait | wc -l         # TIME_WAIT count (high = rapid connection cycling)

# 2. Check TCP retransmissions (high = packet loss or MTU issue)
ss -ti dst 10.0.0.5 | grep retrans
netstat -s | grep -i retran
cat /proc/net/snmp | grep Tcp

# 3. Capture the problem
tcpdump -i eth0 -w /tmp/capture.pcap 'host 10.0.0.5 and port 5432'
# Look for: retransmissions (same seq twice), zero window, RSTs

# 4. Check MTU on the path
ping -M do -s 1452 10.0.0.5           # Test 1480 byte packets
tracepath 10.0.0.5                     # Shows MTU changes at each hop

# 5. Check kernel TCP error counters
netstat -s | grep -E "failed|reset|error|timeout"

# 6. Watch real-time TCP events (Linux ftrace)
echo 1 > /sys/kernel/debug/tracing/events/tcp/enable

// CHAPTER 12

TCP in Modern Applications

// REAL-WORLD SCENARIOHTTP/1.1 reuses connections (keep-alive), but sends one request at a time. HTTP/2 multiplexes streams — dozens of requests in parallel over a single TCP connection. HTTP/3 runs over QUIC (UDP) to eliminate TCP's head-of-line blocking. Each generation is a response to TCP limitations becoming bottlenecks at larger scale.

Understanding TCP helps you understand why HTTP/2 was built, why QUIC was necessary, and what trade-offs HTTP/3 makes. It is not just about TCP itself — it is about understanding the constraints that shape every protocol built on top of it.

TCP Head-of-Line Blocking

TCP delivers bytes in order. If packet N is lost, packets N+1, N+2, ... are buffered and not delivered to the application until N is retransmitted and received. This is TCP head-of-line blocking: a single lost packet holds up everything behind it in the stream.

HTTP/2 multiplexes multiple request/response streams over one TCP connection. If one stream's data is lost, TCP holds up ALL streams — including those with no data loss. A 1% packet loss that only affects one stream stalls all 30 streams in an HTTP/2 connection. HTTP/3 / QUIC solves this by implementing independent stream delivery in user space: a lost packet only blocks the one QUIC stream that contained it, not others.

TCP Fast Open

Standard TCP requires 1 RTT for handshake + 1 RTT minimum for the first request. On a 100ms path, that is 200ms before the server processes the first byte of the request. TCP Fast Open (TFO, RFC 7413) allows data in the SYN packet on repeat connections, reducing first-request latency to 1 RTT. Chrome and iOS use TFO for performance-sensitive connections.

# Enable TCP Fast Open (Linux)
sysctl -w net.ipv4.tcp_fastopen=3      # 1=client, 2=server, 3=both

# Verify TFO in connection
ss -ti | grep tfo                       # Look for "fastopen" in output

# TFO in server code (Python)
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_TCP, socket.TCP_FASTOPEN, 5)   # 5 = backlog for TFO
s.bind(('0.0.0.0', 8080))
s.listen()

// CHAPTER 13

Common Misconceptions

✗ Common Mistake — TCP guarantees delivery all the way to the applicationTCP guarantees delivery and ordering end-to-end. TCP guarantees delivery and ordering from the sender's kernel to the receiver's kernel (socket buffer). It does NOT guarantee that the application processed the data, that the application acknowledged receipt, or that the data was written to disk. A server can ACK data and then crash before the application reads it. For application-level guarantees, use application-layer acknowledgments (database transactions, message queue ACKs).
✗ Common Mistake — RST immediately and reliably terminates a connection on both sidesRST immediately terminates a connection on both sides. RST is sent by one side and received by the other. The sender transitions to CLOSED. The receiver, upon receiving RST, aborts the connection. But if RST is lost (UDP drops it, firewall blocks it), the other side remains in its current state until timeout. More importantly, an RST with an out-of-window sequence number is silently discarded — this is by design to prevent RST injection attacks, but means RST can appear to "not work" if sequence numbers are out of sync.
✗ Common Mistake — Larger TCP buffers always improve performanceIncreasing TCP buffer sizes always improves performance. Increasing socket buffers helps when the bottleneck is the bandwidth-delay product. But it cannot help if the bottleneck is actual link capacity, CPU, application processing, or disk I/O. Oversized buffers can increase latency (bufferbloat) — data queues up in large buffers rather than being dropped and retransmitted quickly. On LAN paths (sub-millisecond RTT), default buffers are already more than adequate. Tune buffers only when measurements show the window is the actual bottleneck.
✗ Common Mistake — Nagle + Delayed ACK are always safe and independent optimizationsThe Nagle algorithm and delayed ACK are both optimizations that never cause problems. Nagle + Delayed ACK interaction is a classic performance anti-pattern. Nagle waits to send small writes until the previous data is ACKed. Delayed ACK waits 40ms before sending an ACK for a segment without data to piggyback. When both are active in the same connection with interactive small writes, every exchange incurs a 40ms delay: sender writes small data, Nagle holds it, receiver delays ACK 40ms, Nagle releases data, cycle repeats. Fix: TCP_NODELAY on the sender (disable Nagle).
✗ Common Mistake — TIME_WAIT is a bug or inefficiency that should be eliminatedTIME_WAIT is a bug or inefficiency that should be minimized. TIME_WAIT prevents two real correctness problems: delayed segment acceptance (old segments arriving after connection close) and reliable final ACK delivery. Aggressive reduction causes subtle data corruption on high-traffic servers — corrupted data streams where an old segment arrives and is accepted as belonging to the new connection because TIME_WAIT was skipped. The correct approach: connection pooling (avoid close events) and tcp_tw_reuse (safe reuse for outbound connections), not tcp_fin_timeout reduction below 30 seconds.
✗ Common Mistake — Three duplicate ACKs signal network-wide congestionThree duplicate ACKs signal network congestion. Three duplicate ACKs signal a missing segment — likely packet loss at a specific link, not network-wide congestion. Fast Retransmit/Recovery (not full Slow Start) is appropriate because later segments are still arriving, indicating the path is functional. A timeout (no ACKs at all) better signals severe congestion or link failure, and justifies the more aggressive Slow Start. Misclassifying the signal leads to inappropriate cwnd reduction: too aggressive on 3-dup-ACK, too conservative on timeouts.

// CHAPTER 14

Depth Check

Beginner
What is the TCP three-way handshake and why does it need three messages?
SYN: client proposes connection and sends its ISN. SYN-ACK: server acknowledges client's ISN and sends its own ISN. ACK: client acknowledges server's ISN. Three messages are the minimum to establish bidirectional agreement: two would leave one side uncertain whether the other received confirmation.
Intermediate
What is the difference between flow control and congestion control?
Flow control prevents the sender from overwhelming the receiver's buffer — managed via the receive window (rwnd) in the TCP header, set by the receiver. Congestion control prevents the sender from overwhelming the network — managed via the congestion window (cwnd) in the sender's kernel, adjusted based on loss signals. Actual sending rate is limited by min(cwnd, rwnd). Both are necessary: a fast receiver with a congested network still needs congestion control.
Intermediate
Why does TIME_WAIT exist and what is the risk of reducing it?
TIME_WAIT (2 × MSL ≈ 60–120s) serves two purposes: absorbing delayed segments from the closed connection before the 4-tuple can be reused, and ensuring reliable delivery of the final ACK. Reducing it risks two bugs: a delayed segment from an old connection arriving and being accepted by a new connection reusing the same 4-tuple (data corruption), and the passive closer retransmitting its final FIN finding no TIME_WAIT socket to respond — receiving RST instead of ACK.
Senior
Explain SACK and how it improves performance over cumulative ACK alone.
Selective Acknowledgment allows the receiver to report non-contiguous received segments. Rather than ACKing only the highest contiguous byte (cumulative ACK), SACK blocks encode the edges of received ranges. Example: if bytes 1–1000 and 2001–3000 are received but 1001–2000 is missing, SACK reports {sack(2001, 3000)} — the sender retransmits only 1001–2000. Without SACK, the sender retransmits from the last cumulative ACK onward (Go-Back-N behavior), wasting bandwidth re-sending already-received data. SACK is critical for performance over lossy links (satellite, Wi-Fi) where multiple segments may be lost in one window.
Senior
How does TCP Fast Open work and what security concern does it address?
TFO allows data to be sent in the SYN packet on repeat connections, saving 1 RTT of setup overhead. The mechanism: on the first connection, the server generates a TFO cookie (HMAC of client IP + secret) and sends it to the client in the TFO option. On subsequent SYNs, the client includes the cookie. The server validates the cookie before accepting SYN data, preventing amplification attacks (an attacker cannot forge valid cookies to send arbitrary data to the server pretending to be a different IP). The limitation: TFO data is not protected against replay on the same connection — the server may process SYN data twice if the SYN is retransmitted. Application-layer idempotency is required for SYN-carried data.
PhD
Describe the interaction between CUBIC congestion control, BBR, and fairness when both run simultaneously on the same bottleneck link.
CUBIC and BBR use fundamentally different congestion signals. CUBIC is loss-based: it backs off only when it detects loss (cwnd reduction on 3-dup-ACKs or timeout). BBR is model-based: it probes bandwidth and RTT, maintaining a model of network state. When CUBIC and BBR share a bottleneck: CUBIC aggressively fills the buffer (high queuing delay, high throughput for CUBIC). BBR sees increased RTT as congestion and reduces its rate. CUBIC flows get disproportionately high bandwidth because they are willing to inflate queues that BBR backs away from. In practice, BBR flows may get 30–70% less throughput than CUBIC flows on the same path — an unfairness that Google has partially addressed in BBRv2 (which adds loss-based congestion response to complement the model-based mechanism). The fundamental tension: loss-based protocols build queues aggressively; delay-based protocols yield. This remains an active area of research in TCP fairness and AQM (Active Queue Management) algorithm design.

🎯 Key Takeaways

  • TCP provides reliability, ordering, flow control, and congestion control over an unreliable IP network by numbering every byte and acknowledging receipt.
  • The three-way handshake (SYN → SYN-ACK → ACK) establishes bidirectional agreement on initial sequence numbers and TCP options (MSS, window scale, SACK, timestamps).
  • SYN cookies allow servers to handle SYN flood attacks without storing state for half-open connections — encoding connection info in the ISN and recovering it from the final ACK.
  • Congestion control phases: Slow Start (exponential cwnd growth) → Congestion Avoidance (linear growth, AIMD) → Fast Recovery (triggered by 3 dup-ACKs, avoids Slow Start restart) → Timeout (Slow Start from cwnd=1).
  • The receive window (rwnd) prevents buffer overflow at the receiver. Window scaling (RFC 7323) extends the 16-bit window to handle high-BDP paths (100ms RTT × 10 Gbps requires ~125 MB window).
  • TIME_WAIT exists for correctness: delayed segment absorption and reliable final ACK delivery. Reducing it aggressively risks data corruption. Use connection pooling and tcp_tw_reuse instead.
  • Nagle algorithm + Delayed ACK interaction causes 40ms delays on interactive small writes. Fix with TCP_NODELAY on the sender to disable Nagle buffering.
  • SACK allows the sender to retransmit only missing segments rather than everything from the last ACK — critical for performance over lossy links (Wi-Fi, mobile, satellite).
  • TCP head-of-line blocking: a single lost packet stalls all HTTP/2 streams over that TCP connection. HTTP/3 / QUIC solves this with independent per-stream delivery in user space.
  • BBR (Bottleneck Bandwidth and RTT) model-based congestion control outperforms loss-based CUBIC on lossy links (mobile, intercontinental) but can be unfairly out-competed by CUBIC on shared queues.
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...