---
title: "Видео 9. Фокусы рукопожатия"
date: 2026-10-26
tags:
  - zapret2
  - видео
  - сценарий
  - syndata
  - synack
  - wssize
  - oob
aliases:
  - "Видео 9"
description: "Видео 9 серии о Zapret 2: приёмы на этапе установки соединения: syndata, synack, размер окна, urgent, с анимированными схемами."
---

> [!info] О чём заметка
> Это полный текст видео номер 9 из серии о том, как работает [[Zapret2/Zapret2|Zapret 2]], разбитый по сценам, с анимированными схемами. Каждую сцену можно читать отдельно.

> [!tip] Как читать пометки
> **«В нашем тесте»** значит настоящий запуск в лаборатории проекта: клиент, роутер, сервер. **«По коду»** значит, что мы прочитали исходники, но не запускали. **«По общим сведениям»** значит, что это вне нашего кода и лаборатории. О настоящем оборудовании ТСПУ мы ничего не утверждаем: наш «инспектор» это модель.


## Повтор и вопрос: что можно сделать до первого байта данных

Добро пожаловать в выпуск «Фокусы рукопожатия». Сначала короткий повтор, чтобы эту часть можно было смотреть отдельно от остальных.

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

Теперь договорённости о выражениях, они повторяются из выпуска в выпуск. «В нашем тесте» значит: мы запустили настоящую программу в нашей лаборатории, с клиентом, роутером и сервером, и посмотрели, что ушло на провод. Провод это запись на сетевой карте клиента, сделанная уже после программы. «По общим сведениям» значит: это общее знание, а не наш опыт и не наш код. «По счёту» значит: арифметика на бумаге, такого запуска у нас не было. И два журнала. Журнал программы это подробная отладочная запись самой программы о каждом пакете, мы включаем её во всех опытах с программой. Журнал игрушки это запись нашего игрушечного инспектора о каждом его решении.

Что мы делали раньше. В выпусках «multisplit: резать на части» и «Перемешать: disorder» программа резала приветствие на куски. В выпусках «Фейк и его условия» и «Резать и подделывать вместе» она клала рядом с настоящим пакетом подложный. Приветствие это первое сообщение защищённого соединения, у нас двести пятьдесят байт, и все эти приёмы работали над ним самим.

И слово, которое легко спутать. В выпуске «Фейк и его условия» мы договорились: «условие подделки», коротко «условие», это намеренный дефект подложного пакета, из-за которого получатель его выбросит или он до сервера не дойдёт, например срок жизни один или испорченная контрольная сумма. Подложный пакет, подделка и фейк в нашей серии одно и то же. Условие подделки не путайте с условием профиля из выпуска «Читаем командную строку zapret2»: то отбирает пакеты для профиля. Сегодня условия встретятся и у SYN с данными, и у сброса, а не только у фейка.

И слово «игрушка». Фраза «Игрушка, пример выдуманный» открывает картинку для понимания, вроде телефонного разговора: к лаборатории она отношения не имеет. А когда мы говорим просто «игрушка» про инспектора в роутере, это сокращение от «игрушечный инспектор», нашей модели наблюдателя. Инспектор у нас бывает разных «характеров», у каждого своё имя в кавычках; два первых разберём в следующей сцене.

Сегодня вопрос другой: что можно сделать раньше? До того как приветствие вообще уйдёт в сеть?

Игрушка, пример выдуманный. Телефонный разговор. Сначала три реплики: «вы меня слышите?», «слышу, а вы меня?», «и я слышу». Только после них разговор по делу. Подслушивающий записывает все три, но решения ещё не принял: ждёт первой фразы по делу. Что, если вставить лишнее слово в эту перекличку, сказать его не тому или сказать раньше положенного? Об этом выпуск.

В сети такая перекличка называется рукопожатием, мы разбирали её в выпуске «Что видит наблюдатель». Три пакета. Клиент отправляет SYN: «давай начнём, мои байты нумеруются отсюда». Сервер отвечает SYN-ACK: «согласен, мои нумеруются отсюда, твой номер принял». Клиент отвечает ACK: «слышу». Ни в одном из трёх нет данных.

Договоримся о номерах, они пойдут сразу. Как в выпусках «Что видит наблюдатель» и «multisplit: резать на части», номера в рассказе относительные: у SYN ноль, у первого байта данных один. Настоящие номера, большие числа из заголовка, мы будем называть настоящими, и это будет сказано прямо.

Посмотрим на наш обычный опыт, без стратегий, пакет за пакетом. Первый: SYN от клиента, номер ноль, данных нет. Второй: SYN-ACK от сервера, примерно через десять миллисекунд, подтверждение один. Третий: ACK от клиента, номер один. Четвёртый: ровно двести пятьдесят байт данных с номером один, это и есть приветствие.

![[wiki-anim-video09-s01-plain-handshake-ladder.svg|Обычное рукопожатие и приветствие]]

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

Проверь себя. Приветствие, четвёртый пакет на проводе. Каким по счёту оно будет среди пакетов самого клиента?

Третьим: сначала SYN, потом ACK, потом приветствие. Между SYN клиента и его ACK на проводе стоял пакет сервера, SYN-ACK. В счёт клиента он не идёт, а ACK и приветствие идут подряд. Запомним это: стратегии считают пакеты по сторонам, как мы видели в выпуске «Читаем командную строку zapret2».

![[wiki-anim-video09-s01-count-client-vs-wire.svg|Сколько пакетов у клиента]]

План части. Три приёма: данные внутри самого SYN, сброс соединения перед приветствием и подложный SYN-ACK, который клиент отправляет до собственного SYN. Для каждого посмотрим, какие пакеты уходят, что отвечает сервер и что делает наша игрушка. Остальные приёмы выпуска, срочный байт, окно и режим сервера, пойдут в следующих частях.

Итак. До приветствия на проводе три пакета без данных, и в них, вокруг них и между ними есть место для приёма. Начнём с самого прямого: положить данные в сам SYN. Но сначала одно описание лаборатории, потому что у неё в этом выпуске есть новая деталь.


## Лаборатория выпуска: круг в десять миллисекунд и игрушки двух видов

Вопрос сцены: чем и где мы смотрим на приёмы рукопожатия? Это нужно описать один раз, потому что все опыты выпуска идут в одной лаборатории.

Лаборатория знакома по прошлым выпускам. Клиент, роутер и сервер, и четыре точки, где мы записываем пакеты: цэ-ноль на сетевой карте клиента, после работы программы, эр-ноль на входе в роутер, эр-один на выходе, эс-ноль на сетевой карте сервера. Роутер вычитает единицу из срока жизни, так что пакет со сроком жизни один виден на входе в роутер и дальше не идёт.

Новая деталь одна. В прошлых выпусках сервер стоял рядом. Здесь на каждом выходе роутера мы добавили задержку в пять миллисекунд, так что круг, туда и обратно, занимает десять. Без неё в нашей лаборатории ответ сервера приходит раньше, чем клиент закончит выпускать свои пакеты, счётчики ядра клиента показывают «недействительные» пакеты, и результаты опытов с данными в SYN нестабильны. Почему так, мы по коду ядра не проверяли. С кругом в десять миллисекунд порядок один и тот же. По общим сведениям, в настоящей сети круг занимает десятки миллисекунд.

В нашем тесте это видно сразу. Клиент отправил SYN в нулевой момент, и SYN-ACK сервера пришёл к нему чуть позже десятой миллисекунды.

![[wiki-anim-video09-s02-lab-points-delay.svg|Лаборатория с кругом в десять миллисекунд]]

Теперь игрушечный инспектор. Это вторая копия программы nfqws2, она стоит в роутере на входе, до проверки срока жизни, и видит все пакеты в обе стороны. Она только выбрасывает пакеты, и каждое решение записывает в журнал. Её положение на входе в роутер это допущение нашей модели; где стоит настоящее оборудование, мы не знаем.

Игрушка, выдуманная. Охранник на воротах решает по первой бумаге, которую ему протянули, и записывает решение в журнал на всё время.

У нашей игрушки несколько характеров, в этой части мы встретим пять. Сейчас два. Первый называется «первый решает»: первый непустой пакет клиента решает судьбу соединения навсегда. Пакет с данными в SYN тоже считается. Если внутри есть имя www.example.com, соединение запрещено, и этот пакет с последующими выбрасываются. Если имени нет, соединение объявлено чистым навсегда, и дальше игрушка его не проверяет.

Этот характер вам знаком по выпуску «Фейк и его условия»: там инспектор тоже решал один раз, по первому пакету с данными, и стоял на том же месте, на входе в роутер, до проверки срока жизни. Отличие одно: здесь первым пакетом с данными может оказаться и SYN с данными.

Второй характер, «данные SYN не читает», делает то же самое, но данные внутри SYN пропускает без разбора и ждёт настоящих данных после рукопожатия. Остальные характеры будем знакомить по одному, когда они понадобятся.

Опорная точка. Опыт без стратегии с игрушкой «первый решает». Рукопожатие идёт как обычно, приветствие видно на цэ-ноль и на эр-ноль, а дальше роутера его нет. Клиент повторяет приветствие по таймеру, как в выпуске «Что видит наблюдатель», несколько раз за первые две секунды. Сервер получил ноль байт, клиент ответа не получил.

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

![[wiki-anim-video09-s02-toy-characters-reference.svg|Характеры игрушки и опыт без стратегии]]

Теперь приёмник. Это сервер на Linux версии шесть точка восемнадцать. У него два слоя: ядро принимает пакеты, а приложение читает то, что приняло ядро, и записывает, сколько байт пришло за каждое чтение. Обычное соединение: одно чтение, двести пятьдесят байт. Если после стратегии записано «двести пятьдесят одним чтением», значит данные дошли целиком.

Итак. Лаборатория: клиент, роутер, сервер, четыре точки съёмки и круг в десять миллисекунд. Игрушка у входа в роутер, у неё два знакомых нам характера, и каждый блокирует обычное соединение. Приёмник, сервер на Linux, записывает, сколько байт получил за каждое чтение. Теперь первый приём.


## syndata на проводе: SYN с шестнадцатью байтами

Вопрос сцены: что уходит в сеть, когда данные кладут в сам SYN, и что с ними делает сервер?

Игрушка, выдуманная. К телефонной перекличке «вы меня слышите?» снаружи приклеена записка. Подслушивающий читает её, потому что записка лежит на виду. А тот, кому звонят, слушает только слова и записку не читает: ждёт, пока начнётся разговор.

Зачем это нужно? Идея такая: если наблюдатель считает «первым пакетом с данными» любой пакет, где данные есть, то мусор в SYN может решить судьбу соединения вместо настоящего приветствия.

Теперь настоящий запуск. Строка короткая: фильтр вида данных «пусто», то есть действуем на пакеты без данных, SYN именно такой, и стратегия syndata. Назовём её «SYN с данными».

Первый пакет на проводе клиента. SYN, номер ноль, но данных в нём теперь шестнадцать байт, и все нули. Длина всего пакета по заголовку IP семьдесят шесть: двадцать байт заголовка IP, сорок байт заголовка TCP с опциями и шестнадцать данных. Это копия настоящего SYN, только с данными: номер, опции и окно те же.

Срок жизни шестьдесят четыре: этот пакет едет до сервера и виден на всех четырёх точках.

Второй пакет странный, объясним коротко, подробности ближе к концу выпуска. Сразу за первым идёт пакет в шестьдесят байт с номером протокола двести пятьдесят пять и сроком жизни один. Напомню про номер протокола: это байт в заголовке IP, у TCP он шесть, мы читали об этом в выпуске «Что видит наблюдатель». Двести пятьдесят пять, по общим сведениям, зарезервировано и ни за каким протоколом не закреплено. Его видят только цэ-ноль и эр-ноль: на роутере он умирает. Это обломок настоящего SYN: после заголовка IP в нём лежит заголовок TCP исходного SYN, изменены только номер протокола и срок жизни, и поэтому контрольная сумма IP неверна.

Откуда он? Программа получает каждый пакет из очереди ядра и отвечает ядру вердиктом: пропустить, изменить или выбросить; подробно это в выпуске «Внутри программы». Оригинальный SYN надо выбросить. Автор пишет в комментарии: если выбросить самый первый пакет в одном из режимов установки, запись о соединении в таблице ядра разрушается. Режим установки здесь, судя по комментарию, это случай, когда правило перехвата стоит после преобразования адресов, NAT из выпуска «Что видит наблюдатель». А таблица ядра, по общим сведениям, это его память о том, какие соединения сейчас открыты. Чтобы этого не случилось, программа отпускает вместо выброшенного его копию с испорченным номером протокола и сроком жизни один. Сообщения об истёкшем сроке для обломка в нашем тесте не было; почему, мы не проверяли.

Третий пакет: ответ сервера. SYN-ACK с подтверждением один. Запомним это число. SYN сам занимает один номер, мы это видели в выпуске «Что видит наблюдатель», так что подтверждение один значит: сервер принял только SYN, а шестнадцать байт в нём не принял. В нашем тесте сервер с Linux данные в SYN в поток не принял.

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

![[wiki-anim-video09-s03-syndata-wire-ladder.svg|SYN с шестнадцатью байтами на проводе]]

Теперь код, всего несколько строк. Проверка одна: у пакета из флагов SYN и ACK стоит только SYN. Дальше копия пакета, и её нагрузка это блоб, а если блоб не назван, шестнадцать нулевых байтов.

Потом копия уходит в сеть, и если отправка удалась, стратегия возвращает вердикт «выбросить» для оригинала. Между копией и отправкой стоят ещё два шага: применение условий, вроде срока жизни и испорченной суммы, и необязательная правка блоба; они понадобятся в следующих сценах. Разрезов и перестановок в этом коде нет. Построчно эту функцию разберём ближе к концу выпуска.

Проверь себя. Допустим, сервер принял бы шестнадцать байт из SYN. С какого номера пошло бы приветствие?

С семнадцатого: ноль, плюс один за сам SYN, плюс шестнадцать данных. У нас приветствие идёт с номера один, подтверждение равно единице: данные SYN в поток не попали.

