Итератор
Главная/Библиотека/Рукопожатие TCP
Сети 21 марта 2026 14 мин чтения Направление «Сети», модуль 03

Трёхстороннее рукопожатие TCP

Три пакета, которыми начинается любое TCP-соединение. Разбираем, зачем нужен каждый, откуда берутся номера последовательности и как всё это связано с таймаутами, которые вы видите в логах HTTP-клиента.

Почти каждый разработчик может назвать три пакета: SYN, SYN-ACK, ACK. Гораздо реже удаётся ответить на вопрос, почему пакетов именно три, а не два и не четыре. Этот разбор отвечает на него, а заодно объясняет несколько вещей, которые из-за непонимания рукопожатия выглядят загадочно: отказы под нагрузкой, зависающие на минуту подключения и странные настройки ядра, которые советуют в интернете.

Зачем вообще рукопожатие

TCP обещает две вещи: данные дойдут и дойдут в том порядке, в котором их отправили. Чтобы это обеспечить, каждому байту присваивается номер, а получатель подтверждает, какие номера он принял.

Значит, до передачи данных стороны должны договориться, с какого номера начинается нумерация. Причём обе: TCP-соединение двунаправленное, и у каждого направления своя независимая нумерация.

Отсюда и берётся число три. Нужно передать два начальных номера и подтвердить оба:

  • клиент сообщает свой начальный номер;
  • сервер подтверждает его и сообщает свой;
  • клиент подтверждает номер сервера.

Второй и третий шаги совмещают подтверждение и объявление в одном пакете — поэтому рукопожатие трёхстороннее, а не четырёхстороннее. Разрыв соединения, где такого совмещения обычно нет, как раз занимает четыре пакета.

Клиент Сервер SYN · seq=x SYN+ACK · seq=y, ack=x+1 ACK · ack=y+1 SYN_SENT SYN_RECEIVED ESTABLISHED ESTABLISHED первые данные — можно слать вместе с третьим пакетом
Обмен пакетами и состояния обеих сторон. Данные клиента могут ехать уже в третьем сегменте — это разрешено стандартом, хотя так делают не все реализации.

Начальные номера и почему они случайные

Начальный номер последовательности (ISN, initial sequence number) — не ноль. Если бы каждое соединение начиналось с нуля, возникли бы две проблемы.

Первая — старые пакеты. Сегмент из предыдущего соединения между той же парой адресов и портов может задержаться в сети и прийти уже после того, как соединение закрылось и открылось заново. С нулевым ISN такой сегмент попал бы в допустимое окно и был бы принят как часть нового потока.

Вторая — подделка. Зная, что нумерация начинается с нуля, кто угодно собрал бы пакет с правильным номером и вклинился в чужое соединение, не видя трафика.

Поэтому RFC 6528 предписывает вычислять ISN как сумму монотонно растущего таймера и криптографической хеш-функции от четвёрки «адрес источника, порт источника, адрес назначения, порт назначения» и секретного ключа хоста. Номер остаётся непредсказуемым извне, но для одной и той же пары сокетов растёт со временем — это и защищает от старых пакетов.

Проверить руками

Запустите tcpdump -n -i any 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0 and port 443' и откройте любой сайт. В выводе будут видны Flags [S], Flags [S.] и Flags [.] — те самые три пакета, с относительными номерами последовательности.

Две очереди на сервере

Между приходом SYN и вызовом accept() в вашем коде соединение живёт в ядре. Там его ждут две очереди, и путать их — распространённая ошибка.

ОчередьЧто в нейЧем ограничена
SYN queue
полуоткрытые
Соединения в состоянии SYN_RECEIVED: сервер ответил SYN-ACK и ждёт третий пакет net.ipv4.tcp_max_syn_backlog
Accept queue
установленные
Рукопожатие завершилось, соединение ждёт, пока приложение вызовет accept() аргумент backlog в listen(), сверху ограничен net.core.somaxconn

Отсюда два разных вида отказа. Если переполнена accept queue — ваше приложение не успевает разбирать соединения: оно занято работой, заблокировано на вводе-выводе или просто однопоточное. Увеличение backlog здесь лечит симптом, а не причину.

Если переполнена SYN queue — до завершения рукопожатия не доходит значительная часть попыток. Это либо потери в сети, либо SYN-флуд.

SYN-флуд и cookies

Асимметрия рукопожатия очевидна: отправить SYN стоит одного пакета, а ответить на него — выделить запись в SYN queue и держать её, пока не придёт третий пакет или не истечёт таймер. Атакующему дёшево, защищающемуся дорого.

Классическая атака — поток SYN-пакетов с подменённых адресов. Третий пакет не придёт никогда, записи копятся, очередь переполняется, настоящие клиенты получают отказ.

Ответ на это — SYN cookies. Когда очередь переполняется, сервер перестаёт хранить состояние: он кодирует всё необходимое прямо в ISN, который отправляет в SYN-ACK. Это хеш от четвёрки адресов, грубой временной метки и секрета плюс закодированный MSS. Если клиент настоящий, он вернёт ack = ISN + 1, и сервер восстановит параметры из самого номера, ничего не храня.

Цена

В ISN помещается немного: MSS кодируется тремя битами из таблицы, а опции масштабирования окна, SACK и timestamps при включённых cookies теряются. Поэтому cookies — аварийный режим, а не постоянная настройка: соединения, установленные через них, работают медленнее.

Откуда берётся минутное ожидание

Теперь то, ради чего это чаще всего гуглят. Если SYN не получил ответа, клиент повторяет его с экспоненциальной задержкой: 1 с, 2 с, 4 с, 8 с, 16 с, 32 с. В Linux число попыток задаётся net.ipv4.tcp_syn_retries и по умолчанию равно шести.

Сумма интервалов — 63 секунды, плюс ожидание последнего ответа. Отсюда знаменитые «примерно 127 секунд» до того, как connect() вернёт ETIMEDOUT.

Практический вывод: если ваш HTTP-клиент не задаёт собственный таймаут на установку соединения, он унаследует этот. Запрос, который «должен отвечать за 200 мс», при недоступном узле будет висеть две минуты, держать поток и съедать место в пуле.

Важно и то, что это тишина, а не отказ. Если бы на том конце был живой хост с закрытым портом, пришёл бы RST и connect() упал бы с ECONNREFUSED мгновенно. Долгое ожидание означает, что пакеты уходят в никуда: фильтрующий брандмауэр, выключенная машина или ошибка маршрутизации.

Коротко

  • Пакетов три, потому что начальных номеров два и каждый нужно подтвердить.
  • ISN непредсказуем, чтобы отсечь задержавшиеся пакеты прошлых соединений и затруднить подделку.
  • У сервера две очереди; их переполнение означает разные проблемы и лечится по-разному.
  • SYN cookies спасают от флуда ценой потери опций соединения — это аварийный режим.
  • Всегда задавайте таймаут на установку соединения. Иначе он равен примерно двум минутам.

Источники

  • RFC 9293 — Transmission Control Protocol, разделы 3.5 и 3.10
  • RFC 6528 — Defending against Sequence Number Attacks
  • RFC 4987 — TCP SYN Flooding Attacks and Common Mitigations
  • Документация ядра Linux: Documentation/networking/ip-sysctl.rst

Нашли ошибку или неточность? Напишите на redakciya@example.invalid — фактические ошибки мы правим в течение недели.