Perché TCP richiede un handshake?
TCP è un protocollo connection-oriented: prima che un singolo byte di dati venga scambiato, i due host devono concordare i parametri della connessione. Questo scambio preliminare serve a:
- Sincronizzare i numeri di sequenza iniziali (ISN) di entrambi i lati
- Verificare che entrambi gli host siano raggiungibili e pronti
- Negoziare le opzioni TCP (MSS, Window Scale, SACK, Timestamps)
- Allocare i buffer di ricezione e inizializzare le variabili di stato
Three-Way Handshake — Apertura della connessione
Client Server
| |
|--- SYN (seq=x) -------------->| Client: SYN_SENT
| | Server: SYN_RECEIVED
|<-- SYN-ACK (seq=y, ack=x+1) --|
| |
|--- ACK (ack=y+1) ------------>| Entrambi: ESTABLISHED
| |
[ dati possono fluire ]
| Passo | Segmento | Flag | Seq/Ack | Significato |
|---|---|---|---|---|
| 1 | Client → Server | SYN | seq=x (ISN client) | Il client vuole aprire una connessione e comunica il suo ISN |
| 2 | Server → Client | SYN+ACK | seq=y (ISN server), ack=x+1 | Il server accetta e comunica il suo ISN; riscontra il SYN del client |
| 3 | Client → Server | ACK | ack=y+1 | Il client riscontra il SYN del server — connessione stabilita |
Il segmento SYN non trasporta payload ma occupa 1 numero di sequenza (come se fosse 1 byte di dati). Per questo l’ACK del passo 3 vale y+1 anche se il SYN-ACK del passo 2 aveva payload zero. Lo stesso vale per FIN.
ISN — Initial Sequence Number
L’ISN non parte da 0: viene scelto pseudocasualmente da ciascun host al momento dell’apertura. I motivi sono fondamentalmente due:
- Sicurezza: un ISN prevedibile permetterebbe a un attaccante di forgiare segmenti TCP validi (IP spoofing / sequence prediction attack, CVE storico)
- Distinzione da connessioni precedenti: evita che segmenti ritardati di una vecchia connessione sulle stesse porte vengano interpretati come appartenenti alla connessione corrente
Nelle implementazioni moderne, l’ISN è generato con una funzione che tiene conto dell’IP sorgente, IP destinazione, porte e un timestamp crittograficamente randomizzato (RFC 6528).
Four-Way Handshake — Chiusura della connessione
TCP è full-duplex: ogni direzione deve essere chiusa separatamente. La chiusura avviene con un four-way handshake, detto anche half-close sequenziale.
Client (inizia la chiusura) Server | | |--- FIN (seq=u) -------------->| Client: FIN_WAIT_1 |<-- ACK (ack=u+1) -------------| Client: FIN_WAIT_2 / Server: CLOSE_WAIT | | (il server può ancora inviare dati!) |<-- FIN (seq=v) ---------------| Server: LAST_ACK |--- ACK (ack=v+1) ------------>| Client: TIME_WAIT → CLOSED (dopo 2×MSL) | | Server: CLOSED
| Passo | Segmento | Flag | Stato client | Significato |
|---|---|---|---|---|
| 1 | Client → Server | FIN | FIN_WAIT_1 | Il client non ha più dati da inviare |
| 2 | Server → Client | ACK | FIN_WAIT_2 | Il server riscontra il FIN; la direzione client→server è chiusa |
| 3 | Server → Client | FIN | TIME_WAIT | Il server ha finito di inviare e chiude la sua direzione |
| 4 | Client → Server | ACK | TIME_WAIT (→ CLOSED) | Il client riscontra il FIN del server |
TIME_WAIT — 2×MSL
Il client rimane in stato TIME_WAIT per 2×MSL (Maximum Segment Lifetime, tipicamente 30–60 s → TIME_WAIT ≈ 60–120 s). Scopo: garantire che l’ACK finale sia arrivato al server e che eventuali segmenti ritardati della vecchia connessione scadano. Solo dopo TIME_WAIT le stesse 4-tuple di porte possono essere riutilizzate.
Chiusura simultanea e RST
Esistono due varianti della chiusura:
- Chiusura simultanea: entrambi gli host inviano FIN contemporaneamente. Ogni lato riceve un FIN e risponde con ACK — la macchina a stati gestisce questo caso simmetrico
- RST (Reset): chiusura immediata e anomala. Nessun graceful close. Causato da: connessione a porta chiusa, crash del processo, timeout, sicurezza. Il destinatario sa che la connessione è terminata ma non può distinguere tra errore e attacco
I segmenti RST possono essere forgiati da un attaccante se è in grado di indovinare il numero di sequenza (vulnerabilità storica usata da firewall stateful per terminare connessioni “indesiderate” — es. Great Firewall of China usa RST injection).
La macchina a stati TCP
Gli stati di una connessione TCP sono definiti formalmente in RFC 9293. I principali da conoscere:
| Stato | Quando |
|---|---|
CLOSED | Stato iniziale e finale — nessuna connessione |
LISTEN | Server in ascolto su una porta (ha chiamato bind() + listen()) |
SYN_SENT | Client ha inviato SYN, attende SYN-ACK |
SYN_RECEIVED | Server ha ricevuto SYN e inviato SYN-ACK |
ESTABLISHED | Connessione attiva — scambio dati |
FIN_WAIT_1/2 | Lato che ha iniziato la chiusura attende ACK/FIN |
CLOSE_WAIT | Server ha ricevuto FIN del client, ma non ha ancora chiuso il suo lato |
TIME_WAIT | Client attende 2×MSL prima di liberare le porte |
LAST_ACK | Server ha inviato FIN, attende l’ACK finale |
Per osservare gli stati TCP live su Linux: ss -tan oppure netstat -an | grep tcp. Nella colonna State trovi ESTABLISHED, TIME_WAIT, CLOSE_WAIT ecc. Utile per diagnosticare connessioni “bloccate” o server con troppe connessioni in CLOSE_WAIT.
- Il three-way handshake (SYN → SYN-ACK → ACK) sincronizza gli ISN e stabilisce la connessione in 3 scambi
- L’ISN è scelto casualmente per motivi di sicurezza e per distinguere connessioni precedenti
- La chiusura avviene con un four-way handshake: ogni direzione si chiude indipendentemente con FIN + ACK
- TIME_WAIT (2×MSL) impedisce che segmenti ritardati di una connessione infestino la successiva sulle stesse porte
- RST chiude immediatamente la connessione in modo anomalo — nessun graceful close
- La macchina a stati TCP (LISTEN → SYN_SENT → ESTABLISHED → TIME_WAIT → CLOSED) è definita in RFC 9293