![[wiki-anim-video09-s03-code-and-ack-check.svg|Код syndata и номер приветствия]]

Итак. SYN с данными это тот же SYN с шестнадцатью лишними байтами. Оригинал заменён обломком, который умирает на роутере. Сервер в нашем тесте данные из SYN не принял, подтвердил единицу, и приветствие в двести пятьдесят байт прошло без изменений. Теперь посмотрим, что об этом решают две наши игрушки.


## syndata и две игрушки: кого обманул SYN с данными

Вопрос сцены: кого из двух игрушек обманул SYN с данными?

Записка нам знакома: один привратник по ней решает про весь разговор, а другой её не читает.

Проверь себя. В SYN лежат шестнадцать нулей, имени сайта в них нет. Что решит про это соединение игрушка «первый решает»?

Что соединение чистое, навсегда. Проверим на настоящих пакетах, тем же приёмом. Журнал игрушки: первый непустой пакет клиента, длина шестнадцать, это SYN, имени нет; соединение объявлено чистым и больше не проверяется. Приветствие проходит все четыре точки, приложение сервера получает двести пятьдесят байт одним чтением и отвечает. Без стратегии та же игрушка блокировала то же приветствие. В нашем тесте её обманули.

Вторая игрушка, «данные SYN не читает». Журнал: на SYN запись «данные SYN игнорируются», а на приветствии «первый пакет с данными, двести пятьдесят байт, имя найдено, блокировать». Приветствие и его повторы видны на цэ-ноль и на эр-ноль и дальше роутера не идут. Сервер получил ноль байт. Пакеты на проводе те же самые, а исход другой.

![[wiki-anim-video09-s04-two-toys-same-wire.svg|Один пакет и два исхода]]

Что отсюда следует. Приём работает против игрушки, которая читает данные в SYN, и не работает против игрушки, которая их пропускает. Читает ли такие данные настоящее оборудование, мы не знаем. Вторая игрушка нужна, чтобы об этом допущении не забывать.

Теперь о том, что лежит в SYN. Блоб можно назвать, как в выпусках про фейк. Встроенная заготовка приветствия весит шестьсот восемьдесят байт, и SYN с ней по заголовку IP семьсот сорок: двадцать плюс сорок плюс шестьсот восемьдесят. Запретного имени игрушка «первый решает» в ней не находит и объявляет соединение чистым. Сервер снова получает наши двести пятьдесят байт.

Ещё два варианта. Блоб из двух байтов, в шестнадцатеричной записи «один шесть, ноль три»: SYN весит шестьдесят два байта, сервер ведёт себя так же. И блоб с именем, которого нет: программа пишет, что такого блоба нет, и пропускает пакет без изменений. Уходит обычный SYN без данных, соединение работает, но приёма в нём нет.

Блоб можно править аргументом tls_mod. Мы попросили три правки: случайные поля, повтор идентификатора сессии, он называется dupsid, и имя google.com. Идентификатор сессии это поле приветствия из выпуска «Что видит наблюдатель», а название tls_mod читается как «правка приветствия»: полностью её разберёт выпуск «Другие слои». Размер стал шестьсот семьдесят три: имя короче на семь байт. А dupsid ничего не сделал: с ним одним в SYN уходят те же шестьсот восемьдесят байт. Почему, объясним в сцене про код.

Последнее: SYN с данными вместе с multisplit. Сначала SYN, затем приветствие двумя пакетами, сто четыре байта и сто сорок шесть. Игрушка решила на SYN и частей приветствия не смотрела, сервер получил оба куска двумя чтениями. В нашем опыте приёмы друг другу не мешают: вызов syndata выключается сам на первом пакете, который не SYN, а разрез работает над приветствием.

![[wiki-anim-video09-s04-blobs-and-sizes.svg|Что лежит в SYN]]

Итак. SYN с данными обманул нашу игрушку «первый решает» и не обманул игрушку, которая данные SYN пропускает. Содержимое можно менять блобом, и сервер в нашем тесте каждый раз получал ровно настоящие двести пятьдесят байт. А теперь о том, чем этот приём опасен.


## Ловушки syndata: единственный SYN, повторы и профиль без имени

Вопрос сцены: что сломается, если сделать данные в SYN ядовитыми, как в выпуске «Фейк и его условия»?

Игрушка, выдуманная. Один гонец, у него одно письмо-«алло». Если у этого письма просрочена марка, оно до адресата не доедет, и разговор не начнётся: второго «алло» не будет.

Напомню из прошлой сцены. Оригинальный SYN выброшен, вместо него в сеть уходит только SYN с данными. Это единственный SYN соединения. Условие, из-за которого этот пакет не дойдёт или будет отброшен, ударит по самому соединению.

Первый опыт, условие «срок жизни один»: аргумент ip_ttl со значением один. SYN с данными виден на цэ-ноль и на эр-ноль, на выходе из роутера его нет. Примерно через пять миллисекунд к клиенту возвращается сообщение роутера, что срок жизни истёк, как в выпуске «Фейк и его условия». Клиент получает ошибку «нет маршрута» сразу, без ожидания. Сервер не видел ни одного пакета.

Оговорка. В нашей лаборатории нет правила, которое выбрасывало бы такие сообщения роутера. В примере настроек проекта, файле config.default, такое правило включено, и что стало бы с клиентом тогда, мы не проверяли.

Проверь себя. Второе условие, «испорченная контрольная сумма», аргумент badsum. Дойдёт ли такой SYN до сервера и установится ли соединение?

До сетевой карты сервера он доходит, но сервер молчит. Клиент повторяет SYN примерно раз в секунду, и каждый повтор программа снова заменяет таким же испорченным. На карточке сервера четыре таких SYN, ни одного ответа, клиент завершается по таймауту. Настоящего SYN в сети нет вообще, поэтому условие на единственном SYN убивает соединение.

![[wiki-anim-video09-s05-ttl1-and-badsum.svg|Условия на единственном SYN]]

Теперь опыт без условия, но с потерей. В нашей лаборатории специальное правило выбросило первый SYN с данными; в настоящей сети такого правила нет. Клиент повторил SYN через секунду с небольшим, и программа снова заменила его SYN с данными, на этот раз без обломка: обломок нужен только самому первому пакету. Соединение установилось, приложение получило двести пятьдесят байт. Значит, в нашем тесте стратегия действует на каждом повторе SYN, пока не придёт пакет другого вида; по коду она выключается на первом пакете, который не SYN.

Теперь профиль. В выпуске «Читаем командную строку zapret2» мы видели профили со списком имён. Но в SYN имени сайта нет: оно лежит только в приветствии, и как программа его узнаёт, объяснено в выпуске «Тип нагрузки и имя сайта».

Поэтому профиль со списком имён на SYN не подходит и приём не запускается: в журнале «профиль без действия» и «в этом профиле нет функций». Как программа выбирает профиль, рассказано в выпуске «Профили: как программа выбирает». Приём работает только в профилях, которым имя не нужно.

![[wiki-anim-video09-s05-lost-syn-and-hostlist.svg|Повтор SYN и профиль без имени]]

Итак. Условия из прошлых выпусков для SYN с данными опасны: оригинала нет, и всё, что убивает данные, убивает рукопожатие. Приём действует на каждом повторе SYN и не запускается в профиле с именем. Порядок стратегий в строке для него тоже важен, об этом в части про окна. Теперь второй приём, сброс перед приветствием.


## rst с TTL один: пакет, который видит только роутер

Вопрос сцены: можно ли заставить игрушку забыть соединение, не оборвав его на сервере?

Игрушка, выдуманная. Клерк ведёт папку на каждый разговор. Услышав слово «отмена», он рвёт папку и больше этот разговор не проверяет. А адресат слова «отмена» не услышал: оно было сказано так, что до него не дошло.

Сброс мы знаем из выпуска «Что видит наблюдатель»: пакет с флагом RST значит «закрываю соединение немедленно». Там сброс подделывал инспектор. Здесь сброс отправляет сам клиент, перед приветствием, и отправляет так, чтобы он не добрался до сервера.

Третий характер нашей игрушки, «сброс стирает память». Любой пакет RST, в любую сторону, заставляет её забыть соединение навсегда. Мы построили её так потому, что RST закрывает соединение; по общим сведениям, многие устройства после него перестают соединение отслеживать, но у настоящих мы этого не видели. Пока сброса нет, игрушка ищет имя в каждом клиентском пакете. Без стратегии она блокирует приветствие: сервер получил ноль байт.

Строка запуска. Стратегия rst, назовём её «сброс перед приветствием», и условие «срок жизни один»: пакет умрёт на первом же роутере. Ещё в строке стоит фильтр вида данных «приветствие»; что он делает, скажем в следующей сцене.

![[wiki-anim-video09-s06-rst-ladder.svg|Сброс перед приветствием на проводе клиента]]

Теперь пакеты, один за другим. Первые три, рукопожатие, как обычно. Четвёртый на проводе клиента это и есть сброс. Единственный флаг: RST. Номер один, тот же, что у приветствия, которое идёт следом.

Поле подтверждения содержит единицу, как у приветствия, хотя флага ACK у пакета нет: число скопировано, флаг снят.

Срок жизни один. Длина по заголовку IP пятьдесят два: двадцать байт IP, двадцать байт заголовка TCP и двенадцать байт опций. Идентификатор IP тот же, что у приветствия: программа взяла копию приветствия и поменяла в ней немногое.

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

Что дальше, в нашем тесте. Сервер получил двести пятьдесят байт одним чтением и ответил: сброс до него не добрался, соединение на сервере целое. А игрушка записала: «сброс от клиента, память о соединении стёрта, больше не проверяю». Приветствие прошло.

Игрушка стоит на входе в роутер, до проверки срока жизни, поэтому сброс со сроком один она видит. Это допущение нашей модели. Что видит настоящее оборудование, мы не знаем и проверить этого не могли.

Код, в основном блоке восемь строк. Копия пакета. Нагрузка пустая. Флаги заменены на один RST, ACK добавляется только если попросили: поэтому в сбросе нет ни PSH, ни ACK приветствия. Дальше применяются условия из строки запуска, и сброс уходит. Вердикта функция не возвращает, и приветствие идёт следом. Построчно её разберём ближе к концу выпуска.

![[wiki-anim-video09-s06-forget-and-code.svg|Игрушка стирает память и код функции rst]]

Проверь себя. ACK добавляется, если попросили: аргументом rstack. Что станет с полем подтверждения, если его добавить?

Ничего: оно и так равно единице. Изменится только флаг, будет RST и ACK. В нашем опыте с rstack и сроком жизни один подтверждение осталось единицей, и игрушка так же стёрла память.

Итак. Сброс перед приветствием это копия приветствия без данных, с флагом RST и сроком жизни один. Он доходит до роутера и до нашей игрушки, но не до сервера, и игрушка стирает память о соединении. В нашем тесте приветствие прошло. Теперь посмотрим, что будет, если «до сервера не дойдёт» выполнено неточно.


## Ловушки rst: срок два, чистый сброс, испорченная сумма и длинное приветствие

Вопрос сцены: насколько точно сброс не должен дойти до сервера?

Игрушка, выдуманная. «Отмена» всё-таки дошла до адресата. Он закрыл дверь, и следующее письмо упирается в закрытую дверь. Нам нужна отмена, которую услышит только клерк.

Первый опыт: срок жизни два вместо одного. Роутер в нашей лаборатории один, и сброс его переживает: на сетевой карте сервера он виден со сроком жизни один. Сервер закрывает соединение. Приветствие, которое идёт следом, приходит в закрытую дверь, и сервер отвечает сбросом с окном ноль. Клиент получает ошибку «соединение сброшено», приложение сервера получает ноль байт.

Второй опыт: никакого условия. Сброс идёт со сроком шестьдесят четыре и тоже доходит до сервера, исход тот же. Урок: срок должен быть таким, чтобы сброс умер до сервера. В нашей лаборатории с одним роутером это один; как подобрать число для другой цепочки, мы разбирали в выпуске «Фейк и его условия».

Третий опыт: условие «испорченная сумма», аргумент badsum. Сброс виден на всех четырёх точках, на сервер он приходит, но сервер его игнорирует, потому что сумма неверна. Соединение целое, приложение получает двести пятьдесят байт, и приветствие проходит. Игрушка суммы не проверяет и всё равно стирает память. По общим сведениям, другие стеки могли бы обработать такой сброс иначе; мы такого не пробовали.

![[wiki-anim-video09-s07-three-resets.svg|Три опыта со сбросом и докуда он доходит]]

Проверь себя. Аргумент repeats со значением три отправляет три одинаковых сброса со сроком жизни один. Сколько сообщений «срок жизни истёк» вернётся от роутера?

Три, по одному на каждый сброс: в нашем тесте именно так.

Ещё один аргумент, ip_id со значением rnd, ставит случайный идентификатор IP. В нашем опыте у сброса одно число, у приветствия другое, а без этого аргумента числа равны.

Теперь о том, когда сброс вообще срабатывает. Без фильтра вида данных опыт получился тот же. Как программа определяет вид данных, рассказывает выпуск «Тип нагрузки и имя сайта». В других сценах вид данных называется ещё типом нагрузки: слова разные, смысл один. По умолчанию стратегия действует на пакет, в котором протокол распознан. У нас такой один, приветствие: SYN и пустые ACK её не запускают, и в журнале всего одна строка о сбросе.

Последний опыт: приветствие не помещается в один пакет. В тысячу четыреста восемьдесят четыре байта оно не влезает, и ядро режет его на два куска, как в выпуске «Что видит наблюдатель»: первый большой, второй короткий, тридцать шесть байт. Программа собирает такое приветствие целиком и проигрывает кусками; как, объяснено в выпуске «Сборка и повторный проигрыш».

В нашем тесте сброс ушёл один раз, перед первым куском, со сроком жизни один. Перед вторым куском сброса нет: в журнале запись, что на следующих кусках программа не действует. Сервер получил оба куска двумя чтениями.

![[wiki-anim-video09-s07-repeats-id-filter-large.svg|Четыре опыта со сбросом: повтор, номер, фильтр, длинное приветствие]]

