Files

44 KiB
Raw Permalink Blame History

D5, часть 2. TCP глубоко (урок)

Это не проверка, а урок: сначала разбираем механизм, потом решаешь задачи. Разбор идёт от байтов заголовка к состояниям соединения, затем к механизмам надёжности и скорости, и в конце к соседним протоколам (UDP, DNS, DHCP, NAT), которые решают то, что TCP не решает.

1. Заголовок TCP: 20 байт, поле за полем

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port         |       Destination Port       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Offset| Rsvd|C E U A P R S F|            Window             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum           |        Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Фиксированная часть — ровно 20 байт, это минимум заголовка TCP-сегмента (тот же минимум 20 байт, что и у IP-заголовка, вложенного на уровень ниже). Поля:

  • Source Port / Destination Port — по 16 бит каждый (порты 0–65535).
  • Sequence Number — 32 бита, номер первого байта данных в этом сегменте.
  • Acknowledgment Number — 32 бита, номер следующего ожидаемого байта от собеседника.
  • Data Offset — 4 бита, длина заголовка в 32-битных словах (максимум 15×4 = 60 байт, значит опции — максимум 60 − 20 = 40 байт).
  • Флаги — по одному биту: SYN (установка соединения), ACK (подтверждение), FIN (корректное закрытие направления), RST (аварийный сброс), PSH (передать данные приложению немедленно, не буферизуя), URG (часть данных помечена срочной через Urgent Pointer); плюс ECE/CWR — сигнализация перегрузки сети (ECN, RFC 3168), делит 6 «резервных» бит исходного RFC 793 на 4 действительно резервных и 2 флаговых.
  • Window — 16 бит, объявляемый размер приёмного окна (flow control, раздел 6).
  • Checksum — 16 бит, контрольная сумма заголовка+данных+псевдозаголовка IP.
  • Urgent Pointer — 16 бит, действителен только при выставленном URG.
  • Options — переменная длина, до 40 байт; сюда входит MSS (Maximum Segment Size) — опция, которой стороны обмениваются только в SYN-сегментах, сообщая максимальный размер сегмента, который готовы принять.

Факты для карточек

  • base | Минимальный размер заголовка TCP? — 20 байт
  • base | Сколько бит занимает порт в заголовке TCP? — 16 бит (диапазон 0–65535)
  • core | Максимальный размер опций TCP-заголовка и почему именно столько? — 40 байт, потому что Data Offset (4 бита) кодирует длину заголовка в 32-битных словах максимум 15×4=60 байт, минус 20 байт фиксированной части
  • core | В каких сегментах передаётся опция MSS? — только в сегментах с флагом SYN, при установке соединения
  • deep | Какие два флага TCP появились позже исходного RFC 793 и для чего? — ECE и CWR (RFC 3168), сигнализация перегрузки сети (ECN) вместо/вместе с потерей пакета

Почему дальше: поля Sequence/ACK Number и флаг SYN используются в первую очередь при установке соединения — разберём этот обмен по шагам.

2. Установка соединения: three-way handshake

  1. Клиент → серверу: сегмент с флагом SYN и собственным начальным порядковым номером (ISN, Initial Sequence Number).
  2. Сервер → клиенту: сегмент с флагами SYN+ACK — подтверждает ISN клиента (Ack = ISN_клиента + 1) и присылает собственный ISN.
  3. Клиент → серверу: сегмент с флагом ACK, подтверждающим ISN сервера.

Три шага, а не два, нужны потому, что соединение TCP полнодуплексное — у каждого направления свой независимый ISN, и каждая сторона должна не только сообщить свой ISN, но и получить подтверждение, что собеседник его получил. Два шага (SYN → SYN-ACK) недостаточно: сервер не может быть уверен, что его SYN-ACK дошёл до клиента, пока не получит финальный ACK. После рукопожатия обе стороны знают начальные номера друг друга и могут независимо отслеживать доставку и порядок байт в каждом направлении.

Ловушки

  • Файрвол блокирует ответный ACK клиента → сервер зависает в состоянии SYN_RECEIVED (полуоткрытое соединение) → видно как одинокий SYN без завершающего ACK в tcpdump и запись в ss -tan.

