TCP Protocol Overview
Connection-Oriented Communication
Read aloud is unavailable in this browser.
The Art of Establishing Understanding
Before two people can have a meaningful conversation, they need to establish that both are present, attentive, and ready to communicate. A phone call begins with "Hello?" and a response confirming the other party is there. A formal meeting starts with introductions. Even a casual chat begins with eye contact and acknowledgment.
TCP follows the same principle. Before any data can flow, both endpoints must explicitly agree to communicate. This is the essence of being connection-oriented: a deliberate, stateful relationship is established, maintained throughout communication, and explicitly terminated when finished.
This stands in stark contrast to connectionless protocols like UDP, where packets are simply fired into the network with no confirmation that anyone is listening. TCP's connection model provides guarantees that connectionless communication cannot: mutual agreement, synchronized state, and a clear contract between communicating parties.
By the end of this page, you will understand the complete lifecycle of a TCP connection: how it's established through the three-way handshake, what state is maintained during communication, how connections are uniquely identified, and why this connection-oriented model is essential for TCP's reliability guarantees. You'll also understand the Transmission Control Block (TCB) and how the TCP state machine governs connection behavior.
Why Connection-Oriented?
The decision to make TCP connection-oriented wasn't arbitrary—it's fundamental to providing reliable communication over an unreliable network. Let's understand why maintaining connection state is essential.
The core challenge:
IP provides best-effort, stateless delivery. Each packet is routed independently with no memory of previous packets. The network doesn't know or care about conversations—it just moves packets toward their destinations.
But reliable communication requires context:
- Which bytes have been sent?
- Which bytes have been acknowledged?
- What's the next byte the receiver expects?
- How much buffer space does the receiver have?
- How congested is the network?
This context must be stored somewhere. In TCP, it's stored at the endpoints—both the sender and receiver maintain synchronized state about their communication.
- •Reliability — Sequence numbers track what's been sent; ACKs confirm receipt; gaps trigger retransmission. Without state, TCP couldn't track what needs retransmission.
- •Ordering — The receiver must remember which bytes it has and which are missing. State allows buffering out-of-order segments until gaps are filled.
- •Flow Control — The receiver advertises available buffer space. The sender must remember this limit. Both must track where they are in the data stream.
- •Congestion Control — The sender must remember how the network has behaved and adjust its sending rate accordingly. This requires historical state.
- •Resource Management — Both hosts allocate buffers and timers for the connection. Without explicit connection setup, when would resources be allocated?
Crucially, TCP connection state exists only at the endpoints—not in the network. Routers simply forward packets; they don't track TCP connections (except for stateful firewalls). This follows the end-to-end principle: keep the network simple, push complexity to the edges. It's what allowed the internet to scale without routers needing per-connection state.
Connection Identification: The 4-Tuple
Every TCP connection must be uniquely identifiable. After all, a single server might handle thousands of simultaneous connections—how does it know which incoming packet belongs to which connection?
The 4-tuple identifier:
Every TCP connection is uniquely identified by four values:
(Source IP, Source Port, Destination IP, Destination Port)
No two active connections can have the same 4-tuple. This means:
- A server listening on port 80 can handle millions of connections from different clients
- Each unique (client IP, client port) pair creates a distinct connection to the same server
- A single client can have multiple connections to the same server (using different source ports)
The IP addresses come from the network layer; the port numbers are TCP's contribution. Together, they create a globally unique identifier for the communication session.
Example: Multiple Client Connections to Web Server Server: IP 203.0.113.100, listening on port 443 (HTTPS) Connection 1: Local: 203.0.113.100:443 (server) Remote: 192.168.1.10:52341 (client A) 4-tuple: (192.168.1.10, 52341, 203.0.113.100, 443) Connection 2: Local: 203.0.113.100:443 (server) Remote: 192.168.1.10:52342 (same client A, different port) 4-tuple: (192.168.1.10, 52342, 203.0.113.100, 443) Connection 3: Local: 203.0.113.100:443 (server) Remote: 10.0.0.50:41280 (client B) 4-tuple: (10.0.0.50, 41280, 203.0.113.100, 443) All three connections share the same server IP and port,but are distinguishable by the client's IP/port combination. ───────────────────────────────────────────────────────── Example: Single Client with Multiple Connections Client IP: 192.168.1.100 Connection to Web Server: 4-tuple: (192.168.1.100, 54001, 93.184.216.34, 443) Connection to Email Server: 4-tuple: (192.168.1.100, 54002, 142.250.185.109, 993) Second connection to same Web Server: 4-tuple: (192.168.1.100, 54003, 93.184.216.34, 443) Each uses a different ephemeral source port (54001, 54002, 54003),making each connection unique even when to the same server.Port Number Ranges:
Port numbers are 16-bit values (0-65535), divided into ranges:
| Range | Name | Purpose |
|---|---|---|
| 0-1023 | Well-Known Ports | Reserved for standard services (HTTP=80, HTTPS=443, SSH=22) |
| 1024-49151 | Registered Ports | Assigned to specific applications by IANA |
| 49152-65535 | Ephemeral Ports | Dynamically assigned for client connections |
When you connect to a web server, your OS picks an available ephemeral port for the source. The destination port is well-known (443 for HTTPS). This asymmetry is why clients can initiate connections but need to know the server's port in advance.
Network Address Translation (NAT) devices use the 4-tuple to track connections. When your home router NATs your connection to a server, it remembers the mapping so return traffic can be routed back to your device. If the 4-tuple weren't unique, NAT wouldn't work. This is also why TCP over NAT sometimes requires keepalive packets—to prevent the NAT table entry from expiring.
The Three-Way Handshake
TCP connection establishment uses the famous three-way handshake: a sequence of three segments that synchronizes both endpoints and establishes the connection. This is TCP's "Hello, are you there? Yes, I'm here. Great, let's talk" protocol.
The three steps:
-
SYN (Synchronize): Client initiates by sending a segment with the SYN flag set and its Initial Sequence Number (ISN)
-
SYN-ACK (Synchronize-Acknowledge): Server responds with its own SYN (its ISN) and an ACK of the client's SYN
-
ACK (Acknowledge): Client acknowledges the server's SYN, completing the handshake
After these three segments, both sides are synchronized and data transfer can begin.
Why three messages? Why not two?
A two-way handshake would be:
- Client: "I want to connect"
- Server: "OK, connected"
But this fails to establish bidirectional synchronization. Consider:
- The server doesn't know if its reply reached the client
- The client doesn't confirm it received the server's parameters
- Old, delayed SYN packets could establish ghost connections (the "old duplicate" problem)
The third message (client's ACK) confirms the client received the server's response. Now both sides know that both sides know the connection is established—this mutual knowledge is essential.
The Four-Way Handshake Case:
Theoretically, four messages would be even more robust (server sends SYN and ACK separately). TCP combines them into one SYN-ACK segment for efficiency. Strictly speaking, we're exchanging four logical pieces of information in just three segments:
- Client SYN (→)
- Server ACK of client SYN (←)
- Server SYN (←) — combined with #2
- Client ACK of server SYN (→)
| Step | Sender | Flags Set | Sequence # | ACK # | Purpose |
|---|---|---|---|---|---|
| 1 | Client | SYN | client_ISN | — | Request to connect, send client's ISN |
| 2 | Server | SYN, ACK | server_ISN | client_ISN + 1 | Accept request, send server's ISN, ACK client's SYN |
| 3 | Client | ACK | client_ISN + 1 | server_ISN + 1 | Confirm server's SYN, connection established |
The three-way handshake has a vulnerability: after receiving a SYN, the server must allocate resources and wait for the ACK. An attacker can send many SYNs without completing handshakes, exhausting server resources. This is a SYN flood attack. Modern systems use SYN cookies to defend—the server encodes state in the SYN-ACK sequence number instead of allocating memory, validating the returning ACK before allocating resources.
Initial Sequence Numbers (ISN)
During the handshake, each side picks an Initial Sequence Number (ISN). This number is crucial—it's the starting point for tracking all bytes sent in that direction. But why not just start at zero? The answer involves security and connection disambiguation.
Why random ISNs?
Historically, many implementations used predictable ISNs (simple counters). This created severe security vulnerabilities:
-
TCP Session Hijacking: If an attacker can predict the ISN, they can inject packets into an existing connection without being on the network path. They simply guess the sequence numbers.
-
Spoofed Connection Establishment: An attacker could establish connections to a victim server by predicting the server's ISN and sending a fake ACK.
-
Old Connection Confusion: If ISNs are predictable and restart at similar values, segments from an old connection might be mistaken for a new connection's data.
Modern implementations use cryptographically secure ISN generation, producing ISNs that are practically impossible to predict.
1234567891011121314151617181920212223242526272829303132333435363738394041
# Simplified ISN generation (conceptual)# Actual implementations use OS-specific secure methods import hashlibimport time def generate_ISN(src_ip, src_port, dst_ip, dst_port, secret_key): """ Generate a cryptographically secure Initial Sequence Number. The ISN should be: 1. Unpredictable to attackers 2. Unique enough to avoid collision with old connections 3. Different for each connection (4-tuple) """ # Create connection identifier connection_id = f"{src_ip}:{src_port}-{dst_ip}:{dst_port}" # Get current time (adds uniqueness across time) timestamp = int(time.time() * 1000) # milliseconds # Hash the connection ID with secret key and timestamp # Secret key is known only to this host hash_input = f"{secret_key}:{connection_id}:{timestamp}" hash_output = hashlib.sha256(hash_input.encode()).digest() # Use first 4 bytes as 32-bit ISN isn = int.from_bytes(hash_output[:4], 'big') return isn # In practice, Linux uses:# ISN = timer + hash(src_ip, dst_ip, src_port, dst_port, secret_key)# where timer increments ~every 4 microseconds # The key properties:# - No attacker can predict ISN without knowing secret_key# - Same connection parameters + time = same ISN (for SYN retries)# - Different connections get different ISNs# - ISN space is large enough that collisions are rareISN Considerations:
- 32-bit space: ISNs are 32-bit values (0 to 4,294,967,295). Sequence numbers wrap around after this.
- Lifetime: A connection's ISN should differ from the ISN used for a previous connection with the same 4-tuple, at least for long enough that old segments have expired.
- MSL (Maximum Segment Lifetime): TCP assumes segments can survive in the network for at most 2 minutes. TIME_WAIT state ensures old segments can't be misinterpreted.
- PAWS (Protection Against Wrapped Sequences): For high-speed connections that could wrap sequence numbers quickly, TCP uses timestamps to distinguish old from new segments.
RFC 6528 specifies recommendations for secure ISN generation. Modern operating systems follow these guidelines, using algorithms that incorporate secret keys, timestamps, and connection identifiers to produce ISNs that are cryptographically unpredictable. This largely mitigates historical TCP vulnerabilities related to ISN prediction.
The Transmission Control Block (TCB)
When a TCP connection is established, the kernel creates a Transmission Control Block (TCB)—a data structure containing all the state needed to manage that connection. The TCB is TCP's memory of the conversation.
What's in a TCB?
The TCB contains everything TCP needs to maintain the connection:
- •Connection Identifiers: Local/remote IP addresses and port numbers (the 4-tuple)
- •Send Sequence Variables: SND.UNA (oldest unacknowledged), SND.NXT (next to send), SND.WND (send window)
- •Receive Sequence Variables: RCV.NXT (next expected), RCV.WND (receive window advertised to peer)
- •Send Buffer: Data waiting to be sent, and sent-but-unacknowledged data
- •Receive Buffer: Arrived data waiting for application to read
- •Timers: Retransmission timer, persist timer, keepalive timer, TIME-WAIT timer
- •RTT Estimates: Smoothed RTT (SRTT), RTT variance (RTTVAR), retransmission timeout (RTO)
- •Congestion Control State: cwnd, ssthresh, congestion algorithm state
- •Connection State: Current state in TCP state machine (ESTABLISHED, FIN_WAIT, etc.)
- •Options: MSS, window scaling, SACK support, timestamps
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
// Conceptual TCB structure (simplified)// Real implementations are more complex struct tcp_control_block { // ===== Connection Identification ===== uint32_t local_ip; uint32_t remote_ip; uint16_t local_port; uint16_t remote_port; // ===== Connection State ===== enum tcp_state state; // CLOSED, LISTEN, SYN_SENT, ESTABLISHED, etc. // ===== Send Sequence Space ===== uint32_t snd_una; // Oldest unacknowledged sequence number uint32_t snd_nxt; // Next sequence number to send uint32_t snd_wnd; // Send window (from receiver's advertisement) uint32_t snd_wl1; // Sequence number for last window update uint32_t snd_wl2; // ACK number for last window update uint32_t iss; // Initial send sequence number // ===== Receive Sequence Space ===== uint32_t rcv_nxt; // Next expected sequence number uint32_t rcv_wnd; // Receive window to advertise uint32_t irs; // Initial receive sequence number // ===== Buffers ===== struct buffer *send_buffer; // Outgoing data struct buffer *receive_buffer; // Incoming data struct segment_queue *ooo_queue; // Out-of-order segments // ===== Timers ===== struct timer retransmit_timer; struct timer persist_timer; struct timer keepalive_timer; struct timer time_wait_timer; // ===== RTT Estimation ===== uint32_t srtt; // Smoothed RTT (microseconds) uint32_t rttvar; // RTT variance uint32_t rto; // Retransmission timeout // ===== Congestion Control ===== uint32_t cwnd; // Congestion window uint32_t ssthresh; // Slow start threshold uint8_t in_recovery; // In fast recovery? uint32_t recover; // Recovery point sequence number // ===== Options ===== uint16_t mss; // Maximum segment size uint8_t wscale_send; // Window scaling factor for sending uint8_t wscale_recv; // Window scaling factor for receiving uint8_t sack_permitted; // SACK enabled? uint8_t timestamps; // Timestamps enabled?}; // TCBs are typically stored in a hash table keyed by the 4-tuple// for O(1) lookup when packets arriveEach TCB consumes kernel memory (typically a few KB). A server handling millions of connections needs hundreds of megabytes just for TCBs. This is why connection-oriented protocols have scalability challenges—each connection requires dedicated state. Modern servers use techniques like SO_REUSEPORT and connection pooling to manage these resources.
TCP State Machine
TCP connection behavior is governed by a state machine. Each connection exists in one of several defined states, and transitions between states are triggered by events: receiving segments, application actions, or timer expirations.
The Main States:
We'll explore these in detail in the dedicated TCP State Diagram module, but here's an overview:
| State | Description | Duration |
|---|---|---|
| CLOSED | No connection exists; starting/ending point | N/A |
| LISTEN | Server waiting for incoming connections | Until connection or close |
| SYN_SENT | Client has sent SYN, awaiting response | Until SYN-ACK or timeout |
| SYN_RECEIVED | Server received SYN, sent SYN-ACK | Until ACK or timeout |
| ESTABLISHED | Connection open, data can flow | Duration of conversation |
| FIN_WAIT_1 | Sent FIN, waiting for ACK | Until ACK received |
| FIN_WAIT_2 | Our FIN acknowledged, waiting for peer's FIN | Until peer's FIN |
| CLOSE_WAIT | Received peer's FIN, wait for app to close | Until application closes |
| CLOSING | Both sides sent FIN simultaneously | Until ACKs arrive |
| LAST_ACK | Sent FIN, waiting for final ACK | Until ACK received |
| TIME_WAIT | Waiting to ensure old segments expire | 2×MSL (typically 2 min) |
On Linux, you can view current connection states with netstat -ant or ss -ant. You'll see connections in various states—ESTABLISHED for active connections, TIME_WAIT for recently closed ones, LISTEN for server sockets awaiting connections. This visibility is invaluable for debugging connection issues.
Simultaneous Open
The three-way handshake assumes one side is the client (initiates) and one is the server (responds). But TCP also supports simultaneous open: both sides initiate at the same time, crossing SYNs.
How does this happen?
Imagine two hosts that have pre-agreed to connect at the same time:
- Host A sends SYN to Host B (thinking it's the client)
- Host B sends SYN to Host A (also thinking it's the client)
- Both receive the other's SYN, recognize the situation
- Both respond with SYN-ACK
- Both receive SYN-ACK, move to ESTABLISHED
This results in a four-message handshake (two SYNs, two SYN-ACKs), but still establishes a connection correctly.
When does this occur?
- Peer-to-peer applications where both sides might try to connect simultaneously
- NAT traversal techniques like TCP hole punching
- Certain testing/debugging scenarios
In practice, simultaneous open is rare because it requires precise timing. But TCP must handle it correctly as per the protocol specification.
Simultaneous open enables TCP hole punching for NAT traversal. If two hosts behind different NATs send SYNs to each other's external addresses simultaneously, their NAT devices create outbound mappings that allow the returning SYN-ACK to pass through. This technique enables direct peer-to-peer connections without a relay server.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.