Итак. В нашем тесте сбросу нужно, чтобы сервер его не принял: срок жизни один не пускает его дальше роутера, а испорченная сумма доходит, но сервер её отбрасывает. Со сроком два и без условий сброс дошёл, и соединение сервер закрыл. Приветствие из двух кусков получает один сброс перед первым. Теперь третий приём.


## synack: ответ раньше вопроса

Вопрос сцены: можно ли ответить раньше, чем спросили? И зачем это нужно?

Игрушка, выдуманная. Телефон. Кто-то говорит «слышу вас» раньше, чем ему позвонили. Подслушивающий записывает в протокол: этот человек отвечающий. Если игрушка запоминает роли по первым словам, она запомнит их неправильно.

Приём называется synack, назовём его «ответ раньше вопроса». Клиент отправляет SYN-ACK, который по порядку должен отправлять сервер, и только потом свой настоящий SYN. Строка запуска простая: только эта стратегия, без условий.

![[wiki-anim-video09-s08-synack-ladder.svg|Ответ раньше вопроса на проводе клиента]]

Теперь пакеты, один за другим. Первый на проводе клиента, и его отправил именно клиент: SYN-ACK. Номер ноль, подтверждение ноль, окно шестьдесят четыре тысячи двести сорок, опции как у SYN, всего шестьдесят байт. Это копия SYN с добавленным флагом ACK.

Второй: настоящий SYN, через четыре сотых миллисекунды. Даже идентификатор IP у двух пакетов один.

Третий: ответ сервера. Не SYN-ACK, а сброс: флага ACK нет, окно ноль, и номер ноль, настоящий, не относительный, он совпадает с полем подтверждения нашего подложного пакета. Сброс приходит к клиенту. По общим сведениям, клиент, который ждёт ответа на свой SYN, такой сброс принимает за ошибочный и не слушает. Мы видим лишь, что соединение дальше установилось.

Четвёртый и пятый: настоящий SYN-ACK сервера с подтверждением один и ACK клиента. Рукопожатие закончено, и сервер в нашем тесте получил все двести пятьдесят байт приветствия.

Но есть и наблюдение, которого мы не ждали. Приветствие появляется на проводе клиента только через две сотни с небольшим миллисекунд после ACK. В журнале программы оно прошло дважды, оба раза «пропустить без изменений», а на проводе только вторая копия: по записи очереди первая копия ушла из программы со словом «пропустить», а на проводе клиента её нет: в нумерации пакетов дыра. Клиент нумерует свои пакеты идентификатором IP подряд, как мы читали в выпуске «Что видит наблюдатель». В четырёх наших запусках с этой задержкой после ACK пропущен ровно один идентификатор: тот, что носила первая копия приветствия. Куда она делась, мы не выясняли.

Вторая копия похожа на повтор по таймеру ядра: пауза порядка двухсот миллисекунд, как первая пауза повтора в выпуске «Что видит наблюдатель». Счётчиков ядра мы не снимали, так что это догадка.

Проверено на обоих проходах. А в опыте с испорченной суммой подложного пакета сервер молчит, сброса нет, и приветствие уходит сразу за ACK, как в обычном соединении. Задержка есть только там, где сервер ответил сбросом. Это совпадение в наших опытах, а не объяснение.

Проверь себя. Стратегия synack с условием «срок жизни один». Умрёт ли подложный SYN-ACK на роутере?

Нет. Срок жизни остаётся шестьдесят четыре, пакет доходит до сервера, и сервер отвечает сбросом: сброс сервера это доказывает. А условие «испорченная сумма» на этом приёме действует: сервер на такой пакет молчит.

Код, пять строк. Проверка одна: у пакета только SYN. Копия. В копии к флагам добавляется ACK. Запись в журнал и отправка напрямую. Обработки условий из выпуска «Фейк и его условия» в этом коде нет, поэтому срок жизни в нём не читается, а испорченная сумма относится к самой сборке пакета. Вердикта функция не даёт, и настоящий SYN уходит следом.

![[wiki-anim-video09-s08-conditions-and-code.svg|Условия на synack и код приёма]]

Итак. Подложный SYN-ACK клиента сервер в нашем тесте встречает сбросом, а клиент сброс не слушает. Рукопожатие завершается, данные доходят, приветствие приходит с задержкой около двухсот миллисекунд, причину которой мы не выяснили. Условие срока жизни на приёме не действует, испорченная сумма действует. Остаётся вопрос, кому это нужно.


## synack и роли: кого игрушка считает сервером

Вопрос сцены: зачем подложный SYN-ACK, если сервер отвечает на него сбросом? Нужен он не серверу.

Игрушка, выдуманная. Секретарь ведёт протокол разговора. Кто первым сказал «алло», тот звонящий. Кто первым сказал «слышу вас», тот отвечающий. Если «слышу вас» прозвучало первым не от того, протокол читается наоборот: секретарь будет слушать не того.

Чтобы судить по направлению, наблюдателю нужна память о соединении: кто здесь клиент, кто сервер. Что это такое, разобрано в выпуске «Память о соединении». У самой программы nfqws2 такая память есть, и роли в ней назначаются по простому правилу. Первый голый SYN, то есть с одним лишь этим флагом: его отправитель клиент. Первый голый SYN-ACK: отправитель сервер. Это правило нашей программы, и мы дали его игрушке, чтобы посмотреть, что будет. Так ли у настоящих устройств, мы не знаем.

Вот это правило в коде. Признак «обратный» ставится, если пакет это SYN-ACK.

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

![[wiki-anim-video09-s09-roles-rule-code.svg|Правило ролей в памяти о соединении]]

Проверь себя. У нашей игрушки есть четвёртый характер, «роли»: она использует то же правило и ищет имя только в пакетах направления, которое её память называет исходящим. Клиент отправил подложный SYN-ACK. Кого игрушка теперь считает сервером?

Настоящего клиента. В журнале игрушки первый пакет, SYN-ACK, на самом деле от клиента: «исходящий: нет». Настоящий клиент для неё теперь сервер.

Четвёртая строка: SYN-ACK от настоящего сервера, для игрушки он исходящий: да. Роли поменялись местами.

Шестая строка: приветствие, двести пятьдесят байт, от настоящего клиента. По памяти игрушки оно идёт в направлении, которое она считает входящим. Решение: пропустить, это направление она не проверяет. Приветствие проходит, приложение сервера получает двести пятьдесят байт. Без стратегии та же игрушка приветствие блокировала.

Пятый характер, «строгое рукопожатие». Игрушка ждёт по порядку: SYN от клиента, SYN-ACK от сервера с правильным подтверждением, ACK от клиента, и только потом смотрит данные. На настоящем рукопожатии она так и делает и блокирует приветствие. В опыте с «ответом раньше вопроса» первый пакет не клиентский SYN, и записано: «рукопожатие нечистое, не проверяю». Приветствие прошло.

Условия мы проверили только на игрушке «роли»: со сроком жизни один и с испорченной суммой решение то же, пропустить. «Строгое рукопожатие» с этими условиями мы не запускали. По её коду она срок жизни и сумму не читает, но это вывод из кода, а не из опыта.

А что говорит сам автор? В документации он называет этот приём разворотом направления соединения и пишет, что цель запутать наблюдателя. Кода, который это доказывает, у нас нет, есть только наши две игрушки.

![[wiki-anim-video09-s09-toys-roles-and-strict.svg|Две игрушки на подложном SYN-ACK]]

Итак. Подложный SYN-ACK нужен не серверу, а наблюдателю: если наблюдатель, как наши игрушки, назначает роли по первому пакету, он путает клиента с сервером или считает рукопожатие нечистым и не проверяет приветствие. Всё это про двух наших игрушек. Теперь подведём итог части.


## Что мы знаем к этому месту: три приёма, пять игрушек, один приёмник

Вопрос сцены: что мы знаем к этому месту? Соберём одну таблицу.

Приём первый, «SYN с данными». С клиента уходит SYN с шестнадцатью байтами, по умолчанию нулями, и рядом пакет-обломок. Сервер в нашем тесте подтверждает единицу, данные SYN в поток не берёт, и приветствие приходит целиком. Игрушку «первый решает» этот приём обманул, а игрушку, которая данные SYN пропускает, нет. Ломается он, когда условие бьёт по единственному SYN: срок жизни один даёт мгновенную ошибку, испорченная сумма даёт таймаут.

Приём второй, «сброс перед приветствием». С клиента уходит сброс с номером один, подтверждением один при снятом флаге, сроком жизни один, и сразу за ним приветствие. До сервера сброс не доходит. Игрушку «сброс стирает память» обманул. Ломается он, когда сброс сервер всё-таки получает: со сроком два или без условий сервер закрывает соединение. С испорченной суммой сервер его игнорирует.

Приём третий, «ответ раньше вопроса». Клиент отправляет подложный SYN-ACK, потом настоящий SYN. Сервер отвечает сбросом, клиент его не слушает, приветствие приходит с задержкой около двухсот миллисекунд, и причину мы не выяснили. Игрушки «роли» и «строгое рукопожатие» он обманул. Срок жизни на нём не действует, испорченная сумма действует.

![[wiki-anim-video09-s10-three-tricks-table.svg|Три приёма до приветствия в одной таблице]]

Четыре урока. Первый: «сработало» значит «обошло нашу игрушку и наш сервер с Linux», о настоящем оборудовании мы не знаем ничего. Второй: у SYN с данными нет запасного SYN. Третий: в нашем тесте сбросу нужно, чтобы сервер его не принял, и срок жизни один, и испорченная сумма это обеспечивают, но по-разному. Четвёртый: «ответ раньше вопроса» срока жизни не принимает.

Проверь себя. Приём «сброс перед приветствием» с условием «испорченная сумма». Какую игрушку он обманет и что будет с сервером?

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

Проверь себя ещё раз. Сброс со сроком жизни один и сброс с испорченной суммой: чем они различаются на пути пакета?

Первый до сервера не доходит, умирает на роутере. Второй доходит до сервера, и сервер его игнорирует. Результат на нашей игрушке и нашем сервере одинаков, а путь разный.

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

Что дальше. Три приёма этой части действуют до приветствия или сразу перед ним. В следующих частях выпуска: приём с лишним срочным байтом, приёмы с окном и сторона сервера. А построчный разбор кода мы оставим на последние сцены.

![[wiki-anim-video09-s10-lessons-checks-map.svg|Четыре урока, две проверки и карта выпуска]]

Итак. Три приёма, пять игрушек, один наш сервер. Каждый результат в этой части относится к ним, а не к настоящему оборудованию. Приёмам нужно, чтобы до сервера доходило нужное и не доходило лишнее: данные в SYN, сброс, который не должен дойти, и ответ раньше вопроса.


## Повтор и сдвиг: лишний байт в приветствии и счёт на единицу меньше

Это середина выпуска «Фокусы рукопожатия». Начнём с короткого повтора, чтобы эту часть можно было смотреть отдельно от остальных.

Рукопожатие, из выпуска «Что видит наблюдатель», это три пакета перед данными: SYN, SYN-ACK и ACK. Потом уходит первое сообщение клиента, приветствие. Лаборатория та же: клиент, роутер с игрушечным инспектором и сервер на Linux, в нашем тесте на ядре шесть точка восемнадцать. Пакет идёт через роутер пять миллисекунд, круг занимает десять. Точка записи цэ-ноль стоит на сетевой карте клиента: входящий пакет она видит до работы нашей программы.

![[wiki-anim-video09-s11-lab-plain-handshake.svg|Лаборатория и рукопожатие без стратегии]]

Слово «работает» в этом выпуске значит одно: приём обошёл нашу игрушку, а наш сервер получил настоящие данные. О настоящем оборудовании мы ничего не знаем. В первой трети выпуска ходы действовали вокруг приветствия, не трогая его. Теперь два хода, которые трогают само приветствие и число «окно».

Вопрос сцены: как вставить в приветствие лишний байт, который получатель выбросит сам, и не сломать нумерацию? Сначала идея на игрушке.

Игрушка, выдуманная. Слово «Москва» на бумажной ленте, и сортировщик ищет в письмах это слово целиком. Мы вставляем звёздочку между «мос» и «ква», и слова больше нет. А получатель, которому известно правило «звёздочку вычёркивай», читает «Москва». Так срочный байт задуман в руководстве: лишний байт посреди имени, который получатель убирает сам. Получится ли это у нашего получателя, проверим.

Но байты соединения пронумерованы, и лишний байт сдвигает все номера после себя. Игрушка: две бухгалтерские книги, клиента и сервера. Клиент отправляет двести пятьдесят байт, но кладёт в посылку лишний, и сервер пишет подтверждение на единицу дальше, чем ждёт клиент. Книги разошлись. Решение в игрушке: заранее сказать серверу «мой счёт начинается на единицу раньше», а лишний байт потом вернёт эту единицу.

![[wiki-anim-video09-s11-toy-star-and-books.svg|Две выдуманные игрушки про лишний байт]]

Теперь настоящая строка запуска, в ней две части. Первая, диапазон входящих «до эс один». Буква «эс» в выпуске «Читаем командную строку zapret2» осталась без разбора, скажу сейчас: это номер TCP от начала соединения, как в выпуске «multisplit: резать на части». «До эс один» значит: пока этот номер не больше единицы. Зачем это нужно, станет ясно на втором пакете.

Вторая часть вызывает срочный байт, по-английски oob, и место байта задаёт меткой midsld из выпуска «Где резать: метки»: середина слова example, байт сто четыре.

Номера считаем от SYN, который ушёл в сеть, как в выпуске «multisplit: резать на части»: на проводе у него ноль. Первый пакет, SYN. Программа вычла единицу из номера, который выдало ядро, и на провод он ушёл с нулём. Ядро клиента считает его номером один.

Второй пакет, SYN-ACK сервера. Сервер видел SYN с нулём и подтверждает следующий номер, один. Но ядро клиента ждёт подтверждение номер два: его SYN был на единицу больше. Поэтому на входе в клиент программа прибавляет единицу, и ядро получает то, что ждёт.

Третий пакет, ACK: у ядра номер два, на проводе один.

