Loading content...
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.
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:
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.
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.
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:
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.
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:
But this fails to establish bidirectional synchronization. Consider:
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:
| 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.
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:
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.
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:
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 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.
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:
This results in a four-message handshake (two SYNs, two SYN-ACKs), but still establishes a connection correctly.
When does this occur?
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.
TCP's support for simultaneous open created an unexpected security issue: the split handshake. This is a variation of the three-way handshake that some implementations accepted, but which violated the spirit of connection-oriented communication.
The Attack:
This happened because the implementation incorrectly treated the ACK as the final step of a simultaneous open, even though the victim never actually sent a SYN.
Why is this bad?
Status:
Modern TCP implementations have been patched to reject split handshakes. RFC 793 is ambiguous here, which allowed the vulnerability. The lesson: connection establishment must be strictly validated.
The split handshake vulnerability illustrates why security requires multiple layers. Even if TCP is properly implemented, firewalls and applications should independently verify connection legitimacy. Never assume that a connection reaching your application was properly established.
TCP's connection-oriented nature is fundamental to its reliability and predictability. Let's summarize what we've learned:
TCP creates a virtual circuit between applications—a logical, bidirectional, reliable channel that abstracts the complexity of the underlying packet-switched network. This abstraction has been the foundation of reliable internet communication for five decades.
What's next:
Now that we understand how TCP establishes connections, the next page explores what happens when things go wrong: reliable delivery. We'll dive deep into sequence numbers, acknowledgments, retransmission strategies, and the mechanisms TCP uses to ensure that every byte reaches its destination correctly.