Факты для карточек

  • base | Сколько сегментов в three-way handshake? — 3 (SYN, SYN+ACK, ACK)
  • core | Почему для установки TCP-соединения недостаточно двух сегментов? — соединение полнодуплексное, серверу нужно подтверждение, что его SYN-ACK (и его ISN) реально дошёл до клиента
  • core | Что означает ISN и синхронизируется ли он в одном экземпляре на оба направления? — начальный порядковый номер; нет, у каждого направления свой собственный ISN

Почему дальше: раз соединение открывается тремя сегментами и переходит через промежуточные состояния (SYN_SENT, SYN_RECEIVED), логично разобрать полный набор состояний TCP как конечный автомат.

3. Состояние-машина TCP

  • CLOSED — соединения нет.
  • LISTEN — сервер ждёт входящих SYN.
  • SYN_SENT — клиент отправил SYN, ждёт SYN-ACK.
  • SYN_RECEIVED — сервер получил SYN, отправил SYN-ACK, ждёт финальный ACK.
  • ESTABLISHED — соединение открыто, идёт обмен данными.
  • FIN_WAIT_1 — эта сторона отправила FIN, ждёт ACK на него.
  • FIN_WAIT_2 — FIN подтверждён, эта сторона ждёт FIN от собеседника.
  • CLOSE_WAIT — получен FIN от собеседника, эта сторона ещё может досылать данные.
  • LAST_ACK — эта сторона отправила свой FIN (после CLOSE_WAIT), ждёт последний ACK.
  • TIME_WAIT — сторона, отправившая финальный ACK, ждёт 2×MSL перед освобождением сокета.
  • CLOSING — оба конца отправили FIN почти одновременно, редкий путь одновременного закрытия.

Автомат асимметричен по конструкции: клиент и сервер проходят разные пути (SYN_SENT только у инициатора, SYN_RECEIVED/LISTEN только у принимающей стороны), потому что роли в рукопожатии разные. ss -tan показывает текущее состояние сокета в столбце State — это прямое отражение позиции в этом автомате, а не абстракция.

Факты для карточек

  • base | В каком состоянии сервер ждёт входящие подключения? — LISTEN
  • core | Чем отличаются пути клиента и сервера в конечном автомате TCP? — клиент проходит SYN_SENT, сервер — LISTEN и SYN_RECEIVED; роли в рукопожатии асимметричны
  • core | Какой командой в Linux видно текущее состояние TCP-сокета? — ss -tan (столбец State)

Почему дальше: часть состояний (FIN_WAIT_*, CLOSE_WAIT, LAST_ACK, TIME_WAIT) относится к закрытию соединения — разберём эту последовательность отдельно, она сложнее открытия.

4. Закрытие в четыре шага и TIME_WAIT

  1. Сторона A, завершившая передачу, шлёт FIN.
  2. Сторона B подтверждает его ACK (A уходит в FIN_WAIT_2, B — в CLOSE_WAIT).
  3. Когда сторона B тоже готова закрыться, она шлёт свой FIN.
  4. Сторона A подтверждает финальным ACK (A уходит в TIME_WAIT, B — в CLOSED сразу после получения этого ACK).

Четыре сегмента, а не два, — потому что закрытие каждого направления независимо (полузакрытие, half-close): получение FIN от B означает только «B больше не пришлёт данных», но A может продолжать досылать данные в обратном направлении, прежде чем закрыть свою половину. FIN идёт в одну сторону за раз именно поэтому — это закрытие конкретного направления потока, а не всего соединения разом.