Четвёртый, приветствие: у ядра номер два и двести пятьдесят байт, на проводе номер один и двести пятьдесят один. Сервер подтвердит один плюс двести пятьдесят один, это двести пятьдесят второй. Ядро ждёт два плюс двести пятьдесят, тоже двести пятьдесят второй. Счёт сошёлся.

![[wiki-anim-video09-s11-shift-numbers-table.svg|Сдвиг номеров на единицу]]

Тот же сдвиг в коде. Программа берёт позицию пакета от начала соединения из записи соединения, о ней рассказывает выпуск «Память о соединении»: у SYN ноль, у подтверждения и первых данных один. Если позиция не больше единицы, из номера вычитается единица. У входящего SYN-ACK к подтверждению прибавляется единица.

Проверь себя. Следующий пакет клиента, чистый ACK после ответа сервера, ядро выдало с номером двести пятьдесят два. Правит ли его программа?

Нет, и причина не в позиции. Сразу после приветствия срочная стратегия выключила сама себя, и этого пакета она уже не видит: в журнале для него зовут только самописец пакетов. А если бы видела, править нечего: на проводе тот же номер двести пятьдесят два.

![[wiki-anim-video09-s11-shift-code-and-cutoff.svg|Сдвиг в коде и выключение функции]]

Итак. Сдвинуты три пакета клиента, SYN, ACK и приветствие, и подтверждение в SYN-ACK сервера. Минус один сначала, лишний байт плюс один потом, и после приветствия функция выключается. Как выглядит пакет с лишним байтом, покажет следующая сцена.


## Пакет со срочным байтом: флаг, указатель и лишний нулевой байт

Вопрос сцены: как выглядит пакет со срочным байтом, и почему получатель его выбросит?

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

В заголовке TCP они есть. В выпуске «Что видит наблюдатель» мы видели два байта urgent-указателя, два нуля, и флаг, который его включает, был выключен. Сегодня флаг включён. Он называется URG, а указатель дальше называем указателем срочности: он показывает, где лежит срочный байт.

Пакет из нашего теста. Это то самое приветствие, но в нём двести пятьдесят один байт, а не двести пятьдесят. Флаги: передать данные, подтверждение и срочность.

![[wiki-anim-video09-s12-header-plain-vs-urgent.svg|Заголовок срочного пакета]]

Указатель срочности равен ста пяти. Метка midsld даёт позицию сто четыре, считая с нуля: это буква «эм» в слове example. Лишний байт встаёт перед ней, и считая с единицы он сто пятый. Слева сто четыре байта, потом лишний, справа сто сорок шесть. По общим сведениям, указатель так и принято считать, с единицы.

В нашем тесте лишний байт нулевой, а вокруг него лежат буквы «э, икс, а», потом ноль, потом «эм, пэ, эл».

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

![[wiki-anim-video09-s12-extra-byte-tape-and-code.svg|Где стоит лишний байт и как собран пакет]]

Что отвечает сервер. В нашем тесте он подтверждает двести пятьдесят второй номер: принял двести пятьдесят один байт. А приложение сервера прочитало ровно оригинальные двести пятьдесят байт, хэш совпал с хэшем обычного запуска, и прочитало двумя чтениями, в сто четыре и в сто сорок шесть байт. Значит, наш Linux вынул срочный байт из потока и разделил чтение там, где он стоял. Почему чтение режется именно так, мы не выясняли.

Другие места. В самом начале указатель ноль, в самом конце двести пятьдесят один, и чтение одно, на двести пятьдесят байт. На позиции два указатель три, чтения в два байта и в двести сорок восемь.

Значение байта тоже можно задать: буква икс или число шестьдесят пять. На проводе тот же пакет, только на месте указателя другой байт, а чтения прежние, сто четыре и сто сорок шесть. Для трёх проверенных значений, ноль, икс и шестьдесят пять, наш сервер вынул байт одинаково. Как поведут себя другие системы, мы не проверяли.

![[wiki-anim-video09-s12-server-reads-and-variants.svg|Что читает сервер при разных местах байта]]

Проверь себя. Запрос HTTP, в нём восемьдесят один байт, а имя узла лежит с тридцать третьего байта. Метка midsld для него даёт позицию тридцать девять. Чему равен указатель срочности?

Сорок: тридцать девять плюс один. В нашем тесте пакет получился из восьмидесяти двух байт, а чтения сервера были в тридцать девять и сорок два байта.

![[wiki-anim-video09-s12-http-check-answer.svg|Срочный байт в запросе HTTP]]

Итак. Срочный пакет это то же приветствие плюс лишний байт, флаг срочности и указатель срочности, равный месту байта от единицы. В нашем тесте сервер выбрасывает этот байт, читает оригинал и режет чтение там, где байт стоял.


## Что видят игрушки: подстрока слепа посередине, разборщик слеп в обоих проверенных местах

Вопрос сцены: видят ли наши игрушки имя в пакете с лишним байтом?

Две игрушки здесь новые. Игрушка-подстрока ищет имя как обычную строку внутри одного пакета: по правилу это «пакетный» читатель из выпусков «Перемешать: disorder» и «Резать и подделывать вместе», только стоит он на входе в роутер, до проверки срока жизни, как инспектор из выпуска «Фейк и его условия», и смотрит живые пакеты. Игрушка-разборщик идёт по длинам полей приветствия, как по ступенькам. Обе придуманы нами. «Слеп» значит то же, что в тех выпусках: имя не нашлось, и для нас это хороший исход.

Игрушка, выдуманная. Лента со словом. Звёздочка в самом начале, «звёздочка Москва»: слово внутри ленты цело, сортировщик его найдёт. Звёздочка посередине, «мос звёздочка ква»: слова больше нет. Место лишнего символа решает всё.

![[wiki-anim-video09-s13-two-toys-and-star.svg|Две игрушки и звёздочка в слове]]

Точка отсчёта. Без стратегии обе наши игрушки роняют обычное приветствие, и сервер получает ноль байт. Дальше четыре запуска: две игрушки и два места. Это измерения нашего теста, каждый раз с настоящим пакетом и настоящим журналом игрушки.

Подстрока и байт в начале. Имя в пакете осталось целым, и игрушка пишет: найдено в пакете из двухсот пятидесяти одного байта. Она роняет его, клиент повторяет приветствие, паузы между повторами растут от двухсот миллисекунд почти до секунды, и каждый раз его роняют. Сервер получает ноль байт.

Подстрока и байт посередине имени. В этом пакете подстрока не находится, игрушка пропускает его, и сервер получает все двести пятьдесят байт, чтениями по сто четыре и сто сорок шесть. Против нашей игрушки-подстроки это сработало.

Разборщик и байт в начале. Он читает первый байт и ждёт тип записи, шестнадцатеричное шестнадцать. А там нулевой байт. Разбор не начинается, имени нет, сервер получает двести пятьдесят байт.

Разборщик и байт посередине. Первый байт на месте, разбор идёт по длинам. Длина имени пятнадцать, и игрушка отсчитывает пятнадцать байт. Среди них лишний нулевой: получается «дабл-ю, дабл-ю, дабл-ю, точка, э, икс, а», ноль, «эм, пэ, эл, е, точка, цэ, о». Последняя буква, эм в слове «ком», осталась за пределом. Это не имя, игрушка слепа.

![[wiki-anim-video09-s13-four-runs-grid.svg|Четыре запуска: две игрушки и два места байта]]

Ещё одна игрушка, «строгое рукопожатие». Она не смотрит данные, пока не увидит правильное рукопожатие: SYN, SYN-ACK с подтверждением ровно на единицу больше и ACK. Стоит она в роутере и видит рукопожатие таким, каким оно идёт по проводу, поправку SYN-ACK клиент делает у себя, уже после роутера. Поэтому игрушка признаёт его настоящим.

Дальше она ищет имя как обычная подстрока и в нашем пакете с лишним байтом не находит: приветствие проходит. В журнале игрушки для этого пакета нет даже решения.

![[wiki-anim-video09-s13-strict-handshake-toy.svg|Игрушка строгое рукопожатие]]

Проверь себя. Лишний байт стоит в имени посередине. Почему игрушка-подстрока его не находит?

В пакете лежит «www точка exa», нулевой байт, «mple точка com». Подряд идущего «www точка example точка com» там нет, поэтому поиск подстроки не срабатывает. Это расчёт по байтам пакета нашего теста.

Всё это про наши игрушки. Как читает пакеты настоящее оборудование, мы не знаем. Руководство описывает замысел так: получатель выбрасывает байт, а инспектор, возможно, читает его как часть сообщения. Это замысел автора, а не наше измерение.

Итак. Подстрока слепа, если лишний байт посередине имени, разборщик слеп в обоих проверенных местах, а «строгое рукопожатие» наше рукопожатие принимает. Всё это в нашей модели и на нашем Linux.


## Ловушки срочного байта: нет входящего диапазона, ошибка после сдвига, соседи по строке

Вопрос сцены: что ломается, если в срочном байте что-то идёт не так? Главное слово: после первого сдвига пути назад нет. Покажем три ловушки, у каждой строка запуска, пакеты из записи и итог.

Игрушка, выдуманная. Вы сказали серверу «считай на единицу раньше», и он записал это в книгу. Стереть запись нельзя. Если теперь ошибиться в посылке, книги разойдутся, а чинить их нечем.

Ловушка первая: убрать диапазон входящих. По умолчанию он «икс», то есть «никогда», и SYN-ACK до функции не доходит. Подтверждение в нём остаётся на единицу меньше, чем ждёт ядро клиента. В нашем тесте ядро отвечает на него сбросом, но попытку не бросает и по таймеру шлёт SYN заново, пять раз за три секунды. Сервер каждый раз отвечает, клиент каждый раз сбрасывает. Соединение не открывается, сервер не получает ни байта.

![[wiki-anim-video09-s14-no-inrange-syn-loop.svg|Без входящего диапазона соединение не открывается]]

Ловушка вторая: ошибка уже после сдвига. Дадим вместо одного символа два, икс и игрек. Функция вставляет ровно один байт и на приветствии останавливается с ошибкой: это ошибка в строке запуска, а не на проводе. Но SYN и ACK уже ушли сдвинутыми. Программа выпускает приветствие как есть, с номером ядра. Номера считаем от SYN, ушедшего в сеть, как раньше: на проводе это номер два, а сервер ждёт номер один.

Между ними дырка в один байт, и заполнить её некому. Сервер раз за разом подтверждает одно и то же, «жду номер один», а клиент шесть раз за три с половиной секунды отправляет то же приветствие. Приложение сервера получает ноль байт, клиент читает пустой ответ.

![[wiki-anim-video09-s14-two-chars-hole.svg|Ошибка после сдвига: дырка в один байт]]

Проверь себя. Метка method из выпуска «Где резать: метки» работает только на запросе HTTP. Что будет, если задать её местом срочного байта на приветствии TLS?

Метку на приветствии найти нельзя: программа пишет, что не может её разобрать, отключается и выпускает приветствие со старым номером. Это та же мёртвая картина: клиент повторяет приветствие, сервер получает ноль байт. Мы запускали именно эту строку, и ответ измерен, а не выведен.

Ловушка третья не ошибка, а соседство. Руководство предупреждает, что срочный байт не дружит с функциями, которые пересылают изменённые данные: multisplit, multidisorder и подобными.

Поставим срочный байт перед multisplit. На проводе три пакета: срочный на двести пятьдесят один байт и две части multisplit, сто четыре и сто сорок шесть. Части накрывают те же номера, но уже со сдвигом на байт, и по счёту сервер отбрасывает их как повторы: отсюда два лишних подтверждения двести пятьдесят второго. Приложение читает те же сто четыре и сто сорок шесть.

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

![[wiki-anim-video09-s14-oob-and-multisplit-neighbors.svg|Срочный байт рядом с multisplit]]

Итак. Срочный байт чувствителен. Без входящего диапазона соединение не открывается, ошибка после сдвига убивает его насовсем, а соседние стратегии дают повторы, которые наш Linux сумел разобрать. Всё измерено на нашем получателе.


## Длинное приветствие в двух кусках ядра и итог про срочный байт

Вопрос сцены: что делает срочный байт, если приветствие не помещается в один пакет ядра?

Напомню из выпуска «Что видит наблюдатель»: длинное приветствие в тысячу четыреста восемьдесят четыре байта ядро режет на два куска, тысяча четыреста сорок восемь байт и тридцать шесть. Это делает само ядро, не zapret2.

Срочному байту нужно приветствие целиком, чтобы вставить байт на место. По умолчанию программа придерживает неполное приветствие, пока не получит его целиком, и потом проигрывает собранное. Поэтому срочная стратегия получает его целым. Подробно об этом выпуск «Сборка и повторный проигрыш», он выйдет позже, а нам хватит фразы «собрать, потом проиграть».

В нашем тесте срочный пакет строится из всего приветствия, плюс байт, и его режут по привычному лимиту порции: сначала тысяча четыреста сорок восемь байт, потом тридцать семь. Тридцать семь, потому что один байт лишний.

![[wiki-anim-video09-s15-hold-replay-and-wire.svg|Длинное приветствие: собрать, потом проиграть]]

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

Оригинальный второй кусок ядра, тридцать шесть байт, в сеть не уходит: его байты уже лежат внутри срочного пакета, так что на проводе остаются только два куска. В журнале об этом строка про сброс куска при повторной отправке.

Сервер в нашем тесте читает двумя чтениями: сто четыре байта и тысяча триста восемьдесят. Вместе тысяча четыреста восемьдесят четыре, как в оригинале, и хэш совпал. Лишний байт сервер вынул, и в этот раз чтение после него длинное.

![[wiki-anim-video09-s15-flag-only-on-byte-piece.svg|Флаг срочности только на куске с байтом]]

Проверь себя. Клиент отправил куски в тысячу четыреста сорок восемь и в тридцать семь байт, первый с номера один. Какое подтверждение вернёт сервер?

Один плюс тысяча четыреста сорок восемь плюс тридцать семь: тысяча четыреста восемьдесят шесть. Сервер ответил именно так.

