Loading learning content...
Every TCP connection numbers its bytes with 32-bit sequence numbers. But where does that numbering start? Not at zero—and for fascinating reasons involving both correctness and security.
The Initial Sequence Number (ISN) is the sequence number of the first byte of data (minus one, technically—the SYN itself consumes the ISN). This seemingly simple value has been the subject of security research, RFC revisions, and countless attack vectors over the decades.
This page explores ISN in depth: why it exists, how it's chosen, why predictability enables attacks, and how modern TCP implementations generate cryptographically secure ISNs.
By the end of this page, you will understand: the purpose of the Initial Sequence Number, why ISN selection matters for security, the historical evolution from predictable to random ISNs, modern ISN generation algorithms (RFC 6528), how ISN is exchanged during the three-way handshake, and the relationship between ISN and connection uniqueness.
The Initial Sequence Number serves multiple critical purposes in TCP:
1. Starting Point for Byte Numbering
Each direction of a TCP connection has its own ISN. The sender uses ISN as the sequence number for the SYN segment. All subsequent data bytes are numbered relative to this starting point:
2. Connection Incarnation Differentiation
Consider this scenario:
How does TCP know this packet belongs to the old connection, not the new one?
Answer: Different ISNs. The new connection has different Initial Sequence Numbers, so the old packet's sequence number falls outside the expected range for the new connection. TCP discards it.
This is why ISNs should never start at the same value for connections between the same endpoints.
If ISNs always started at 0, old segments from a previous connection could easily fall into the valid sequence window of a new connection. A segment with SEQ=50000 from connection #1 might look valid to connection #2 if both started at 0. This could corrupt the data stream—a catastrophic reliability failure.
During connection establishment, both endpoints exchange their Initial Sequence Numbers. This happens through the famous three-way handshake:
Step 1: SYN (Client → Server)
Step 2: SYN-ACK (Server → Client)
Step 3: ACK (Client → Server)
Post-Handshake Sequence Numbers:
After the handshake completes:
Both parties now know both ISNs, establishing the shared context for reliable communication.
Connection State After Handshake:═══════════════════════════════════════════════════════════════════ Client State: ISS (Initial Send Sequence) = 1000 ← Client's ISN_C IRS (Initial Receive Sequence) = 5000 ← Server's ISN_S SND.NXT = 1001 (next byte to send) RCV.NXT = 5001 (next byte to receive) Server State: ISS (Initial Send Sequence) = 5000 ← Server's ISN_S IRS (Initial Receive Sequence) = 1000 ← Client's ISN_C SND.NXT = 5001 (next byte to send) RCV.NXT = 1001 (next byte to receive) ═══════════════════════════════════════════════════════════════════Note: Each side stores both ISNs but labels them differently: - ISS: what I chose to start my sequence space - IRS: what the peer chose to start their sequence spaceThe SYN flag consumes one sequence number even though it carries no data. This ensures SYN (and SYN-ACK) can be reliably acknowledged and retransmitted. Similarly, the FIN flag consumes a sequence number. Both are considered 'virtual bytes' in the sequence space.
The original TCP specification (RFC 793, 1981) recommended a simple ISN selection algorithm:
ISN = (32-bit timer) incrementing every 4 microseconds
This would cycle through the entire 32-bit space in about 4.55 hours. The intent was to ensure:
The problem: This was completely predictable.
The Morris Attack (1985)
Security researcher Robert Morris (later infamous for the Morris Worm) demonstrated that predictable ISNs enable devastating attacks:
With predictable ISNs, an attacker who can't even see the server's responses (a 'blind' attack) can still establish connections by predicting sequence numbers. This enabled IP spoofing attacks that bypassed address-based trust (like .rhosts authentication). The 1995 Mitnick attack exploited exactly this vulnerability.
RFC 6528 (2012) specifies the current standard for secure ISN generation. The algorithm produces ISNs that are:
The Algorithm:
ISN = M + F(local_ip, local_port, remote_ip, remote_port, secret_key)
Where:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
import hashlibimport structimport timeimport os class SecureISNGenerator: """ RFC 6528 compliant ISN generation. Combines clock-based progression with cryptographic unpredictability. """ def __init__(self): # Secret key - randomly generated, never exposed # In production: regenerate periodically, protect in kernel memory self.secret_key = os.urandom(32) self.start_time = time.time() def _get_clock_component(self) -> int: """ M: 32-bit counter incrementing every 4 microseconds. Cycles through full space in ~4.55 hours. """ elapsed_us = int((time.time() - self.start_time) * 1_000_000) clock_ticks = elapsed_us // 4 # 4 microsecond granularity return clock_ticks & 0xFFFFFFFF # 32-bit wrap def _compute_hash_component(self, local_ip: str, local_port: int, remote_ip: str, remote_port: int) -> int: """ F: Cryptographic hash of connection 4-tuple + secret. Makes ISN unpredictable without the secret. """ # Pack connection identifiers data = f"{local_ip}:{local_port}-{remote_ip}:{remote_port}".encode() # HMAC-like construction with secret h = hashlib.sha256() h.update(self.secret_key) h.update(data) # Extract 32-bit value from hash digest = h.digest() return struct.unpack('<I', digest[:4])[0] def generate_isn(self, local_ip: str, local_port: int, remote_ip: str, remote_port: int) -> int: """ Generate secure ISN for a new connection. """ M = self._get_clock_component() F = self._compute_hash_component(local_ip, local_port, remote_ip, remote_port) # Combine: clock provides progression, hash provides randomness isn = (M + F) & 0xFFFFFFFF return isn # Example usagegenerator = SecureISNGenerator() # Two connections to same destination get different ISNsisn1 = generator.generate_isn("192.168.1.100", 45678, "10.0.0.1", 443)isn2 = generator.generate_isn("192.168.1.100", 45679, "10.0.0.1", 443) print(f"Connection 1 ISN: {isn1}")print(f"Connection 2 ISN: {isn2}")print(f"Difference: {abs(isn2 - isn1)}") # Large, unpredictableSecurity Properties:
Per-Connection Uniqueness: The hash input includes all four tuple elements, so different connections produce different hash components
Unpredictability Without Secret: Knowing the connection tuple isn't enough; the secret_key is required to compute F
Resistance to Sampling: Observing ISNs from other connections doesn't help predict ISNs for new connections (different hash inputs)
Forward Progress: The clock component M ensures ISNs increase over time, preventing old segment acceptance
All modern operating systems (Linux, Windows, macOS, BSD) implement RFC 6528-style ISN generation. Blind TCP injection attacks that exploited predictable ISNs are no longer feasible against properly configured systems. Always ensure your systems are up to date.
ISN selection interacts critically with TCP's TIME_WAIT state. When a connection closes, one endpoint enters TIME_WAIT for 2*MSL (Maximum Segment Lifetime, typically 60-120 seconds).
Why TIME_WAIT Exists:
Ensure final ACK delivery: If the last ACK is lost, the peer will retransmit FIN; the TIME_WAIT endpoint can re-ACK
Allow old segments to expire: Any delayed segments from the closed connection will expire before a new connection with the same 4-tuple can be established
ISN's Role:
Even if a new connection is somehow established before TIME_WAIT expires (which TIME_WAIT normally prevents), different ISNs provide a secondary defense:
Scenario: Old segment arrives at new connection Old Connection (closed): Client ISN_C = 1000000 Server ISN_S = 5000000 Delayed segment: SEQ=1050000, 500 bytes of data New Connection (same 4-tuple, established prematurely): Client ISN_C' = 2500000 (different due to secure generation) Server ISN_S' = 7800000 (different due to secure generation) Expected range: SEQ around 2500001-2500XXX When delayed segment arrives (SEQ=1050000): ───────────────────────────────────────────────────────── Check: Is 1050000 within [RCV.NXT, RCV.NXT + RCV.WND]? RCV.NXT = 2500001 RCV.WND = 65535 Valid range = [2500001, 2565536] SEQ 1050000 is WAY outside this range! → Segment discarded as "out of window" ───────────────────────────────────────────────────────── Result: ISN randomness provides defense-in-depth against old segment acceptance, complementing TIME_WAIT.Protection Against Wrapped Sequence Numbers (PAWS):
At very high bandwidth, sequence numbers can wrap within the 2*MSL period. A segment from the previous "lap" around the sequence space could have a valid-looking sequence number.
RFC 7323 defines PAWS (Protection Against Wrapped Sequences), which uses TCP timestamps to reject old segments:
PAWS complements ISN randomization, providing protection at gigabit+ speeds where sequence wraparound is frequent.
TCP's reliability doesn't depend on any single mechanism. TIME_WAIT prevents premature connection reuse. Random ISNs ensure different sequence spaces. PAWS rejects old timestamps. Together, they make old segment acceptance virtually impossible in properly implemented TCP stacks.
TCP supports a rarely-used mode called simultaneous open, where both endpoints independently initiate connection at the same time. This scenario also demonstrates ISN exchange.
Simultaneous Open Handshake:
Key Observations:
Practical Relevance:
Simultaneous open is rare in practice (requires precise timing), but its support demonstrates TCP's robustness. NAT traversal techniques sometimes exploit simultaneous open to establish peer-to-peer connections through firewalls.
Simultaneous open adds complexity to the TCP state machine. Both hosts transition through SYN_SENT to SYN_RECEIVED to ESTABLISHED, rather than the normal client (SYN_SENT → ESTABLISHED) and server (LISTEN → SYN_RECEIVED → ESTABLISHED) paths. This is why TCP's state diagram has more transitions than the simple three-way handshake suggests.
Understanding ISN has practical implications for network engineers, security professionals, and developers:
Debugging and Analysis:
When analyzing TCP traces:
| Symptom | Possible ISN-Related Cause | Investigation Steps |
|---|---|---|
| Connection resets unexpectedly | Old segment with 'valid' SEQ accepted | Check for duplicate connections, verify TIME_WAIT behavior |
| Data corruption on reused connection | Old data injected into new connection | Analyze ISNs across connections, verify PAWS enabled |
| Failed connections through NAT | NAT rewriting breaks ISN consistency | Capture on both sides of NAT, compare ISNs |
| Blind injection attempts logged | Attacker guessing ISNs | Verify RFC 6528 compliance, check entropy of generated ISNs |
| Asymmetric routing issues | Segments with wrong ISN reaching wrong path | Trace ISNs through all network paths |
Security Monitoring:
ISN Pattern Analysis: Tools like nmap can probe ISN generation to fingerprint operating systems. Defenders should be aware that ISN behavior is a fingerprinting vector.
Entropy Verification: Security audits should verify ISN randomness. Patterns in ISN generation indicate implementation weaknesses.
Spoofed Packet Detection: Packets with sequence numbers far outside expected windows may indicate spoofing attempts. IDS/IPS systems use this heuristic.
Wireshark Filter for ISN Analysis:────────────────────────────────────────────────────────────────# Show only SYN segments (where ISN is visible)tcp.flags.syn == 1 && tcp.flags.ack == 0 # Show SYN-ACK segments (reveals server ISN)tcp.flags.syn == 1 && tcp.flags.ack == 1 # After disabling relative sequence numbers:# Look at "Sequence Number" field for actual ISN Example SYN segment analysis: Frame 1: SYN from 192.168.1.5:45678 → 10.0.0.1:443 Sequence Number: 3482917654 ← This is the client's ISN Frame 2: SYN-ACK from 10.0.0.1:443 → 192.168.1.5:45678 Sequence Number: 892047261 ← This is the server's ISN Acknowledgment: 3482917655 ← Client ISN + 1 Expected: ISNs should appear random, not sequential across connections.Even with RFC 6528 compliance, subtle differences in ISN generation (clock tick rates, hash algorithms, counter initialization) can fingerprint operating systems. Security-conscious deployments may implement additional randomization or use TCP stack obfuscation.
The Initial Sequence Number might seem like a minor detail—just the starting point for byte numbering. But as we've seen, ISN selection has profound implications for both correctness and security. Let's consolidate the key insights:
What's Next:
We've covered sequence numbers (which identify data the sender transmits) and ISN (where they start). Next, we explore acknowledgment numbers—how the receiver communicates what it has received, enabling the sender to track progress and trigger retransmissions.
You now understand the Initial Sequence Number: its purpose, how it's exchanged, the security evolution from predictable to cryptographically random, and its practical implications. Next, we examine how acknowledgment numbers complete the reliable delivery picture.