Сторона, отправившая последний ACK (то есть первой инициировавшая закрытие), уходит в TIME_WAIT и ждёт там 2×MSL (Maximum Segment Life) перед освобождением сокета. Зачем: эта сторона должна поймать задержавшиеся в сети дубликаты старых сегментов (если порт освободить сразу и тут же переиспользовать для нового соединения, устаревший сегмент может быть по ошибке принят как часть новой сессии) и быть готовой повторно отправить последний ACK, если он потерялся и партнёр повторяет свой FIN. RFC 793 определяет номинальный MSL = 2 минуты (отсюда 2×MSL = 4 минуты), но в Linux TIME_WAIT реализован как фиксированный таймаут 60 секунд (константа TCP_TIMEWAIT_LEN в ядре) независимо от настраиваемого MSL. Куча накопившихся TIME_WAIT-сокетов на активном сервере — видимая проблема (ss -tan state time-wait), решается через SO_REUSEADDR или снижением частоты пересоздания соединений.

Ловушки

  • Считать TIME_WAIT «багом» и убирать его целиком (агрессивные настройки reuse) → сервер начинает принимать дубликаты старых сегментов как часть новых соединений → редкие, трудно воспроизводимые повреждения данных на высоконагруженных коротких соединениях.

Факты для карточек

  • base | Сколько сегментов нужно для полного закрытия TCP-соединения? — 4 (FIN, ACK, FIN, ACK)
  • base | Формула длительности TIME_WAIT? — 2×MSL
  • core | Почему закрытие TCP асимметрично («полузакрытие»), а не мгновенное закрытие по первому FIN? — соединение дуплексное, получение FIN означает только «собеседник больше не пришлёт данные», но сама сторона может ещё дописывать данные в обратном направлении
  • deep | Сколько секунд реально длится TIME_WAIT в Linux и совпадает ли это с 2×MSL по RFC 793? — 60 секунд, фиксированная константа ядра; не совпадает с номинальными 4 минутами (2×2 мин) по RFC 793

Почему дальше: FIN — это вежливое закрытие. Разберём флаг, который сигнализирует не закрытие по согласию, а ошибку или невозможность продолжить, — RST.

5. RST: аварийный сброс, а не закрытие

RST сигнализирует ошибку или невозможность продолжить соединение — в отличие от вежливого FIN, тишины не будет. Типичный случай: попытка подключиться к закрытому порту получает в ответ именно RST, а не молчание — так клиент сразу узнаёт, что порт не слушает, вместо ожидания таймаута. Если приложение получает RST там, где ожидало нормальное закрытие через FIN, это обычно означает, что сокет на другой стороне был закрыт грубо (например, процесс убит) — классический симптом «connection reset by peer» в логах, подтверждается флагом RST в последнем сегменте дампа tcpdump.

Факты для карточек

  • base | Что получает клиент в ответ на попытку подключиться к закрытому порту? — RST
  • core | Чем RST принципиально отличается от FIN по смыслу? — RST — аварийный немедленный сброс (ошибка/невозможность продолжить), FIN — согласованное закрытие направления

Почему дальше: SYN, FIN, RST — это управление соединением. Отдельный вопрос — как TCP поверх этого управления гарантирует, что данные точно дойдут и в правильном порядке.

6. Надёжность: ACK, окно, flow control против congestion control

TCP строит надёжный упорядоченный байтовый поток поверх ненадёжной доставки IP:

  • Каждый байт данных нумеруется порядковым номером (Sequence Number).
  • Получатель подтверждает принятые данные кумулятивным ACK — Ack Number означает «я получил всё непрерывно вплоть до этого байта», а не «я получил именно этот сегмент».
  • Если подтверждение не пришло за таймаут (RTO, раздел 7) — отправитель ретранслирует.
  • Сегменты, пришедшие не по порядку, получатель буферизует и переупорядочивает перед тем, как отдать данные приложению.
  • Окно скольжения (sliding window) — отправитель держит в полёте сразу много неподтверждённых байт, не дожидаясь ACK на каждый сегмент отдельно; иначе пришлось бы ждать полный RTT на каждый пакет.