Сведём, что мы знаем про срочный байт. Стратегия начинается с SYN и требует диапазон входящих. Она сдвигает на единицу три клиентских пакета и подтверждение в SYN-ACK, а после приветствия выключается. Приветствие заменяется срочным пакетом, на длинном приветствии флаг получает только кусок с байтом. Наш Linux вынимает байт и читает оригинал.

Чего мы не знаем. Как поведут себя другие получатели: Windows, BSD, телефоны. Прочтёт ли приложение сервера срочный байт отдельно: наш сервер его не читает. Как устроен настоящий инспектор. И как это работает на IPv6 и при нарезке на уровне IP, тема выпуска «Другие слои»: мы не пробовали.

![[wiki-anim-video09-s15-oob-known-unknown.svg|Что мы знаем про срочный байт]]

Итак. Длинное приветствие становится одним срочным пакетом, нарезанным по лимиту, и флаг остаётся только на куске с байтом. Про срочный байт мы знаем всё, что видели на нашем Linux и в нашей модели, и ничего сверх этого. Дальше второй приём части: ложное окно.


## Ложное окно у клиента: wssize в трёх пакетах

Вопрос сцены: что будет, если клиент скажет серверу, что может принять только один байт?

Напомню из выпуска «Что видит наблюдатель»: окно в пакете сообщает отправителю, сколько данных можно прислать, не дожидаясь подтверждения. Число умножается на множитель, о котором договариваются в SYN. У нашего клиента окно шестьдесят три и множитель два в десятой степени, вместе шестьдесят четыре тысячи пятьсот двенадцать байт.

Игрушка, выдуманная. Почтальон носит ответ клиенту. Клиент говорит: «в моём ящике место на одну штуку». Почтальон мог бы привезти всё сразу, но носит по одной. Руководство описывает замысел так: заставить сервер резать свой ответ мелко, пока его читает инспектор. Это замысел, а не наше измерение.

Точка отсчёта. Наш тестовый сервер отвечает на приветствие шестью тысячами байт, чтобы было что резать. Без стратегии ответ идёт пятью кусками: четыре по тысяче четыреста сорок восемь байт и один на двести восемь.

Строка запуска. Зажим окна у клиента, по имени в коде wssize, с двумя аргументами: окно один и множитель шесть. Окно это значение поля в пакете. Множитель это число из SYN, и программа умеет только уменьшать его. Диапазон входящих «эй», все входящие, для этой функции не обязателен: она сама выключает входящую сторону, и в другом нашем запуске без него окно правится так же.

Первый пакет, SYN. Ядро выдало окно шестьдесят четыре тысячи двести сорок и множитель десять. На проводе окно один и множитель шесть.

Второй пакет, подтверждение рукопожатия. В нём окно шестьдесят три, на проводе оно стало один. Множителя в этом пакете нет, поэтому меняется только окно.

Третий пакет, приветствие. В нём тоже окно один, и после него функция отключается: она дошла до первых данных. В журнале строка «принудительное отключение». Следующий пакет клиента уже несёт обычное окно шестьдесят три.

![[wiki-anim-video09-s16-command-and-three-packets.svg|Зажим окна у клиента в трёх пакетах]]

Что делает сервер. Приветствие принято целиком. Окно один, умноженное на шестьдесят четыре, это шестьдесят четыре байта, и первый кусок ответа ровно такой. Дальше в основном куски по сто двадцать восемь и по сто девяносто два байта, всего сорок кусков вместо пяти. Какое правило ядра выбирает остальные размеры, мы не проверяли.

Код, по строкам. Если задано окно, оно записывается в поле пакета. Если задан множитель и в пакете есть опция множителя, он записывается, но только когда новый меньше старого. А на первом пакете с данными функция отключается.

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

Проверь себя. Окно два и множитель шесть. Какой будет первый кусок ответа?

Сто двадцать восемь байт: два умножить на шестьдесят четыре. Мы запускали именно эту строку, и первый кусок оказался таким.

![[wiki-anim-video09-s16-first-segment-code-variants.svg|Первый кусок ответа и код зажима окна]]

Итак. Зажим окна у клиента правит три пакета: SYN, подтверждение и приветствие. В нашем тесте сервер начинает ответ с куска, равного окну на множитель, и вместо пяти кусков получается сорок. Что мы платим за это, расскажет следующая сцена.


## Цена ложного окна: множитель остаётся, ответ идёт дольше

Вопрос сцены: что мы платим за мелкие куски?

Игрушка, выдуманная. В кафе тарелка весила тысячу двадцать четыре грамма, потом договорились, что она весит шестьдесят четыре. Посетитель каждый раз просит шестьдесят три тарелки, а договорённость отменить нельзя. Вместо шестидесяти четырёх килограммов выходит четыре.

Так и с окном. Множитель называется в SYN один раз, а в следующих пакетах опции множителя нет, так что вернуть его нельзя. Наш SYN сказал «шесть». Ядро клиента о подмене не знает: по своему множителю десять оно честно пишет окно шестьдесят три, а сервер читает это число с множителем шесть. По счёту выходит четыре тысячи тридцать два байта.

Без стратегии то же окно значило бы шестьдесят четыре тысячи пятьсот двенадцать байт: в шестнадцать раз больше.

Как это выглядит во времени. Первый кусок в шестьдесят четыре байта приходит на двадцать первой миллисекунде. Клиент подтверждает, и следующая пачка приходит через круг, на тридцать второй. Круг стоит около десяти миллисекунд.

Итог. В нашем тесте клиент получает все шесть тысяч байт за пятьдесят три миллисекунды вместо десяти с половиной: примерно в пять раз дольше при круге в десять миллисекунд.

Заметьте: пять, а не шестнадцать. Окно в четыре тысячи байт само по себе дало бы по счёту около двух кругов, а их пять. Значит, дело не только в нём: сервер шлёт мелкие куски, пачками по десять и по двадцать. Почему так, мы не проверяли; по общим сведениям, тут важно число пакетов в полёте.

![[wiki-anim-video09-s17-scale-stays-and-rounds.svg|Множитель остаётся и ответ идёт кругами]]

А если не отключаться? Для этого есть принудительное отключение, по имени в коде forced_cutoff, со значением no, которое никогда не совпадёт с видом данных. Тогда функция правит окно у каждого исходящего пакета, который ей дают.

Но программе отдают не все пакеты: правило очереди в нашей лаборатории пропускает к ней только первые двенадцать пакетов клиента, как в выпуске «Читаем командную строку zapret2». Поэтому ровно двенадцать клиентских пакетов несут окно один, остальные обычное шестьдесят три.

Первые десять кусков ответа в этом запуске по шестьдесят четыре байта, всего сорок три куска, и клиент получает ответ за сто пятьдесят миллисекунд, около четырнадцати раз дольше обычного.

Руководство предупреждает: зажим окна замедляет всегда, а если ограничение не снять, скорость может упасть катастрофически. Мы мерили только на круге в десять миллисекунд, что это значит для живого канала, не знаем.

Проверь себя. Окно один без множителя: множитель остался десять. Будет ли окно после приветствия таким же узким, как при множителе шесть?

Нет, не таким же. Обычное окно шестьдесят три снова значит почти шестьдесят пять тысяч байт, и весь ответ пришёл за тридцать две целых шесть десятых миллисекунды вместо пятидесяти трёх. Это измерено в нашем тесте. Но первые два куска всё равно по пятьсот двенадцать, дальше куски разные: тысяча четыреста сорок восемь, восемьдесят восемь, тысяча двадцать четыре.

![[wiki-anim-video09-s17-time-forced-cutoff-and-check.svg|Время ответа и отключение no]]

Итак. Множитель из SYN остаётся навсегда, и это главная цена. В нашем тесте ответ идёт в пять раз дольше, а без отключения около четырнадцати. Мы знаем цену, а пользу в нашей модели проверим в следующей сцене.


## Игрушки на ответе сервера: решает то, где легла граница сегмента

Вопрос сцены: может ли мелкая нарезка ответа спрятать в нём имя?

Сначала договорённость. В нашем стенде ответ сервера это простое тело в шесть тысяч байт, куда мы сами записали текст «www точка example точка com» на выбранном месте. Это условная замена имени, лежащего в ответе открыто. Руководство говорит, что приём задуман против TLS один точка два, где сертификат идёт открыто. Мы этого не проверяли, и о настоящих ответах мы ничего не знаем. Сертификат это документ, которым сервер представляется клиенту, и в нём есть имя сайта; в версии один точка три, по общим сведениям, он зашифрован.

Игрушка, выдуманная. Строка букв режется на куски по шестьдесят четыре. Слово «Москва» стоит так, что «мос» в конце первого куска, а «ква» в начале второго. Читатель, который смотрит на куски по одному, слова не найдёт. Читатель, который склеивает их по порядку, найдёт. У нас две такие игрушки: игрушка по пакетам и игрушка-сборщик. По правилам игрушка по пакетам близка к «пакетному» из выпуска «Резать и подделывать вместе», а сборщик ближе всего к «первый записал» из выпуска «Перемешать: disorder»: он собирает ответ по номерам, и занятое место не меняется. Читают обе пакеты сервера, а не клиента.

Точка отсчёта. Игрушка по пакетам ответа ищет имя в каждом серверном пакете отдельно. Без стратегии оно лежит в первом куске на тысячу четыреста сорок восемь байт, игрушка находит его и роняет ответ. Клиент получает ноль из шести тысяч байт: это точка отсчёта нашего стенда.

Теперь зажим окна у клиента и имя на смещении шестьдесят: пятнадцать байт, с шестидесятого по семьдесят четвёртый. Первый кусок в шестьдесят четыре байта кончается словами «www точка», а «example точка com» лежит уже во втором. Игрушка по пакетам про каждый пакет говорит «не вижу», и клиент получает все шесть тысяч байт за пятьдесят шесть миллисекунд, а без стратегии на это ушло бы десять с половиной.

![[wiki-anim-video09-s18-stand-toys-and-name-cut.svg|Стенд, две игрушки и разрезанное имя]]

Сдвинем имя на смещение семьдесят: с семидесятого по восемьдесят четвёртый. Первый кусок проходит, имени в нём нет. Но целиком имя лежит во втором куске, на сто девяносто два байта, и игрушка роняет всё дальше. Клиент получает шестьдесят четыре байта из шести тысяч.

Игрушка-сборщик склеивает ответ по порядку. Возьмём то же имя на шестидесятом, которое обмануло игрушку по пакетам. После второго куска у сборщика двести пятьдесят шесть байт подряд, и имя в них целиком. Он роняет ответ, и клиент получает те же шестьдесят четыре байта.

Арифметика границы. Первый кусок это байты с нулевого по шестьдесят третий. Имя на шестидесятом начинается внутри него: четыре байта, «www точка», в первом куске, остальные одиннадцать во втором. Имя на семидесятом начинается уже после границы и лежит целиком во втором.

Вывод простой: результат зависит от того, где легла граница куска. Смещения выбрали мы, чтобы показать обе стороны. Это не измерение настоящего сертификата и не слова о настоящем инспекторе.

Проверь себя. Игрушка-сборщик, имя на смещении семьдесят. Блокирует ли она ответ?

По счёту да: после второго куска имя целиком окажется в склеенном потоке, как при шестидесяти. Это расчёт, а не запуск: такого запуска у нас нет. Есть шестьдесят у сборщика и семьдесят у игрушки по пакетам, и оба заблокированы.

![[wiki-anim-video09-s18-boundary-grid-and-check.svg|Граница куска и сетка из четырёх запусков]]

Итак. Нарезка спрятала имя от игрушки по пакетам, только когда граница куска упала внутрь имени. Сборщик нашёл его в обоих случаях. Всё это про наши игрушки и наш стенд.


## Ложное окно в ответе сервера: клиент режет своё приветствие сам

Вопрос сцены: что будет, если подменить окно в ответе сервера? Кто тогда режет приветствие?

Игрушка, выдуманная. У входа в зал сидит человек: «сейчас пускаем одного». Один проходит сразу, остальные ждут, пока он вернётся. Мы повесили эту табличку не на ворота, а в объявление сервера, и очередь клиента выстраивается сама. Режем приветствие не мы, а сам клиент.

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

Строка запуска. Зажим окна в ответе сервера, по имени в коде wsize, с окном один и множителем шесть. Функция правит SYN-ACK и на следующем пакете сервера отключается, в журнале видно и то и другое. На проводе SYN-ACK теперь несёт окно один и множитель шесть вместо настоящих шестидесяти пяти тысяч ста шестидесяти и десяти.

Клиент в нашем тесте обычный: одна отправка в двести пятьдесят байт. Но его ядро теперь считает, что сервер принимает один байт. Окно в самом SYN-ACK множитель не умножает, поэтому единица значит ровно один байт, это по общим сведениям. После подтверждения рукопожатия клиент молчит около двухсот пятнадцати миллисекунд, потом уходит один байт.

Сервер подтверждает этот байт, и ядро клиента сразу отправляет сто семь байт и следом семьдесят два. Сервер отвечает своими четырнадцатью байтами, и клиент отправляет последние семьдесят вместе с флагом закрытия. Один, сто семь, семьдесят два и семьдесят в сумме двести пятьдесят: то же приветствие, нарезанное самим клиентом.

![[wiki-anim-video09-s19-server-window-ladder.svg|Окно в ответе сервера и лесенка клиента]]

Повторяется ли это. В шести запусках на сервере при круге в десять миллисекунд куски были такие же, а первый байт уходил через время от двухсот тринадцати до двухсот девятнадцати миллисекунд. Без сетевой задержки куски другие: один, двести пятнадцать и тридцать четыре. Значит, размеры зависят от обстановки.

Другие значения. Окно два даёт два, сто шесть, семьдесят два и семьдесят. Множитель три, а также неизменённый десять дают те же один, сто семь, семьдесят два и семьдесят.

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

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

Теперь расчёт, не опыт. Имя лежит с байта девяносто семь по сто одиннадцатый, считая с нуля. Второй кусок кончается на байте сто семь, третий начинается со ста восьмого. Значит, имя разрезано между ними: «www точка example» в одном, «точка com» в другом. Читатель, который смотрит на пакеты по одному, целиком его не увидит. Запуска с игрушкой на этом у нас нет.

