Что происходит
Когда клиент делает connect() к серверу, ядра обмениваются тремя пакетами:
Client Server
| ── SYN seq=X ──────► | (1)
| |
| ◄── SYN-ACK | (2)
| seq=Y ack=X+1 |
| |
| ── ACK ack=Y+1 ─────► | (3)
| |
| <════════════════════════> | ESTABLISHED, ready to send data
Что в каждом пакете
- SYN - флаг
Sвtcpdump. Клиент: «хочу открыть, мой ISN = X» - SYN-ACK - флаги
S+A. Сервер: «согласен, мой ISN = Y, твоё X+1 я подтверждаю» - ACK - флаг
A. Клиент: «подтверждаю Y+1»
После третьего пакета обе стороны считают соединение Established -
ядро на сервере добавляет в accept-queue, оттуда accept()-syscall
достаёт его в приложение.
ISN - Initial Sequence Number
Каждая сторона выбирает случайный 32-битный ISN. Это не нумерация байтов от нуля - нет. Случайность защищает от:
- Sequence-prediction атак (вставка пакетов в чужое соединение)
- Перепутывания со старыми пакетами после crash'а
Дальше последовательность инкрементируется на размер payload в каждом пакете данных.
Где видно
В tcpdump каждый пакет имеет флаги:
Flags [S], seq 1000 ← SYN
Flags [S.], seq 5000, ack 1001 ← SYN-ACK
Flags [.], ack 5001 ← ACK
Flags [P.], seq 1001:1041, ack 5001 ← данные с PUSH (HTTP request)
Точка после буквы ([.]) обозначает ACK.
Когда что-то пошло не так
- Connection refused - порт закрыт. Сервер вместо SYN-ACK шлёт RST
- Connection timeout - пакеты теряются или файрвол дропает; клиент ретранслирует SYN несколько раз и сдаётся (~3 минуты по дефолту)
- SYN+но нет ACK - половина handshake'а застряла; на стороне сервера это SYN_RECV state. Если таких много - атака SYN-flood
- SYN cookieы - защита от SYN-flood: сервер не аллоцирует state до получения ACK, а кодирует ISN в cookie
Что после
Сразу после ACK обычно идёт первый data-пакет (HTTP GET, TLS ClientHello, etc.). На loopback это всё происходит за микросекунды - на WAN добавляется RTT × 1.5 как минимум (handshake + первый запрос).