TCP регулирует скорость передачи двумя разными механизмами, которые легко перепутать:

  • Flow control (управление получателем, поле Window) — сколько байт получатель готов принять прямо сейчас. Защищает получателя от переполнения его приёмного буфера: если приложение читает данные медленно, окно сужается, вплоть до нуля («TCP Zero Window» в tcpdump — передача полностью останавливается).
  • Congestion control (управление сетью, cwnd — congestion window) — сколько отправитель может слать, не перегружая сеть между узлами, о состоянии которой напрямую ничего не известно. Работает по схеме AIMD (Additive Increase, Multiplicative Decrease):
    • Slow start — cwnd стартует с малого значения (исторически 1 MSS, современный RFC 6928 разрешает стартовое окно до 10 MSS) и удваивается каждый RTT, пока не достигнет порога ssthresh или не случится потеря.
    • Congestion avoidance — после ssthresh рост становится линейным: cwnd растёт примерно на 1 MSS за RTT (аддитивное увеличение).
    • Fast retransmit — 3 повторных (дублирующих) ACK на один и тот же номер сегмента трактуются как сигнал потери без ожидания полного таймаута RTO — ретрансмиссия начинается немедленно.
    • При потере: ssthresh = cwnd / 2, cwnd тоже уменьшается (мультипликативное уменьшение). Потеря вдвое режет cwnd, а не сбрасывает в ноль, потому что потеря одного сегмента — сигнал «сеть перегружена сейчас», а не «сеть недоступна»; резкое падение до минимума впустую потратило бы уже проверенную пропускную способность. Полный сброс cwnd к минимуму происходит отдельно — при таймауте RTO (более серьёзный сигнал, чем дублирующие ACK).

Действующий по умолчанию в Linux алгоритм congestion control — CUBIC (с ядра 2.6.19), более сложная функция роста cwnd от времени, чем классический AIMD Reno, но сама идея «расти, пока не потеряли, резко сократиться при потере» сохраняется.

Ловушки

  • Перепутать flow control (Window) с congestion control (cwnd) → неверный ответ на вопрос «почему передача остановилась»: Zero Window — проблема медленного читателя на приёмнике, просевший cwnd — проблема сети между узлами, у них разная диагностика и разное решение.

Факты для карточек

  • base | Чем измеряется flow control в заголовке TCP? — полем Window
  • core | Чем отличается flow control от congestion control по цели? — flow control защищает получателя от переполнения буфера, congestion control защищает сеть от перегрузки
  • core | Во сколько раз падает cwnd при обнаруженной потере? — вдвое (ssthresh = cwnd/2)
  • core | Что такое fast retransmit? — ретрансмиссия по 3 дублирующим ACK без ожидания полного таймаута RTO
  • deep | Какой алгоритм congestion control используется в Linux по умолчанию? — CUBIC

Почему дальше: и ретрансмиссия по таймауту, и fast retransmit опираются на измеренное время кругового пути — разберём, как считается сам таймаут RTO.

7. Таймеры ретрансмиссии: RTO и RTTVAR

Таймаут ретрансмиссии (RTO) не фиксированное число — он адаптируется под измеренный round-trip time (RTT) конкретного соединения. По алгоритму Джекобсона/Карелса (RFC 6298):

  • SRTT (сглаженный RTT) обновляется как экспоненциальное скользящее среднее с коэффициентом α = 1/8 от нового измерения.
  • RTTVAR (вариация RTT) обновляется как скользящее среднее отклонения |SRTT − RTT_sample| с коэффициентом β = 1/4.
  • RTO = SRTT + 4 × RTTVAR.

Множитель 4 у RTTVAR — запас на случай, если сеть внезапно станет менее стабильной (большой разброс задержек), чтобы не срабатывать ложно на обычный джиттер, но и не ждать избыточно долго при реальной потере. Фиксированный RTO (без адаптации под RTT) не работает — RTT локальной сети и RTT через несколько континентов различаются на порядки, единое число либо слишком долго ждёт в быстрой сети, либо слишком рано ретранслирует в медленной.

Факты для карточек

  • core | Формула RTO по Джекобсону/Карелсу? — SRTT + 4×RTTVAR
  • deep | Какие коэффициенты сглаживания используются для SRTT и RTTVAR? — α=1/8 для SRTT, β=1/4 для RTTVAR

Почему дальше: RTO касается решения «когда переслать заново». Отдельный, более локальный таймер решает более мелкий вопрос — отправлять ли данные прямо сейчас маленьким куском или подождать и накопить.