Проверь себя. Окно в SYN-ACK стало один, а клиент хочет отправить двести пятьдесят байт. Сколько байт уйдёт в первом пакете?

Один, как в нашем запуске. Остальное клиент отправит только после подтверждения этого байта.

![[wiki-anim-video09-s19-values-repeat-and-unknown.svg|Другие значения окна и чего мы не знаем]]

Итак. Ложное окно в ответе сервера заставило клиента в нашем тесте нарезать собственное приветствие на один, сто семь, семьдесят два и семьдесят байт после паузы в две десятых секунды. Почему так, мы не знаем, а как это ляжет на настоящий инспектор, не знаем тем более.


## Ложное окно на клиенте и итог части: функция wsize мертва, пока не спрятать исходящие

Вопрос сцены: а если сделать то же самое на клиенте?

На сервере SYN-ACK это собственный пакет сервера, а на клиенте он приходит снаружи, это входящий пакет.

Запустим на клиенте: диапазон входящих «эй», wsize, окно один, множитель шесть. И ничего не происходит. Приветствие уходит одним пакетом, ответ приходит за одиннадцать миллисекунд, в журнале запись: функция отключена.

Почему? Код короткий. Если у пакета есть оба флага, SYN и ACK, окно правится, если нет, функция отключается: миссия выполнена. А первый раз её вызвали на SYN самого клиента, без флага ACK. SYN-ACK сервера она так и не увидела.

Чтобы функция дожила до SYN-ACK, нужны два диапазона. Входящие «эй», иначе SYN-ACK до неё не дойдёт. И исходящие «икс», то есть никогда: тогда она пропускает SYN как вне диапазона, а на SYN-ACK срабатывает, и окно шестьдесят пять тысяч сто шестьдесят заменено на один, множитель десять на шесть.

Проверь себя. Захват цэ-ноль стоит на сетевой карте клиента. Покажет ли он SYN-ACK с окном один?

Нет. Захват покажет настоящее окно и множитель десять: правка делается внутри клиента, уже после точки записи. Но последствия у неё есть.

Ядро клиента видит окно один и множитель шесть и режет собственное приветствие: один, сто семь, семьдесят два и семьдесят, как при зажиме на сервере. Первый байт уходит через двести с небольшим миллисекунд после подтверждения. Клиент получает ответ за двести двадцать три миллисекунды вместо одиннадцати: он сам себя душит. Это про наш Linux и нашу лабораторию.

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

![[wiki-anim-video09-s20-client-dead-ranges-and-mirror.svg|Зажим окна на клиенте мёртв пока не спрятать исходящие]]

Кому это нужно? Места разреза мы не выбираем. Руководство называет функцию устаревшей и советует с клиентской стороны брать нарезку TCP: она не снижает скорость и гибче.

Сведём часть. Срочный байт: сдвиг на единицу, пакет с флагом и указателем, наш Linux вынимает байт, подстрока слепа посередине имени, а ловушки убивают соединение.

Зажим окна у клиента: три пакета с окном один, первый кусок ответа шестьдесят четыре байта, цена в пять раз по времени. Зажим в ответе сервера: клиент режет своё приветствие после паузы около двух десятых секунды, а на клиенте функция мертва, пока не спрятать исходящие.

Чего мы не знаем: как поведут себя другие получатели и настоящий инспектор. И мы не нашли причину размеров сто семь, семьдесят два и семьдесят и этой паузы.

Проверь себя по таблице. Зажим окна у клиента, окно один, множитель шесть. На каком по счёту исходящем пакете функция отключается, и какое окно записано в следующем пакете клиента?

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

Ещё один пример. Что нужно сделать, чтобы зажим окна в ответе сервера сработал на клиенте?

Открыть входящие диапазоном «эй» и спрятать исходящие диапазоном «икс»: иначе функция выключится на собственном SYN или вообще не увидит SYN-ACK.

![[wiki-anim-video09-s20-part-summary-table.svg|Итог части про срочный байт и ложное окно]]

Дальше в выпуске ловушки окна, потом серверная сторона: режим сервера, зеркальные правила и поддельное рукопожатие. И в конце код этих функций.

Итак. Срочный байт и ложное окно разобраны на проводе, на игрушках и на ловушках. Все выводы относятся к нашему Linux и нашей модели.


## Ловушки окна: сто байт и сборка, список имён, порядок со syndata

Это последняя часть выпуска «Фокусы рукопожатия»: ловушки окна, серверная сторона и код. Сначала короткий повтор, чтобы её можно было смотреть отдельно.

Рукопожатие это три пакета: SYN от клиента, SYN-ACK от сервера и подтверждение от клиента. Вокруг его начала мы уже делали многое: данные в SYN, сброс до приветствия, SYN-ACK раньше SYN, срочный байт, ложное окно. Окно объявляет получатель: сколько байтов отправитель вправе держать в пути без подтверждения. Всё это мы показывали в нашем тесте, на ядре Linux шесть точка восемнадцать, против наших игрушечных инспекторов. О настоящем ТСПУ нам ничего не известно.

Вопрос сцены: где ложь про окно ломает соединение, а где она просто не доходит до нужного пакета?

Игрушка, пример выдуманный. В приёмной вам говорят: «Приносите не больше ста листов за раз». Вы приносите сто листов, но это лишь начало документа. Помощник у двери решает: «Начало без конца я не читаю, подожду остальное» и не расписывается. Пока подписи нет, следующую стопку нести нельзя: вы ждёте подписи, он ждёт продолжения.

Помощник у двери это сборка приветствия в программе: если пришла лишь часть приветствия, программа придерживает пакет и ждёт остальное. Подробности в выпуске «Сборка и повторный проигрыш»; нам важно одно: придержанный пакет ядро не получает и потому не подтверждает.

Теперь настоящий запуск, режим сервера: программа стоит на сервере, об этом режиме ниже отдельная сцена. Она подменила в SYN-ACK окно на сто байт и множитель окна на ноль. Клиентское ядро отправило первые сто байт приветствия. В журнале программы три записи: приветствие частичное; собрано сто из двухсот пятидесяти; ждём остальное. Пакет придержан, подтверждения нет, и клиент шлёт те же сто байт снова: пять копий за две секунды без малого. Приложение сервера получило ноль байт.

Тот же запуск, но сборка приветствия отключена. Сто байт проходят, сервер подтверждает, и клиент продолжает кусками по шестьдесят четыре байта. В заголовке сервера число шестьдесят четыре; множитель мы обнулили, и клиент читает его как шестьдесят четыре байта. Приложение получило сто, шестьдесят четыре, шестьдесят четыре и двадцать два байта, всего двести пятьдесят.

С окном в один байт, как в прошлых сценах, ловушки не было. Тип нагрузки это то, чем программа считает содержимое пакета: приветствие TLS, запрос HTTP или «неизвестно». Кусок в один байт получил тип «неизвестно» и прошёл без сборки, а кусок в сто байт распознан как начало приветствия. Где проходит граница, мы не искали.

![[wiki-anim-video09-s21-window-100-stall-vs-free.svg|Окно в сто байт: остановка и ход без сборки]]

Вторая ловушка: список имён. Профиль, выбранный по имени сайта, на SYN не срабатывает: имя появится только в приветствии; о профилях рассказывает выпуск «Профили: как программа выбирает». В нашем тесте wssize с таким профилем оставил SYN нетронутым: окно шестьдесят четыре тысячи двести сорок, множитель десять. Окно в единицу получил один пакет, приветствие, и ответ сервера пришёл теми же пятью сегментами, что и без стратегии: четыре по тысяче четыреста сорок восемь байт и последний в двести восемь. Почему окно приветствия на них не повлияло, мы не выясняли.

Третья ловушка: порядок. В строке две стратегии: syndata кладёт данные в SYN, wssize лжёт про окно. Syndata отправляет собственную копию SYN, а wssize правит пакет, который программа держит сама.

Проверь себя. Сначала syndata, потом wssize. Какое окно будет в SYN с данными?

Обычное: шестьдесят четыре тысячи двести сорок и множитель десять, копия ушла до правки. Если wssize стоит перед syndata, копия делается уже после правки и несёт окно один и множитель шесть. В нашем тесте было именно так.

Подтверждение клиента и оба куска приветствия, сто четыре и сто сорок шесть байт, несут окно один в обоих запусках: разница только в SYN. Причина в коде syndata: она копирует пакет сразу после ворот. Об этом порядке предупреждает и документация проекта.

![[wiki-anim-video09-s21-hostlist-and-order.svg|Список имён и порядок строк]]

Итак, об окнах, всё в нашем тесте. Окно в сто байт при включённой сборке останавливает соединение. Список имён оставляет SYN нетронутым, а порядок с syndata решает, получит ли SYN с данными ложное окно. Wssize на клиенте лжёт в SYN, подтверждении и приветствии, и ответ сервера начинается с куска в шестьдесят четыре байта. Wsize на сервере заставляет клиентское ядро резать приветствие на один, сто семь, семьдесят два и семьдесят байт, в шести запусках из шести. Это Linux и наши игрушки, не настоящее оборудование.


## Серверная сторона: роли и ярлыки, правила ядра, две ошибки

Теперь серверная сторона. Вопрос сцены: что нужно изменить, чтобы программа работала на сервере, а не у клиента?

До сих пор программа стояла у клиента. Её можно поставить на сервер: тогда она меняет то, что отправляет сервер, а клиенту ничего ставить не нужно. Для этого есть флаг сервера.

Игрушка, пример выдуманный. Анна пишет Борису. Анна всегда отправитель, Борис получатель: это роли, они не меняются. В ящике Анны письмо «исходящее», в ящике Бориса «входящее»: это ярлыки, они зависят от того, чей ящик разбираем. Флаг сервера переставляет только ящик.

Первая вещь это роль. Клиент тот, кто первым прислал SYN; роли программа хранит в записи о соединении, об этом выпуск «Память о соединении». Флаг сервера роли не меняет.

Вторая вещь это ярлык направления, «входящий» или «исходящий»: слово, которое программа клеит на пакет для стратегий. Вот его флаг и переворачивает.

Один и тот же обмен в двух журналах нашего теста. В каждой записи два слова: роль отправителя и ярлык. Сначала программа на клиенте. SYN клиента: роль клиент, ярлык «исходящий». SYN-ACK сервера: роль сервер, ярлык «входящий».

Теперь программа на сервере, с флагом. Тот же SYN клиента: роль по-прежнему клиент, а ярлык «входящий»: для сервера это пакет, который к нему пришёл. SYN-ACK сервера: роль сервер, ярлык «исходящий».

Отсюда важное следствие. Диапазон исходящих по умолчанию, «эй», то есть «всегда», в режиме сервера относится к пакетам, которые отправляет сам сервер. В нашем тесте у стратегии synack_split, которая заменяет SYN-ACK сервера двумя пакетами и на любом другом пакете выключается, SYN клиента вне диапазона, а SYN-ACK сервера в диапазоне.

Фильтры профиля подстроены так же: порт в них всегда серверный, а адрес для списков в режиме сервера клиентский; в журнале цель поиска профиля это адрес клиента и порт четыреста сорок три. Поэтому большинство стратегий выпуска в режиме сервера действует на ответы сервера, а не на приветствие клиента.

Проверь себя. Режим сервера, сервер отправляет клиенту ответ из четырнадцати байт. Какая роль у отправителя и какой ярлык у пакета?

Роль сервер, ярлык «исходящий»: флаг роли не трогает, а ярлык переворачивает. В журнале нашего теста так и было.

![[wiki-anim-video09-s22-roles-and-labels.svg|Роль и ярлык при флаге сервера]]

Игрушка, пример выдуманный. Камера над перекрёстком смотрит на выезд. Перенесите её на другой конец дороги: выезд стал въездом, и камеру надо повернуть зеркально.

Теперь правила ядра. Ядро Linux отдаёт программе только те пакеты, для которых есть правило; как устроены правила и очередь, разбирает выпуск «Внутри программы». У клиента два правила: пакеты на порты восемьдесят и четыреста сорок три и такие же пакеты с этих портов, идущие к клиенту; в каждом направлении первые двенадцать пакетов.

У сервера те же два правила, но цепочки поменяны местами: что клиент ловит на выходе, сервер ловит на входе. Первое ловит то, что сервер получает, пакеты на его порт. Второе ловит то, что сервер отправляет, пакеты с его порта. Скрипты установки меняют слова в правиле для клиента; эти два правила для сервера мы в нашей лаборатории написали вручную.

В поставке есть пример для сервера: правило одно, только на пакеты сервера, пока клиент отправил не больше пятнадцати пакетов, и строка стратегий из трёх частей: флаг сервера, фейк с multisplit для ответа сервера и synack_split для пустых пакетов.

Мы проверили в нашем тесте единственное правило. Первый пакет, который видит программа, это SYN-ACK сервера, и запись о соединении создаётся по нему с перевёрнутым ключом: отправитель SYN-ACK считается сервером. Роли верные, и synack_split работает так же, как с двумя правилами.

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

Вторая ошибка: правила верные, флаг забыли. Строка одна: multisplit, позиция пять, нагрузка любая. С флагом режется ответ сервера: четырнадцать байт уходят пятью и девятью, и клиент, прочитав один раз, видит только первые пять букв ответа.

Без флага «исходящий» для программы это пакет клиента, то есть приветствие, которое сервер получает. Его и режут: приложение сервера читает пять и двести сорок пять байт, хотя по проводу приветствие шло целым. Мы читаем это так: куски созданы и доставлены внутри сервера; первое подтверждение сервера на проводе покрывает только пять байт. А synack_split без флага в нашем тесте не делает ничего: первым она видит SYN клиента, и она выключается.

![[wiki-anim-video09-s22-kernel-rules-and-flag-mistakes.svg|Правила ядра сервера и две ошибки]]

Итак. Флаг сервера не меняет, кто клиент и кто сервер: он переворачивает слова «входящий» и «исходящий». Правила ядра сервера зеркальны правилам клиента, а поставочный пример обходится одним правилом на пакеты сервера. Чужие правила молчат, забытый флаг заставляет стратегию работать на пакетах клиента. Всё это в нашем тесте.


## synack_split на проводе: три режима

Теперь сама стратегия для сервера. Вопрос сцены: что сервер отправляет вместо одного SYN-ACK?

Игрушка, пример выдуманный. Вместо одной открытки «согласен, вот мой номер» вам присылают две: на одной «вот мой номер», на другой «согласен». Получатель на первую отвечает: «вы тоже хотите начать?», и ждёт.

У стратегии synack_split три режима, аргумент mode. По умолчанию synack: сначала SYN, потом ACK. Режим syn: только SYN. Режим acksyn: сначала ACK, потом SYN.

Режим по умолчанию в нашем тесте. Клиент отправил SYN. Сервер отвечает первым пакетом с одним флагом SYN. Флаг ACK снят, но номер подтверждения остался в заголовке, и опции те же, что у настоящего SYN-ACK: размер сегмента, метки времени, множитель окна.

Сразу за ним второй пакет, чистый ACK: номер последовательности ноль относительно сервера, подтверждение один. У обычного ACK опций нет, кроме меток времени; у этого опции от SYN-ACK.

Почему так? Обе части это копии одного пакета, и функция меняет в копиях только флаги. Опции, окно и номера остаются от настоящего SYN-ACK, поэтому такой ACK необычен. Это следствие копирования; нужно ли оно для обхода, мы не знаем.

Третий пакет, обломок. Это пакет самой программы с протоколом двести пятьдесят пять и сроком жизни один. В нашем тесте он виден на проводе между сервером и роутером; дальше роутера не прошёл, клиент его не получает. Откуда он берётся, расскажем в сцене про код.

Что делает клиент. Его ядро отвечает на голый SYN пакетом SYN-ACK: по общим сведениям, это одновременное открытие соединения, когда обе стороны шлют SYN навстречу друг другу. Потом клиент отправляет ACK, а приветствие уходит, когда от сервера придёт ещё один ACK. Подключение у клиента завершилось примерно через двадцать две миллисекунды.

![[wiki-anim-video09-s23-synack-split-ladder.svg|Расщеплённый SYN-ACK на проводе клиента]]

Режимы syn и acksyn. В режиме syn сервер отправляет один SYN: клиент отвечает SYN-ACK, и сервер присылает чистый ACK. В режиме acksyn сначала ACK, потом SYN, дальше то же самое. Во всех трёх режимах клиент в нашем тесте считает соединение открытым примерно через двадцать две миллисекунды, и во всех трёх уходит обломок: стратегия выбрасывает SYN-ACK, первый пакет сервера, и программа подменяет его обломком.

Два края. На клиенте та же стратегия ничего не делает: первым она видит SYN клиента, а не SYN-ACK, и выключается; об этом правиле ещё скажем в сцене про код. А неизвестный режим даёт ошибку, и уходит обычный SYN-ACK. В обоих случаях в нашем тесте соединение обычное.

Проверь себя. Режим syn. Какой пакет сервер отправит после ответа клиента SYN-ACK?

Чистый ACK. В режиме syn сервер сначала отправляет только SYN, а после ответа клиента присылает ACK. В нашем тесте на проводе так и было.

![[wiki-anim-video09-s23-three-modes-and-edges.svg|Три режима synack split и два края]]

Итак. Стратегия synack_split на сервере заменяет SYN-ACK двумя пакетами: SYN и ACK, или ACK и SYN, или одним SYN. Это копии настоящего SYN-ACK с изменёнными флагами. Клиент на Linux в нашем тесте отвечает на голый SYN пакетом SYN-ACK; по общим сведениям, это одновременное открытие.


## Цена расщепления и строгая игрушка

Вопрос сцены: чем платит сервер за расщепление и что оно даёт против нашей игрушки?

Игрушка, пример выдуманный. Вы привыкли: поздоровались, и сразу разговор. Теперь после приветствия уходит время на выяснение, кто кого поприветствовал, и разговор начинается позже.

Сначала измерение, в нашем тесте. Мы смотрим на паузу между подтверждением клиента и его приветствием. У обычного сервера она девять сотых миллисекунды: разрыва почти нет.

В режиме synack пауза одиннадцать миллисекунд: это чуть больше одного круга, а круг в нашей лаборатории десять. Клиент ждал ещё одного ACK от сервера, и приветствие ушло через две десятых миллисекунды после его прихода. В других запусках пауза около десяти целых семи десятых. В первом запуске приветствие ушло около двадцати двух миллисекунд после первого SYN, то есть через два круга: первый на обмен SYN, второй на лишний ACK.

Режимы syn и acksyn. Клиент тоже считает соединение открытым и отправляет приветствие, но сервер его не принимает. Клиент шлёт приветствие пять раз, и паузы между копиями растут: две десятых секунды, ещё две десятых, около полсекунды, около восьми десятых.

Данные сервер подтверждает только после пятой копии, примерно через одну целую восемь десятых секунды после первого SYN. На первые четыре копии он отвечает пустым ACK со старым номером.

Счётчики самого сервера в этих запусках. Метку времени мы видели в выпуске «Что видит наблюдатель»: клиент записывает в пакет свои часы, а сервер возвращает их, это и есть эхо. Счётчик с названием про отвергнутое эхо метки времени вырос на четыре, а счётчики повторной отправки SYN-ACK и тайм-аутов выросли на один. В режиме synack везде ноль. Четыре отвергнутых эха совпадают с четырьмя копиями перед пятой.

Как мы это читаем, ядро Linux мы не открывали. Ответ клиента на голый SYN для сервера лежит вне окна, и сервер отвечает пустым ACK. Какое именно условие у ядра сервера, мы не знаем. Видно только: четыре копии отброшены из-за метки времени, так считает счётчик, а пятая принята после того, как сервер сам повторил SYN-ACK, на тысяча тридцать девятой миллисекунде. На проводе это согласуется, но не доказывается: эхо метки в каждой копии равно метке последнего пакета сервера перед ней, и копии по меткам друг от друга не отличить.

Проверь себя. Круг в лаборатории стал двадцать миллисекунд вместо десяти. Какую паузу даст режим synack?

По счёту, около двадцати: цена режима один круг. Такой запуск мы не делали.

![[wiki-anim-video09-s24-split-price.svg|Цена расщепления SYN-ACK]]

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

Наша игрушка «строгое рукопожатие» из первых частей устроена как экзаменатор с чек-листом: ждёт SYN клиента, SYN-ACK сервера с верным подтверждением и ACK клиента, и только потом ищет имя сайта в пакетах клиента. Без стратегии, в нашем тесте, она проходит все три шага и блокирует приветствие: приложение сервера получило ноль байт.

Теперь synack_split в режиме synack. Игрушка ждёт SYN-ACK, а видит SYN от сервера. Запись: «ожидался SYN-ACK сервера, пришла буква эс; поток не чистое рукопожатие, не проверяется». Приветствие проходит, приложение получило двести пятьдесят байт. В режиме syn запись та же.

Проверь себя. Режим acksyn. Какую букву игрушка запишет вместо ожидаемого SYN-ACK?

Букву А: первым от сервера пришёл ACK. Поток объявлен недействительным, приветствие прошло. Во всех трёх режимах оно дошло до сервера, но в двух медленных ответ пришёл лишь через одну целую восемь десятых секунды.

![[wiki-anim-video09-s24-strict-toy-three-modes.svg|Строгая игрушка и три режима расщепления]]

Недействительный значит: игрушка помечает соединение и больше не смотрит его пакеты, поэтому приветствие с именем проходит мимо; метка действует до конца соединения. Правило «нужно учебное рукопожатие» мы сами записали в игрушку; что делает настоящий инспектор, нам неизвестно.

Итак. Режим synack стоит один круг, режимы syn и acksyn около одной целой восьми десятых секунды, а наша строгая игрушка проиграла во всех трёх режимах. Расщеплению SYN-ACK нужны флаг сервера и правило ядра, которое отдаёт программе пакеты сервера. О настоящем оборудовании мы по-прежнему ничего не знаем.


## В коде: syndata шаг за шагом и подмена первого пакета

Теперь код. Вопрос сцены: из каких шагов состоит syndata и откуда берётся обломок, который шёл следом за ней?

Игрушка, пример выдуманный. Вы делаете ксерокопию письма, дописываете в копию несколько строк, отправляете копию, а оригинал рвёте.

Первый шаг, ворота. Пакет должен быть TCP, и из двух флагов, SYN и ACK, стоит только SYN. Любой другой пакет рукопожатия, например ACK, выключает экземпляр: «задача выполнена». В нашем тесте это записано в журнале на первом же подтверждении.

Второй шаг, глубокая копия пакета, в коде deepcopy. Она делается сразу после ворот, и все правки идут в копию. Поэтому важен порядок со wssize: что исправлено раньше, попадает в копию, что позже, не попадает.

Третий шаг, полезная нагрузка. Её берёт функция blob из аргумента с тем же именем. Если аргумента нет, это шестнадцать нулевых байт. Можно написать «ноль икс» и шестнадцатеричные цифры, можно назвать заготовку. Неизвестное имя даёт ошибку: в нашем тесте программа записала «заготовка не найдена», обычный SYN ушёл без изменений, и соединение заработало.

Четвёртый шаг, условия: функция apply_fooling из выпуска «Фейк и его условия». Она применяется один раз, к единственному SYN, поэтому срок жизни один убивает рукопожатие: в нашем тесте SYN умирал на роутере, и клиент сразу получал ошибку «нет маршрута». С плохой суммой сервер молчал, а клиент повторял SYN примерно раз в секунду.

Пятый шаг, опция tls_mod. Ей передают только заготовку, настоящего приветствия нет. Поэтому rnd и sni сработали: в нашем тесте имя в заготовке сменилось с microsoft на google, и SYN стал в шестьсот семьдесят три байта. А dupsid ничего не сделала: ей нечего копировать, и SYN остался в шестьсот восемьдесят байт, как сама заготовка fake_default_tls, которую мы указали.

Шестой шаг, отправка. Копию отдаёт ядру отправщик копии, в коде rawsend_dissect_ipfrag. Если отправка удалась, стратегия возвращает «выбросить» для оригинала; если нет, оригинальный SYN уходит как есть. Двадцать байт заголовка IP, сорок заголовка TCP и шестнадцать данных: семьдесят шесть.

Проверь себя. Стратегия syndata с blob равным «ноль икс один шесть ноль три». Сколько байт данных будет в SYN?

Два. «Ноль икс» и четыре цифры дают два байта. В нашем тесте SYN нёс два байта данных, и соединение открылось.

![[wiki-anim-video09-s25-syndata-six-steps.svg|Шесть шагов syndata в коде]]

Теперь оригинал, который syndata выбросила. Это первый пакет клиента, и программа подменяет его: назовём это подменой первого пакета, в коде ct_new_postnat_fix. Причину называет комментарий автора: если программа стоит после преобразования адресов, выбрасывание первого пакета заставит ядро забыть соединение. В нашей лаборатории преобразования адресов нет, и саму защиту мы не проверяли.

Подмена срабатывает, если стратегии вернули «выбросить» и счётчик пакетов роли равен единице: в режиме клиента это первый пакет клиента, в режиме сервера первый пакет сервера. Этот счётчик хранится в записи о соединении, об этом выпуск «Память о соединении».

Дальше три действия. Скопировать весь исходный пакет, записать в заголовок IP протокол двести пятьдесят пять и срок жизни один и вернуть «изменить». Контрольную сумму заголовка функция не пересчитывает: автор пишет, что она, вероятно, станет неверной.

Так выглядит обломок в нашем тесте. Журнал: «применяем обход для ядра» и «пакет изменён, шестьдесят байт в шестьдесят». В заголовках различаются два байта, срок жизни и протокол, а поле контрольной суммы осталось прежним и теперь неверно.

Остальные выброшенные пакеты исчезают по-настоящему: счётчик уже не один. Так было в нашем тесте с приветствием oob, третьим пакетом клиента, и с повторным SYN, который клиент отправил через секунду. А в режиме сервера synack_split выбрасывает первый пакет сервера, SYN-ACK, и вместо него уходит обломок: он виден между сервером и роутером, клиент его не получает.

Оговорка про лабораторию, и только про syndata. В пяти из шести наших запусков syndata без задержки на линии SYN-ACK приходил раньше обломка, и клиентское ядро запутывалось: на проводе нет подтверждения рукопожатия, но соединение всё равно открылось. Счётчик недействительных пакетов в таблице соединений клиента вырос на шесть, неудавшихся вставок и сброшенных пакетов на один. Объяснение наше, ядро мы не читали. По общим сведениям, на длинных путях круг больше времени работы программы, но мы это не проверяли.

![[wiki-anim-video09-s25-first-packet-swap.svg|Подмена первого пакета после syndata]]

Итак. В syndata шесть шагов: ворота на голый SYN, глубокая копия, заготовка, условия на единственный SYN, tls_mod без настоящего приветствия и отправка с вердиктом «выбросить». Когда выброшен первый пакет роли, вместо него уходит обломок с протоколом двести пятьдесят пять и сроком жизни один; по словам автора кода, чтобы ядро не забыло соединение.


## В коде: rst, synack и synack_split рядом

Три короткие функции подряд. Вопрос сцены: что каждая из них берёт у настоящего пакета, что подменяет и что возвращает движку?

Игрушка, пример выдуманный. Три копии чека. На первой зачёркнуто всё и написано «отмена»: это rst. На второй добавлена отметка «принято»: это synack. Третья разрезана пополам: это synack_split.

Первая, rst. Ворота: TCP и известный тип нагрузки, то есть любой распознанный; в нашем тесте это было приветствие TLS. Дальше копия пакета с пустыми данными, а флаги заменены на флаг сброса: заменены, а не дополнены, поэтому остальные флаги приветствия исчезают. Номера, окно и опции остаются, условия применяются к сбросу, идентификатор IP не трогается.