8. Nagle и TCP_NODELAY

Алгоритм Нейгла по умолчанию задерживает отправку маленьких сегментов, пока не придёт ACK на предыдущие неподтверждённые данные или не накопится достаточно данных для полного сегмента — цель в том, чтобы не засорять сеть множеством мелких пакетов (несколько байт полезной нагрузки на 20 байт TCP-заголовка — плохое соотношение). Флаг сокета TCP_NODELAY отключает эту задержку — данные уходят сразу, как только приложение вызвало write/send.

Nagle плохо сочетается с delayed ACK (получатель тоже не спешит слать ACK, ждёт немного — вдруг появятся данные для отправки в обратную сторону, тогда ACK можно приклеить к ним) — классический сценарий из статьи Кларка 1982 года даёт задержки порядка сотен миллисекунд: отправитель ждёт ACK, чтобы послать следующий маленький кусок, получатель ждёт данные, чтобы не слать ACK отдельно — оба ждут друг друга. Для интерактивных протоколов с мелкими, чувствительными к задержке сообщениями (например, построчный ввод в интерактивном сеансе) TCP_NODELAY — стандартная практика.

Факты для карточек

  • base | Что делает флаг TCP_NODELAY? — отключает алгоритм Нейгла, данные отправляются сразу без задержки на накопление
  • core | Почему Nagle + delayed ACK вместе дают заметные задержки? — обе стороны ждут друг друга: отправитель — ACK перед следующей мелкой отправкой, получатель — данные для отправки в обратную сторону, чтобы не слать ACK отдельно

Почему дальше: TCP — не единственный транспортный протокол. Разберём его прямую противоположность по философии — UDP, у которого почти ничего из разобранного выше просто нет.

9. UDP: 8 байт, без гарантий

Заголовок UDP — всего 8 байт: порт источника, порт назначения, длина, контрольная сумма — и всё; никаких порядковых номеров, подтверждений или окна, как у TCP. Нет установки соединения, нет гарантии доставки, порядка или отсутствия дублей — датаграмма либо доходит, либо нет, молча. Такая простота осознанная: приложениям, которым важнее низкая задержка и минимум накладных расходов, чем гарантия каждого байта, не нужен вес состояния соединения и ретрансмиссий.

Где уместен UDP:

  • DNS — короткий запрос-ответ, переспросить целиком дешевле, чем ждать TCP-ретрансмиссию.
  • RTP (голос/видео реального времени) — устаревший потерянный кадр всё равно бесполезен, ждать его повторной доставки хуже, чем пропустить.
  • QUIC — строит собственную надёжность и порядок поверх UDP на прикладном уровне, обходя то, что классический TCP жёстко зашивает в ядро (head-of-line blocking на уровне сегментов).

Факты для карточек

  • base | Размер заголовка UDP? — 8 байт
  • base | Какие поля есть в заголовке UDP? — порт источника, порт назначения, длина, контрольная сумма
  • core | Почему DNS исторически использует UDP, а не TCP? — типичный запрос-ответ короткий и умещается в одну датаграмму, устанавливать TCP-соединение ради одного маленького обмена избыточно медленно

Почему дальше: TCP и UDP используют один и тот же числовой идентификатор приложения на узле — порт. Разберём, как устроено адресное пространство портов.

10. Порты: три диапазона

  • 0–1023 — Well-known / System Ports: закреплены за стандартными службами (22 SSH, 53 DNS, 80 HTTP, 443 HTTPS) — клиент заранее знает, куда стучаться.
  • 1024–49151 — Registered Ports: регистрируются IANA за конкретными приложениями, но без такой строгой резервации, как первый диапазон.
  • 49152–65535 — Dynamic/Private Ports (ephemeral): ОС временно выделяет их клиентским сокетам на время соединения, не привязывая ни к какой конкретной службе.

Два процесса не могут одновременно слушать один и тот же порт на одном интерфейсе — второй вызов bind завершится ошибкой «Address already in use»; частая причина, по которой сервис не поднимается после аварийного перезапуска — старый процесс ещё держит порт (либо сокет всё ещё в TIME_WAIT).