Вердикта у rst нет: настоящее приветствие идёт следом. Если приветствие пришло по частям, сброс уходит только перед первой, подробности в выпуске «Сборка и повторный проигрыш». В нашем тесте, со сроком жизни один: сброс с номером приветствия, полем подтверждения приветствия при снятом флаге ACK, окном шестьдесят три, длиной пятьдесят два байта и тем же идентификатором IP, что у приветствия. Он ушёл за шесть сотых миллисекунды до приветствия.

Вторая, synack. Копия SYN, к флагам добавлен ACK, и всё: номер последовательности SYN, подтверждение ноль, окно шестьдесят четыре тысячи двести сорок, опции SYN. Условия вроде срока жизни функция не применяет: она сразу отправляет копию, и вердикта нет. Но плохую сумму и повторы читает сама отправка, поэтому они работают.

В нашем тесте synack с условием «срок жизни один» отправил подложный SYN-ACK со сроком жизни шестьдесят четыре, и сервер ответил сбросом. Условие функция просто не прочитала.

![[wiki-anim-video09-s26-copies-rst-synack-code.svg|Копии пакета: rst и synack]]

Третья, synack_split. Ворота: оба флага, SYN и ACK. Режим synack: копия с флагами без ACK и отправка, копия с флагами без SYN и отправка. Обе копии берутся от одного пакета, поэтому опции SYN-ACK остаются и у чистого ACK.

Если обе отправки удались, вердикт «выбросить»; если нет, «пропустить», и настоящий SYN-ACK уходит. Неизвестный режим даёт ошибку, и в нашем тесте уходит обычный SYN-ACK.

Что общего и чем они различаются. У synack и synack_split на любом другом пакете экземпляр выключается целиком: задача выполнена. У rst иначе: он выключает только встречное направление, а на своём работает каждый раз, но подходит ему лишь приветствие. В нашем тесте журнал показал для rst отключение только входящего направления, а для двух других отключение в обоих.

Проверь себя. Стратегия synack с условием «плохая сумма». Дойдёт ли подложный SYN-ACK до сервера и что сервер сделает?

Дойдёт и будет молча отброшен из-за неверной суммы: её читает сама отправка. В нашем тесте на карте сервера этот пакет был, ответа на него не было, а настоящий SYN прошёл.

![[wiki-anim-video09-s26-split-code-and-compare-table.svg|Расщепление ответа и три функции рядом]]

Итак. Rst копирует приветствие и заменяет флаги на сброс, synack копирует SYN и добавляет ACK, synack_split копирует SYN-ACK и режет его на два пакета. Срок жизни и остальные условия читает только apply_fooling, а зовёт его из этих трёх один rst; плохая сумма и повторы доходят до всех трёх, потому что их читает сама отправка. Вердикт «выбросить» возвращает только synack_split.


## В коде: wsize, wssize и переписчик окна

Окна в коде. Вопрос сцены: как несколько строк превращают обычную цифру окна в ложь?

Игрушка, пример выдуманный. Табличка «мест нет» на двери. Её можно повесить на одну дверь, а можно на каждую по очереди, пока кто-нибудь не войдёт с вещами.

Сначала помощник, переписчик окна, в коде wsize_rewrite. Если задан аргумент wsize, он записывает это число в поле окна как есть. Если задан scale, он ищет в опциях множитель окна: множитель можно только уменьшить, а если опции нет, функция её не добавляет. В конце она возвращает «да», если что-то изменила.

Пример из нашего теста. В SYN окно шестьдесят четыре тысячи двести сорок стало единицей, а множитель десять стал шестью. На подтверждении опции множителя нет, поэтому переписчик поменял только окно: шестьдесят три стало единицей. Окно один при множителе шесть это шестьдесят четыре байта, при множителе десять было бы тысяча двадцать четыре; это согласуется с первым куском ответа сервера в шестьдесят четыре байта.

Теперь wsize. Ворота: TCP и оба флага, SYN и ACK. Если переписчик что-то изменил, вердикт «изменить». Любой другой TCP-пакет выключает экземпляр.

Там, где это работает, на сервере, картина та же: SYN-ACK сервера имел окно шестьдесят пять тысяч сто шестьдесят и множитель десять, журнал записал «окно один» и «множитель шесть», и клиентское ядро получило SYN-ACK с этими числами.

А на клиенте первым экземпляр видит собственный SYN клиента. Это не SYN-ACK, и экземпляр выключается раньше, чем придёт ответ сервера. Мы даже разрешили входящие пакеты, и всё равно в нашем тесте приветствие ушло одним пакетом, а ответ сервера пришёл теми же пятью сегментами, что и без стратегии.

![[wiki-anim-video09-s27-rewriter-code-and-wsize-results.svg|Переписчик окна и функция wsize]]

Теперь wssize. Сначала он выключает встречное направление. Потом на исходящих пакетах зовёт переписчика. А если в пакете есть данные, выключает экземпляр целиком, причём этот пакет уже переписан. Поэтому лгут SYN, подтверждение и приветствие.

Лента окон клиента в нашем тесте: SYN несёт окно один и множитель шесть, подтверждение окно один, приветствие окно один, а дальше окно шестьдесят три, как честно считает ядро клиента. Приветствие это последний пакет, который переписан: ложь заканчивается на первых данных.

Принудительное отключение по первым данным задаёт аргумент forced_cutoff: список типов нагрузки, при которых выключаться. Если назвать тип, которому ничего не соответствует, экземпляр сам не выключится. В нашем тесте окно в единицу осталось у двенадцати пакетов клиента, а потом правило ядра перестало присылать пакеты программе, и ложь закончилась сама. А в запуске, где мы это отключение запретили, ответ сервера начинался десятью кусками по шестьдесят четыре байта.

Проверь себя. В SYN множитель окна десять, а в аргументе scale стоит двенадцать. Что сделает переписчик?

Ничего с множителем: его можно только уменьшить, функция пишет «не увеличиваем» и возвращает «нет». Это чтение кода, такой запуск мы не делали.

![[wiki-anim-video09-s27-wssize-code-ribbon-and-check.svg|Функция wssize, лента окон и проверка]]

Итак. Переписчик окна пишет число в окно и может только уменьшить множитель. Wsize переписывает один SYN-ACK, wssize переписывает исходящие пакеты до первых данных включительно. Диапазоны по умолчанию решают, где это работает: на клиенте wsize молчит.


## В коде: oob шаг за шагом

Теперь oob шаг за шагом. Вопрос сцены: как функция примерно в девяносто строк сдвигает весь поток на один байт и вставляет срочный байт?

Игрушка, пример выдуманный. Чтобы вставить лишнюю страницу в начало рукописи, вы начинаете нумерацию на единицу раньше, а читателя, который считает по-старому, просите: «в подтверждении прибавьте единицу». Этот читатель здесь ядро клиента.

Первый шаг, ворота. Нужна память о соединении, а первым пакетом, на который вызван экземпляр, должен быть SYN. Иначе запись «приём надо применять с самого начала» и отключение: сдвигать поток с середины нельзя.

Второй шаг, сдвиг. Функция берёт относительный номер: сколько байтов отправитель отправил после SYN. У SYN он ноль, у подтверждения и первых данных один; как его считает программа, разбирает выпуск «Память о соединении». Пока номер не больше единицы, из номера последовательности вычитается один. В нашем тесте SYN ушёл с номером на единицу меньше настоящего.

Третий шаг, ответ сервера. У SYN-ACK, который идёт к клиенту, функция прибавляет единицу к номеру подтверждения: сервер подтвердил сдвинутый номер, а ядро клиента ждёт настоящий. В нашем тесте подтверждение стало на единицу больше. Чтобы функция вообще увидела этот SYN-ACK, нужен диапазон входящих до эс один, из сцены про срочный байт: без него клиент в нашем тесте отвергал ответ сервера.

![[wiki-anim-video09-s28-gate-shift-ack-numbers.svg|Ворота, сдвиг и ответ сервера в oob]]

Четвёртый шаг, байт и его место. Байт задают аргументы char или byte, по умолчанию нулевой, и он обязан быть один. Место задаёт аргумент urp: «b» значит в начало, «e» в конец, а метка превращается в номер через переводчика меток. Для метки указатель срочности равен позиции плюс один; для значения по умолчанию «b» он ноль. В нашем тесте midsld дала позицию сто четыре, а указатель сто пять.

Пятый шаг, сборка пакета. Данные до позиции, потом срочный байт, потом остальное; к флагам добавляется флаг срочности; пакет отправляется, оригинал выбрасывается, экземпляр выключается. Арифметика такая: было двести пятьдесят байт, добавили один, стало двести пятьдесят один. Сервер подтверждает один за SYN плюс двести пятьдесят один: двести пятьдесят два. Приложение прочитало сто четыре байта до срочного и сто сорок шесть после.

Ловушка, которая вытекает из шага со сдвигом. Если на приветствии случается ошибка, например в аргументе char два символа, функция выходит, и приветствие уходит со старым номером, на единицу выше ожидаемого сервером. Сервер раз за разом подтверждал один и тот же номер, клиент повторял приветствие больше пяти раз, и приложение получило ноль байт.

Проверь себя. Аргумент urp равен method, а приветствие TLS. Что будет?

Метка method для приветствия не находится. Запись «не удалось разрешить», экземпляр отключается, приветствие уходит без изменений со старым номером, и соединение мёртвое. Так и было в нашем тесте.

![[wiki-anim-video09-s28-byte-build-trap-check.svg|Байт, сборка пакета и ловушка в oob]]

Итак. Oob проверяет, что видит SYN, вычитает единицу из номера у SYN, подтверждения и первых данных, прибавляет единицу в подтверждении сервера и заменяет первые данные пакетом со срочным байтом. Сдвиг не отменить, поэтому ошибка позже оставляет мёртвое соединение.


## В коде: режим сервера; итог выпуска и что дальше

Последний кусок кода и итог выпуска. Вопрос сцены: где в программе читается флаг сервера и что ещё он меняет?

Флаг разбирается в одной строке: без значения он включён, значение ноль его выключает. Разберём главные места, где он читается.

Первое, ярлык направления. Ярлык «входящий» получается так: «пакет пришёл от сервера» исключающее ИЛИ флаг. Исключающее ИЛИ значит: одинаковые значения дают «нет», разные «да». Флаг выключен, ярлык совпадает с ролью. Включён, ярлык перевёрнут. Это мы видели в журналах.

Второе, счётчики для диапазонов: для пакетов с ярлыком «исходящий» в режиме сервера берутся счётчики роли сервер.

Третье, фильтры профиля: порт берётся всегда серверный, а адрес в режиме сервера клиентский. Четвёртое, Lua: стратегия получает поле «исходящий» с тем же ярлыком.

Пятое, что в режиме сервера выключено. Нет автоматических списков имён: если профилю они нужны, программа при запуске выходит с сообщением. Нет и общего кэша сроков жизни между соединениями: в режиме сервера он не нужен, первый пакет и так входящий, срок жизни виден сразу. Как это сказывается на автоподборе срока жизни, то есть на аргументе ip_autottl из выпуска «Фейк и его условия», мы не проверяли. Эти два места мы прочитали только в коде.

![[wiki-anim-video09-s29-server-flag-xor-map.svg|Флаг сервера в коде и ярлык направления]]

Проверь себя. Режим сервера, пакет пришёл от клиента. По формуле «пакет от сервера» нет, флаг да. Какой ярлык?

Входящий: «нет» и «да» дают «да». В журнале нашего теста так и было.

Теперь итог выпуска «Фокусы рукопожатия». Мы работали вокруг начала передачи. Syndata кладёт в SYN данные, но сервер на Linux в нашем тесте подтверждает только сам SYN. Игрушка «первые данные решают» была обманута, игрушка, которая пропускает данные SYN, нет. Rst посылает сброс перед приветствием и не должен дойти до сервера целым: со сроком жизни один он умирает на роутере, с плохой суммой доходит, но сервер его игнорирует, а срок два и обычный сброс разорвали настоящее соединение.

Synack посылает подложный SYN-ACK раньше SYN: сервер отвечает безобидным сбросом, а игрушка с тем же правилом ролей перепутала направления. Oob сдвигает поток на единицу, и пути назад нет. Окна: wssize лжёт до первых данных, wsize на сервере заставляет клиентское ядро резать приветствие, а окно в сто байт при включённой сборке останавливает соединение.

На стороне сервера флаг переворачивает ярлыки, правила ядра зеркальны, и synack_split стоит один круг или почти две секунды. А у приёмов, которые выбрасывают самый первый пакет, syndata и synack_split, программа подменяет его обломком; по словам автора кода, чтобы ядро не забыло соединение.

Теперь о границах знания. Всё это мы видели в нашем тесте, на ядре Linux шесть точка восемнадцать и против игрушек, которые написали сами. О настоящем ТСПУ нам ничего не известно. Мы не проверяли другие получатели: Windows, BSD, телефоны, серверы с быстрым открытием соединения. Ядро мы не читали: объяснения про метки времени и про пропавшее подтверждение без задержки это наши чтения. Мы не знаем, почему клиент режет приветствие на сто семь, семьдесят два и семьдесят. И мы не пробовали IPv6, фрагменты и сеть за преобразованием адресов.

Что дальше. Все наши запуски шли по TCP поверх IPv4. В выпуске «Другие слои» мы выйдем за эти рамки: IPv6, фрагменты IP-пакетов, правки в запросе HTTP, QUIC и UDP, а также опции tls_mod, которые мелькнули здесь в syndata.

![[wiki-anim-video09-s29-recap-grey-list-next.svg|Итог выпуска, непроверенное и следующий выпуск]]

Итак. Вокруг начала передачи есть место для пяти приёмов и для режима сервера. Каждый работает против наших игрушек и нашего Linux, у каждого своя ловушка, а настоящее оборудование остаётся для нас неизвестным. Дальше слои, которых мы пока не касались.

## См. также

- [[08-rezat-i-poddelyvat-vmeste|Резать и подделывать вместе]] — предыдущее видео серии
- [[syndata]] — справочная статья о функции
- [[oob]] — справочная статья о функции
- [[ts-and-fooling]] — подмена полей пакета и условия

---

> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git2.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/video/09-fokusy-rukopozhatiya.md) · [скачать весь репозиторий одним zip-архивом](https://git2.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).