Факты для карточек

  • base | Диапазон well-known портов? — 0–1023
  • base | В каком диапазоне ОС обычно выделяет эфемерные порты клиентским соединениям? — 49152–65535
  • core | Что вернёт второй bind на уже занятый порт? — ошибку «Address already in use»

Почему дальше: порт определяет приложение на узле, но не решает проблему нехватки IPv4-адресов для самих узлов — этим занимается NAT.

11. NAT: таблица трансляций и почему ломается P2P

Маршрутизатор на исходящем пакете подменяет внутренний адрес источника на свой внешний и запоминает в таблице трансляций соответствие «внутренний IP:порт — внешний IP:порт»; на входящий ответный пакет ищет по этой таблице нужного внутреннего получателя и подменяет адрес обратно. NAT возник как практическое решение нехватки публичных IPv4-адресов: 32-битного пространства не хватает на все устройства мира, а один внешний адрес может обслуживать целую локальную сеть, различая внутренние узлы по номеру порта (PAT, Port Address Translation).

Почему ломается P2P: узел за NAT не имеет собственного публичного адреса и не может принимать входящие соединения без явной настройки (проброс портов, -p в Docker — тот же принцип) — входящий пакет от нового, незнакомого узла просто не с чем сопоставить в таблице трансляций, её запись создаётся только исходящим трафиком. Диагностируется тем, что снаружи виден только внешний адрес роутера, а не внутренний узел.

Факты для карточек

  • base | Что делает NAT с исходящим пакетом? — подменяет внутренний IP:порт источника на внешний IP:порт, запоминая соответствие в таблице трансляций
  • core | Почему NAT ломает входящие P2P-соединения без проброса портов? — запись в таблице трансляций создаётся только исходящим трафиком, входящему от незнакомого узла не с чем сопоставиться

Почему дальше: NAT решает адресацию узлов, но перед этим узел нужно ещё найти по имени — разберём DNS.

12. DNS: порт 53, рекурсия, типы записей, TTL

Клиент отправляет запрос резолверу через UDP на порт 53 (типичный ответ умещается в один пакет, соединение не нужно); резолвер либо отвечает из кэша, либо рекурсивно опрашивает корневые, затем доменные (TLD), затем авторитативные серверы, пока не получит финальный ответ. Если ответ не помещается в стандартный размер UDP-датаграммы (передача зоны, большие DNSSEC-записи), DNS переключается на TCP/53, где нет ограничения на размер одного пакета.

Основные типы записей: A (имя → IPv4-адрес), AAAA (имя → IPv6-адрес), MX (почтовый сервер домена, с приоритетом). Каждая запись несёт TTL — время в секундах, на которое резолверам разрешено кэшировать запись без повторного запроса к авторитативному серверу; меньший TTL — быстрее распространяются изменения (например, при смене IP сервиса), но больше нагрузка на DNS-инфраструктуру повторными запросами.

Факты для карточек

  • base | Порт DNS по умолчанию и протокол? — 53, UDP (переключение на TCP/53 для больших ответов)
  • base | Что хранит запись типа A? — соответствие имени домена IPv4-адресу
  • core | Зачем у DNS-записи есть TTL? — ограничивает время кэширования резолверами; компромисс между скоростью распространения изменений и нагрузкой повторными запросами

Почему дальше: DNS резолвит имя в адрес, но сам адрес узлу тоже нужно откуда-то получить при подключении к сети — этим занимается DHCP.

13. DHCP: порты 67/68, схема DORA

DHCP-сервер слушает UDP-порт 67, клиент — UDP-порт 68. Обмен идёт по схеме DORA: Discover (узел широковещательно ищет сервер) → Offer (сервер предлагает адрес) → Request (узел подтверждает выбор) → Ack (сервер закрепляет адрес на ограниченный срок аренды — lease, который нужно периодически продлевать). Автоматизация нужна потому, что вручную прописывать уникальный IP на каждое устройство в сети из сотен узлов неуправляемо и чревато конфликтами адресов при ошибке администратора.

Факты для карточек

  • base | Порты DHCP-сервера и клиента? — сервер 67/UDP, клиент 68/UDP
  • core | Из каких четырёх шагов состоит DORA? — Discover, Offer, Request, Ack

Почему дальше: всё разобранное выше — это то, что реально видно в байтах на проводе; разберём инструмент, которым эти байты читают напрямую.

14. tcpdump и Wireshark: как читать дамп

tcpdump захватывает пакеты на интерфейсе и печатает их построчно (или пишет в файл для Wireshark). Базовые приёмы:

  • Фильтр по порту: tcpdump tcp port 80 — только TCP-трафик на порту 80 в любую сторону.
  • Сохранение в файл для последующего анализа в Wireshark: tcpdump -w capture.pcap.
  • Чтение handshake в выводе: строка с флагом [S] (SYN) от клиента, [S.] (SYN-ACK) от сервера, [.] (ACK) от клиента — три строки подряд с растущими seq/ack номерами это и есть three-way handshake, ровно как в разделе 2.
  • Закрытие видно как пара [F.] (FIN+ACK) с обеих сторон, каждый подтверждён отдельным [.].
  • Одинокий [S] без ответа — недоступный порт или заблокированный файрволом ACK (раздел 2, ловушка SYN_RECEIVED).
  • Флаг [R] — RST, аварийный сброс (раздел 5).

Факты для карточек

  • base | Команда для захвата TCP-трафика на 80 порту? — tcpdump tcp port 80
  • base | Флаг tcpdump для сохранения дампа в файл? — -w
  • core | Как в выводе tcpdump выглядит three-way handshake? — три строки подряд: [S] от клиента, [S.] от сервера, [.] от клиента

Ссылки на задачи этого дня: tasks/04_ipv4 — разбор IPv4-заголовка и контрольной суммы (нижний уровень относительно TCP, инкапсулирующий его); задачи чтения дампов — применение раздела 14 на реальных .pcap.

Проверь себя
  1. В tcpdump видно: [S] от клиента, [S.] от сервера, дальше тишина — ACK от клиента не приходит. В каком состоянии завис сервер и почему?

    Ответ`SYN_RECEIVED` — сервер получил SYN, отправил SYN-ACK, но финальный ACK не дошёл (например, заблокирован файрволом), поэтому рукопожатие не завершилось и сервер ждёт третий сегмент.
  2. Почему TIME_WAIT длится именно 2×MSL, а не произвольное короткое время вроде 1 секунды?

    ОтветСторона, закрывшая соединение последней, должна успеть поймать задержавшиеся в сети дубликаты старых сегментов (которым отводится время жизни MSL на путь туда и обратно — отсюда удвоение) и быть готовой повторно отправить последний ACK, если его потеря заставит партнёра повторить FIN. Слишком короткий таймаут рискует освободить порт раньше, чем дубликат добежит и будет ошибочно принят новым соединением.
  3. Клиент шлёт данные маленькими кусками через write() без TCP_NODELAY, сервер использует delayed ACK. Почему передача может ощутимо тормозить, хотя пропускной способности канала достаточно?

    ОтветАлгоритм Нейгла на клиенте задерживает отправку следующего маленького куска, пока не придёт ACK на предыдущий; сервер с delayed ACK не спешит слать этот ACK отдельно, ожидая данных для отправки в обратную сторону — обе стороны ждут друг друга, и задержка растёт не от нехватки полосы, а от этого взаимного ожидания.
  4. При передаче по сети с потерями каждые несколько RTT срабатывает fast retransmit. Что происходит с cwnd при этом и почему не сбрасывается до минимального значения, как при таймауте RTO?

    Ответ`ssthresh` и `cwnd` уменьшаются вдвое (`cwnd/2`), а не до минимума — 3 дублирующих ACK означают, что сеть в целом жива и часть сегментов всё же доходит, это более мягкий сигнал перегрузки, чем полное отсутствие ответа при таймауте RTO, поэтому реакция мягче.

Материалы