О чём заметка

Это полный текст видео номер 9 из серии о том, как работает Zapret 2, разбитый по сценам, с анимированными схемами. Каждую сцену можно читать отдельно.

Как читать пометки

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

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

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

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

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

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

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

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

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

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

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

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

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

Обычное рукопожатие и приветствие Четыре пакета обычного соединения без стратегий на проводе клиента: SYN с номером 0, SYN-ACK сервера через 10 мс с подтверждением 1, ACK с номером 1 и приветствие в 250 байт с номером 1, причём в первых трёх пакетах данных нет и данные начинаются только с четвёртого. обычное соединение без стратегий: пакеты на проводе клиентаномера относительные: у SYN ноль, у первого байта данных один клиентсервер 1SYNS · номер 0 · данных 0 · 60 байт0 мс 2SYN-ACK от сервераSA · номер 0 · подтверждение 110,14 мс 3ACKA · номер 1 · подтверждение 110,20 мс 4приветствиеPA · номер 1 · данных 25010,29 мс здесь данных нетSYN, SYN-ACK, ACKздесь данныепервый байт данных: номер 1 выдуманный примертелефонный разговор: те же четыре шага «вы меня слышите?»как SYN «слышу, а вы меня?»как SYN-ACK «и я слышу»как ACK разговор по делукак приветствие подслушивающий слышит все три реплики, но решает по первой фразе по делу

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

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

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

Сколько пакетов у клиента Приветствие стоит четвёртым среди пакетов на проводе, но третьим среди пакетов самого клиента, потому что между SYN клиента и его ACK на проводе стоит SYN-ACK сервера, а в счёт клиента он не идёт. каким по счёту стоит приветствие: на проводе и среди пакетов клиента пакеты на проводе1SYNклиент → сервер2SYN-ACKсервер → клиент3ACKклиент → сервер4приветствие250 байт, номер 1 проверь себя: каким по счёту будет приветствие среди пакетов самого клиента?пакеты самого клиента??? пакеты самого клиента1SYN2ACK3приветствиев счёт клиентане идёт на проводе 4, у клиента 3: SYN-ACK сервера стоит между SYN и ACK клиентастратегии считают пакеты по сторонам: счёт клиента ведётся отдельно от счёта сервера договорённости о выраженияхв нашем тестенастоящий запуск в лабораториипо общим сведениямобщее знание, не наш опытТСПУ: неизвестнопро настоящее ничего не знаем

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

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

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

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

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

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

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

Лаборатория с кругом в десять миллисекунд В лаборатории выпуска между клиентом и сервером стоит один роутер с четырьмя точками съёмки и задержкой в пять миллисекунд на каждом его выходе, поэтому круг занимает десять миллисекунд, и в нашем тесте SYN-ACK приходит к клиенту на 10,14 мс после SYN. лаборатория выпуска: клиент, роутер, сервер и четыре точки съёмки клиентроутерсервер c0на карте клиента,после программыr0входв роутерr1выходиз роутераs0на картесервера срок жизни − 1 ← +5 мс+5 мс →задержка 5 мс на каждом выходе роутера: круг туда и обратно 10 мс роутер вычитает 1 из срока жизни: пакет со сроком жизни 1 виден на r0 и дальше не идёт круг в десять миллисекунд: обычное рукопожатие024681012мс SYN0 мс SYN-ACK10,14 мс круг ≈ 10 мс в лаборатории прошлых выпусков сервер стоял рядомбез задержки результаты опытов с данными в SYN нестабильны: счётчики ядра клиента показываютпакеты «недействительные» (invalid); почему так, по коду ядра не проверяли; с кругом 10 мс порядок один и тот жепо общим сведениям, в настоящей сети круг занимает десятки миллисекунд

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

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

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

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

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

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

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

Характеры игрушки и опыт без стратегии Игрушечный инспектор стоит на входе в роутер и в этом выпуске знает два характера, «первый решает» и «данные SYN не читает», а в опыте без стратегии он блокирует приветствие в 250 байт, так что оно и его повторы видны только на клиенте и на входе в роутер и сервер получает 0 байт, тогда как без игрушки приложение сервера читает 250 байт одним чтением. игрушка у входа в роутер: два характера и опыт без стратегии характеры игрушки: наша модель, не настоящее оборудование«первый решает»первый непустой пакет клиента решает навсегда; SYN с даннымитоже считается: есть имя — запрет, нет имени — чисто навсегда «данные SYN не читает»то же самое, но данные внутри SYN пропускает без разбораи ждёт настоящих данных после рукопожатия опыт без стратегии, игрушка «первый решает»: повторы за первые две секундыигрушка стоит на входе в роутер, до проверки срока жизни: так допускает наша модельc0r0r1s0пакетвремя 1SYNS · номер 0 · данных 0 2SYN-ACK от сервераSA · подтверждение 1 3ACKA · номер 1 4приветствиеPA · номер 1 · данных 25010,7 мс 5повтортот же номер, те же байты223,6 мс 6повтортот же номер, те же байты443,4 мс 7повтортот же номер, те же байты879,4 мс 8повтортот же номер, те же байты1747,4 мс игрушка приложение сервера получило0 байтклиент получил ответпусто для сравнения, без игрушки:250 байт одним чтением каждый характер, который читает клиента, блокирует обычное соединение:если дальше приветствие пройдёт, это заслуга приёма, а не слабой игрушкиприёмник: сервер на Linux 6.18; ядро принимает пакеты, приложение читает и записывает размеры чтений,запись [250] значит, что данные дошли целиком

Теперь приёмник. Это сервер на 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 мог бы принять их иначе; такой сервер мы не пробовали.

SYN с шестнадцатью байтами на проводе В нашем тесте стратегия syndata кладёт в SYN шестнадцать нулевых байт и заменяет оригинал обломком с номером протокола 255 и сроком жизни 1, который виден только на клиенте и на входе в роутер, сервер отвечает SYN-ACK с подтверждением 1, то есть принимает один SYN без данных, и приветствие в 250 байт идёт с номера 1 и доходит одним чтением. syndata: пакеты на проводе клиента, номера относительные --payload=empty --lua-desync=syndata --payload=emptyдействуем на пустые пакеты: SYN без данных именно такойsyndataпрозвище «SYN с данными» c0r0r1s0 1SYN с даннымиS · номер 0 · данных 16 · IP всего 76ttl 64видно: c0 r0 r1 s0 2обломок настоящего SYNпротокол 255 · TTL 1 · 60 байтttl 1видно: c0 r0 3SYN-ACK от сервераSA · подтверждение 1 4ACKA · номер 1 · подтверждение 1 5приветствиеPA · номер 1 · данных 250 обломок: байты пакета номер 2 из лабораториизаголовок IP, 20 байтдальше: заголовок TCP как у настоящего SYN4500003ce841400001ff3e750a0100010a020002dc6801bb1e349fbc… срок жизни 01 и протокол ff (255); было 40 и 06, то есть 64 и 6контрольная сумма IP неверна: 3e 75dc68 порт клиента, 01bb порт сервера 443меняется только номер протокола и срок жизни приложение сервера: чтения [250], ответ SERVER-REPLY, принятые байты равны оригиналусервер принял только SYN: подтверждение 1; шестнадцать байт в поток не попали выдуманный пример«алло» с запиской снаружи: подслушивающий читает записку, адресат только слово

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

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

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

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

Код syndata и номер приветствия В коде функции syndata проверка одна, у пакета стоит только SYN, дальше идёт копия пакета с блобом из шестнадцати нулей, отправка и выброс оригинала, а подтверждение сервера 1 равно 0 плюс 1 за сам SYN, и если бы сервер принял шестнадцать байт, оно было бы 17 и приветствие шло бы с номера 17. syndata в коде и проверка подтверждения lua/zapret-antidpi.lua, строки 397-407 397if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN thenтолько SYN 398 local dis = deepcopy(desync.dis)копия пакета 399 dis.payload = blob(desync, desync.arg.blob, "\x00\x00…")блоб, по умолчанию 16 нулей строки 400-404: условия и правка блоба, в следующих сценах 406 if rawsend_dissect_ipfrag(dis, desync_opts(desync)) thenотправка копии 407 return VERDICT_DROPоригинал выбросить проверь себя: с какого номера пошло бы приветствие,если бы сервер принял шестнадцать байт из SYN? ? если бы принялигипотезаSYNномер 0данные SYN: 16 байтномера 1…16приветствие с номера 170 + 1 + 16 = 17 в нашем тестена проводеSYNномер 0приветствие с номера 1, 250 байт0 + 1 = 1 подтверждение сервера 1, номер приветствия 1: данные SYN в поток не попалив нашем тесте сервер с Linux их не принял; по общим сведениям, сервер с быстрой отправкой данных в SYNмог бы принять иначе, такой сервер мы не пробовали

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

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

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

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

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

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

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

Один пакет и два исхода В нашем тесте SYN с шестнадцатью нулевыми байтами без имени сайта обманул игрушку «первый решает», которая объявила поток чистым, и приветствие дошло до сервера, а игрушку «данные SYN не читает» он не обманул: она пропустила SYN, нашла имя в приветствии и заблокировала его вместе с четырьмя повторами, так что сервер получил 0 байт. syndata против двух характеров игрушки: пакеты на проводе клиента выдуманный примердва привратника: первому с «алло» дают записку, второй её не читает «первый решает» TOY DECISION CLEAN: first client payload(len 16, syn=true) has no host name: flowmarked clean and never inspected again c0r0r1s0 S · номер 0 · данных 16 обломок · протокол 255 · TTL 1 SA · подтверждение 1 A · номер 1 PA · номер 1 · данных 250 PA · ответ сервера 14 байт приложение сервера: [250]ответ получен «данные SYN не читает» TOY DECISION skip: payload of a SYN is ignoredTOY DECISION BLOCK: first client payload(len 250) holds the host name c0r0r1s0 S · номер 0 · данных 16 обломок · протокол 255 · TTL 1 SA · подтверждение 1 A · номер 1 PA · номер 1 · данных 250 тот же PA: 4 повтора за 2 с приложение сервера: []ответ пустой один и тот же пакет на проводе, два разных исходачитает ли такие данные настоящее оборудование, мы не знаем: вторая игрушка нужна, чтобы помнить об этом допущении

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

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

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

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

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

Что лежит в SYN Размер SYN с данными зависит от блоба: шестнадцать нулей по умолчанию дают пакет IP в 76 байт, встроенная заготовка приветствия в 680 байт даёт 740, два байта дают 62, правка имени сайта делает блоб короче на 7 байт, то есть 673, а блоб с несуществующим именем даёт обычный SYN, и во всех опытах сервер получает те же 250 байт, а вместе с multisplit приложение читает 104 и 146 байт. что лежит в SYN: заготовки, размеры и результат что просилибайтIP всегочто видно по умолчанию: 16 нулей167620 + 40 + 16 = 76игрушка «первый решает»: чисто blob=fake_default_tls68074020 + 40 + 680 = 740; имя в байтах 125…141игрушка: чисто (len 680) blob=0x160326220 + 40 + 2 = 62сервер ведёт себя так же tls_mod=rnd,dupsid,sni=google.com673733680 − 7 = 673имя на 7 байт короче, поля случайные tls_mod=dupsid680740dupsid ничего не сделал: байты равны заготовкепочему, объясним в сцене про код blob=nosuchblob060программа пишет «блоба нет» и пропускаетпакет: уходит обычный SYN без данных имя сайта в SYN: байты, номера с нулязаготовка по умолчаниюбайты 125…141www.microsoft.com после sni=google.comбайты 125…134google.com17 − 10 = 7 байт короче syndata вместе с multisplitSYN16 байтобломокпротокол 255SAподтв. 1Aномер 1PA104 байтаPA146 байт приложение сервера: чтения [104, 146], вместе 250 байтигрушка решила на SYN и частей приветствия не смотрела; syndata выключается сам на первом пакете,который не SYN, а разрез работает над приветствием: в нашем опыте приёмы не мешают друг другу во всех опытах с блобами приложение сервера получило те же 250 байт приветствия

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

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

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

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

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

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

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

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

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

Условия на единственном SYN Если у SYN с данными срок жизни один, то в нашем тесте он виден только на клиенте и на входе в роутер, роутер возвращает сообщение об истёкшем сроке и клиент сразу получает ошибку «нет маршрута», а с испорченной контрольной суммой четыре таких SYN доходят до сервера без ответа и клиент завершается по таймауту, потому что оригинал выброшен и другого SYN в сети нет. условия подделки на SYN с данными: оригинала нет, этот SYN единственный оригинал SYNSYN с данными = единственный SYNударит по самому соединению выдуманный примергонец с одним письмом «алло»: марка просрочена, письмо не доедет, второго «алло» нет syndata:ip_ttl=1c0r0r1s0 SYN · данных 16 · TTL 10,00 мс обломок · протокол 255 · TTL 10,15 мс ICMP тип 11: роутер сообщает клиенту5,11 мс клиент: [Errno 113] No route to host, сразусервер: ни одного пакета (r1 и s0 пусто)в лаборатории нет правила, которое выбрасывало бы такие сообщения роутера;что было бы с правилом из примера настроек проекта, мы не проверяли проверь себя: дойдёт ли SYN с испорченной суммой до сервера, будет ли соединение?? syndata:badsumc0r0r1s0 SYN · данных 16 · сумма TCP неверна0,00 мс обломок · протокол 255 · TTL 10,05 мс повтор SYN, та же неверная сумма1004,5 мс повтор SYN2028,0 мс повтор SYN3052,1 мс клиент: завершился по таймаутусервер: 4 SYN на карте, ответов ноль сервер молчит: сумма неверна, настоящего SYN в сети нет вообщеклиент повторяет SYN примерно раз в секунду, и каждый повтор программа снова заменяет таким же испорченным

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

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

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

Повтор SYN и профиль без имени Когда лабораторное правило выбросило первый SYN с данными, клиент повторил его через 1030 мс, программа снова подставила SYN с данными без обломка и соединение установилось примерно за 1,04 с, а профиль со списком имён на SYN не подходит, потому что имени сайта в SYN нет, и приём в нём не запускается. повтор SYN: стратегия действует снова; профиль со списком имён: не действует первый SYN с данными потерянc0r0r1s0 SYN · данных 160,00 мс обломок · протокол 255 · TTL 10,04 мс повтор SYN · данных 16, без обломка1029,97 мс SYN-ACK от сервера1040,10 мс ACK1040,46 мс приветствие · данных 2501040,59 мс первый SYN выбросило специальное правило лаборатории; в настоящей сети такого правила нет LUA: syndata: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00packet: id=2 dropнастоящий выброс оригинала повтора: это уже не первый пакет, подмены нет соединение: около 1,04 сприложение сервера: [250]обломков в сети: один стратегия действует на каждом повторе SYN, пока не придёт пакет другого вида;по коду она выключается на первом пакете, который не SYN профиль со списком имёнdesync profile 0 (no_action) matchesno lua functions in this profileв журнале: профиль без действия,в этом профиле нет функций SYN: имени сайта нетпрофиль со списком имён не подходитsyndata не запускается приём работает только в профилях, которым имя не нужно;порядок стратегий в строке для него тоже важен: см. часть про окна

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

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

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

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

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

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

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

Сброс перед приветствием на проводе клиента Сброс перед приветствием на проводе клиента: после трёх пакетов рукопожатия уходит пакет с единственным флагом RST, номером 1, подтверждением 1 при снятом флаге ACK, сроком жизни 1 и длиной по заголовку IP 52 байта, сразу за ним через 0,06 мс идёт приветствие в 250 байт со сроком 64, сброс видят клиент и вход роутера и дальше он не идёт, а роутер примерно через 5 мс возвращает сообщение «срок жизни истёк», игрушка «сброс стирает память» записывает, что соединение забыто, и сервер получает 250 байт одним чтением и отвечает. провод клиента в нашем тесте по порядку отправки, номера относительные --payload=tls_client_helloфильтр: о нём в следующей сцене rstсброс перед приветствием ip_ttl=1срок жизни 1: умрёт на роутере клиентроутерсервервремя, мс 1-3рукопожатие, как обычноSYN, SYN-ACK, ACK0,00 · 10,58 · 11,05 4RST: единственный флагномер 1 · подтверждение 1, флага ACK нет19,63TTL 1 · IP 52 Б 5приветствие: PSH, ACK, 250 байтномер 1 · TTL 64 · IP id тот же, что у RST19,69+0,06 мс после RST 6роутер: ICMP тип 11 «срок жизни истёк»ответ на пакет, который он убил24,93+5,3 мс после RST 7+сервер: подтверждение 251 и ответ 14 байтSERVER-REPLY: соединение на сервере целое30,36 кто увидел RST: карта клиента и вход роутера; выход роутера и карта сервера нет пакет 4 по байтам IP 20TCP 20опции 12NOP NOP TS20 + 20 + 12 = 52 флаги Rномер 1подтверждение 1 скопировано, флаг ACK снятокно 63сумма верна игрушка и сервер: итогвыдуманный пример игрушка «сброс стирает память»видит пакет 4 на входе роутераTOY DECISION FORGET: RST seen (from client):flow state erased, never inspected againприветствие дальше не проверяется приложение сервераполучило 250 байт одним чтениемответ SERVER-REPLYдо сервера RST не дошёлдопущение: игрушка стоит до проверки TTL что видит настоящее оборудование, мы не знаем и проверить не могли: это допущение нашей модели

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

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

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

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

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

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

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

Игрушка стирает память и код функции rst Игрушка «сброс стирает память» и код функции rst: без стратегии игрушка блокирует приветствие и сервер получает 0 байт, а сброс со сроком жизни 1 умирает на роутере и стирает её память, поэтому приветствие проходит и сервер получает 250 байт, восемь строк основного блока копируют приветствие без данных, заменяют флаги на единственный RST, применяют условия строки запуска и отправляют сброс без вердикта, а аргумент rstack меняет только флаг, подтверждение остаётся равным 1. игрушка «сброс стирает память» и код функции rstвыдуманный пример третий характер игрушки: сброс стирает памятьлюбой RST в любую сторону: забыть соединение навсегда; пока сброса нет, имя сайта ищется в каждомклиентском пакетеаналогия: клерк рвёт папку дела на слове «отмена», а адресат слова не услышал без стратегииклиентроутерсерверприветствиеигрушка: BLOCK0 байтигрушка блокирует приветствие rst:ip_ttl=1клиентроутерсерверRSTприветствиеигрушка: FORGET250 байтRST стёр память, приветствие прошло код функции rst: основной блок, восемь строкстроки 430-437 430local dis = deepcopy(desync.dis)431dis.payload = ""432dis.tcp.th_flags = TH_RST + (desync.arg.rstack and TH_ACK or 0)433apply_fooling(desync, dis)434apply_ip_id(desync, dis, nil, "none")435DLOG("rst")436-- it uses rawsend, reconstruct and ipfrag options437rawsend_dissect_ipfrag(dis, desync_opts(desync)) копия пакета (приветствия) нагрузка пустая флаги: RST; ACK если rstack условия строки запуска: ip_ttl=1 IP id по умолчанию не меняется запись в журнал комментарий автора отправка сброса сразу функция вердикт не возвращает: приветствие идёт следом, как есть проверь себя: аргумент rstack добавляет флаг ACK, что станет с полем подтверждения? rst: флаги R, подтверждение 1rstack: флаги RA, подтверждение 1ничего: меняется только флаг срок 1, ICMP, игрушка FORGET

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

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

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

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

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

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

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

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

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

Три опыта со сбросом и докуда он доходит Три опыта со сбросом перед приветствием и то, докуда он доходит: со сроком жизни 1 сброс умирает на роутере, сервер его не получает, приложение читает 250 байт и игрушка стирает память, со сроком 2 и без условий сброс доходит до сервера, сервер отвечает сбросом с окном 0, клиент получает ошибку «соединение сброшено» и приложение сервера получает 0 байт, а с испорченной суммой сброс доходит, но сервер его игнорирует, приложение получает 250 байт и игрушка всё равно стирает память. докуда доходит сброс и что делает сервервыдуманный пример игрушка: «отмена» всё-таки дошла до адресата, он закрыл дверь,и следующее письмо упёрлось в закрытую дверь клиентроутерсервер rst:ip_ttl=1для сравнения, предыдущая сценаRST, TTL 1приветствиесервер: сброс не получалклиент: ошибки нетприложение сервера: 250 байтигрушка: FORGET, всё прошло rst:ip_ttl=2роутер один, сброс его переживаетRSTприветствиеответ сервера: RST, окно 0на карте сервера сброс с TTL 1сервер закрыл соединение: RST, окно 0клиент: Errno 104, сброшеноприложение сервера: 0 байт rstникакого условия, TTL 64RSTприветствиеответ сервера: RST, окно 0сброс идёт с TTL 64, доходитисход тот же: ответ RST, окно 0клиент: Errno 104, сброшеноприложение сервера: 0 байт rst:badsumневерная сумма TCP у сбросаRST, сумма BADприветствиесброс на карте сервера, сумма BADсервер его игнорируетприложение сервера: 250 байтигрушка: FORGET, сумму не смотрит сброс: что делает сервер TTL 1не получает сброс, соединениецелое TTL 2 или без условийполучает и закрываетсоединение badsumполучает и игнорирует,соединение целое

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

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

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

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

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

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

Четыре опыта со сбросом: повтор, номер, фильтр, длинное приветствие Четыре опыта со сбросом: при repeats равном 3 и сроке жизни 1 уходят три одинаковых сброса и от роутера возвращаются три сообщения «срок жизни истёк», при ip_id равном rnd идентификатор IP у сброса 5332 а у приветствия 4198, тогда как без аргумента оба равны 27216, без фильтра типа нагрузки сброс запускает только приветствие, а не SYN и пустые ACK, и в журнале одна строка о сбросе, а приветствие в 1484 байта делится на куски по 1448 и 36 байт, один сброс со сроком 1 уходит перед первым куском, перед вторым его нет, и сервер читает два куска двумя чтениями. ещё четыре опыта со сбросом: повтор, номер IP, фильтр, длинное приветствие rst:ip_ttl=1:repeats=3 клиентроутерRST × 3 проверь себя: сколько сообщений «срок жизни истёк»вернётся от роутера? ICMP × 3 три, по одному на каждый сбросприветствие одно: 250 байт, на сервер оно доходит ip_id=rnd: случайный идентификатор IP без аргументасброс 27216=приветствие 27216равны ip_id=rndсброс 5332≠приветствие 4198разные без фильтра вида данных: что запускает сброс SYNне запускает SYN, ACKне запускает пустой ACKне запускает приветствиезапускает журнал:LUA: rstодна строка запускает пакет с распознанным протоколом; «вид данных» и «тип нагрузки» это одно и то же приветствие 1484 байта: сброс только перед первым куском кусок 1: 1448 байт36всего 1484 байта: ядро режет на два куска клиентроутерсервер RST, TTL 1, перед первым куском11,07 мс кусок 1: ACK, 1448 байт11,12 мс кусок 2: PSH, ACK, 36 байт11,24 мс роутер: ICMP тип 1116,12 мс LUA: rstи затем: LUA: rst: not acting on further replay piecesсервер получил оба куска двумя чтениями: 1448 и 36

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

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

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

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

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

Ответ раньше вопроса на проводе клиента Приём synack на проводе клиента по порядку: подложный SYN-ACK с номером 0, подтверждением 0 и длиной 60 байт, через 0,04 мс настоящий SYN с тем же номером и тем же идентификатором IP, ответ сервера сбросом без флага ACK с окном 0 и номером 0, настоящий SYN-ACK сервера с подтверждением 1 и ACK клиента, а приветствие в 250 байт появляется на проводе только на 223-й миллисекунде, через 212,8 мс после ACK, причём в нумерации IP пропущен один идентификатор, потому что программа увидела две копии приветствия, а на проводе осталась одна, и приложение сервера получило 250 байт. ответ раньше вопроса: провод клиента по порядку отправкивыдуманный пример игрушка: телефон. кто-то говорит «слышу вас» раньше, чем ему позвонили;подслушивающий записывает в протокол: этот человек отвечающий --lua-desync=synackответ раньше вопроса, без условий клиентсервермс · IP id 1SYN-ACK от клиента, а не от сервераномер 0 · подтверждение 0 · окно 64240 · 60 Б0,00id 39080 2настоящий SYN, номер тот жечерез 0,04 мс · опции и окно как у № 10,04id 39080, тот же 3ответ сервера: сброс, а не SYN-ACKфлага ACK нет · окно 0 · номер 0 · TTL 6310,15 4настоящий SYN-ACK сервераподтверждение 110,17 5ACK клиента: рукопожатие законченономер 110,51id 39081 пауза: на проводе клиента ничего нет 212,8 мсid 39082: нет 6приветствие: PSH, ACK, 250 байтчерез 212,8 мс после ACK223,32id 39083 = 39081 + 2 номера относительные, кроме сброса сервера и подтверждения подложного пакета: там настоящий ноль что видела сама программа в журнале приветствие прошло дваждыpacket: id=4 pass unmodifiedpacket: id=5 pass unmodifiedдва пакета по 302 байта, первый на 10,70 мсIP id первой копии 39082 на проводе клиента одна копияid 39083 на 223,32 мсid 39082 на проводе нет: дыра в нумерациипохоже на повтор по таймеру: это догадка,счётчиков ядра не снимали приложение сервера: 250 байт одним чтением, ответ SERVER-REPLY

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

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

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

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

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

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

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

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

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

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

Условия на synack и код приёма Условия на приёме synack и пять строк его кода: с условием срока жизни 1 подложный SYN-ACK идёт со сроком 64 и доходит до сервера, который отвечает сбросом, с испорченной суммой он тоже доходит, но сервер молчит и приветствие уходит сразу за ACK, через 0,17 мс, а без порчи приветствие появляется на проводе через 212,8 мс и 213,7 мс, код проверяет, что у пакета только SYN, копирует его, добавляет флаг ACK, пишет в журнал и отправляет напрямую без обработки условий, поэтому срок жизни в нём не читается. условия на synack и код функции проверь себя: synack:ip_ttl=1, умрёт ли подложный SYN-ACK на роутере? нет: срок жизни остаётся 64, пакет доходит до сервера, и сервер отвечает сбросом запускTTL подложного:клиент → серверсуммаTCPответсервераприветствие напроводе после ACK synackбез условий64 → 63вернасброс, окно 0+212,8 мс synack:ip_ttl=1срок 1 не действует64 → 63вернасброс, окно 0+213,7 мс synack:badsumсумма действует64 → 63BADмолчит+0,17 мс во всех строках приложение сервера получило 250 байт; задержка есть только там, где сервер ответил сбросом это совпадение в наших опытах, а не объяснение код функции synack: пять строкстроки 299-303 299if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then300local dis = deepcopy(desync.dis)301dis.tcp.th_flags = bitor(dis.tcp.th_flags, TH_ACK)302DLOG("synack: sending")303rawsend_dissect_ipfrag(dis, desync_opts(desync)) проверка: у пакета только SYN копия этого SYN к флагам добавлен ACK запись в журнал отправка напрямую вердикта функция не даёт: настоящий SYN уходит следом срок жизни читает только apply_fooling, а его здесь нет: поэтому ip_ttl не действует badsum и repeats входят в desync_opts (строки 423-431 общей библиотеки): поэтому badsum действует

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

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

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

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

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

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

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

Правило ролей в памяти о соединении Как программа nfqws2 назначает роли в памяти о соединении и что из этого получается: запись создаётся для голого SYN, отправитель которого считается клиентом, а для голого SYN-ACK признак обратный и адреса с портами берутся в перевёрнутом порядке, так что отправитель окажется сервером, поэтому подложный SYN-ACK клиента даёт запись, где клиент и сервер поменялись местами; это правило нашей программы, которое мы дали игрушке, и как у настоящих устройств, мы не знаем. память о соединении: как программа назначает роливыдуманный пример игрушка: секретарь пишет протокол разговоракто первым сказал «алло», тот звонящий; кто первым «слышу вас», тот отвечающийесли «слышу вас» прозвучало первым не от того, протокол читается наоборот память о соединении: запись, где отмечено, кто клиент и кто серверподробно: выпуск «Память о соединении»; у программы nfqws2 такая память есть правило ролей в коде программыnfq2/conntrack.c, строки 278-283 278b_rev = dis->tcp && tcp_synack_segment(dis->tcp);279if ((dis->tcp && tcp_syn_segment(dis->tcp)) || b_rev || dis->udp)280{281if ((ctr = ConntrackNew(pp, b_rev ? &connswp : &conn)))282{283ConntrackFeedPacket(&ctr->track, b_rev, dis); «обратный» = пакет SYN-ACK вход: голый SYN или SYN-ACK обратный: ключ наоборот запись получает пакет голый SYN: из флагов URG, ACK, PSH, RST, SYN, FIN стоит только SYN (nfq2/darkmagic.c, строки 93-102) что запомнит программа первый голый SYNнастоящий клиентв записи: клиентнастоящий серверв записи: серверотправитель этого пакета — клиентзапись обычная: роли на своих местах первый голый SYN-ACKнастоящий клиентв записи: сервернастоящий серверв записи: клиентзапись создаётся наоборот: адреса и портыв обратном порядке, отправитель — сервер это правило нашей программы, мы дали его игрушке; так ли у настоящих устройств, мы не знаем

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

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

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

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

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

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

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

Две игрушки на подложном SYN-ACK Две игрушки на подложном SYN-ACK: игрушка «роли» помнит, что подложный SYN-ACK и пакеты клиента идут от сервера, потому что первый SYN-ACK делает отправителя сервером, поэтому приветствие в 250 байт идёт в направлении, которое она считает входящим, и она решает пропустить, игрушка «строгое рукопожатие» видит, что первый пакет не SYN клиента, записывает рукопожатие как нечистое и тоже пропускает, в обоих опытах приложение сервера получает 250 байт, а без приёма обе блокируют приветствие; условия срока жизни 1 и испорченной суммы проверены только на игрушке «роли» с тем же решением, а слова автора про разворот направления соединения не доказательство. две игрушки на подложном SYN-ACKнаша игрушечная модель игрушка «роли»: ищет имя только в пакетах направления, которое её память называет исходящим проверь себя: клиент отправил подложный SYN-ACK, кого игрушка теперь считает сервером? настоящего клиента: его пакеты для неё «не исходящие» №пакетотправительпамять игрушкирешение 1SYN-ACK, подложныйклиентисходящий: нет 2SYN, настоящийклиентисходящий: нет 3RST, ответ серверасерверисходящий: да 4SYN-ACK серверасерверисходящий: дароли поменялись 5ACKклиентисходящий: нет 6приветствие, 250 байтклиентисходящий: нетSKIP: «входящее», не проверяю красное: игрушка считает наоборот; у настоящего клиента везде «нет», у настоящего сервера везде «да» без стратегии: BLOCK, имя найденоприложение сервера: 0 байтсо стратегией synack: SKIP, прошлоприложение сервера: 250 байт игрушка «строгое рукопожатие»: ждёт по порядку, потом смотрит данные SYN клиентасостояние 0 → 1SYN-ACK сервераверный ack · состояние 1 → 2ACK клиентасостояние 2 → 3данные: смотрюBLOCK по имени с подложным SYN-ACK первый пакет не клиентский SYN: INVALID: рукопожатие нечистое, не проверяю; приветствие прошлоприложение сервера: 250 байт какие условия проверены на какой игрушке ролистрогое рукопожатие synackSKIPINVALID synack:ip_ttl=1SKIPне запускали synack:badsumSKIPне запускали для «строгого рукопожатия» срок жизни и сумма не читаются по её коду: вывод из кода, не из опыта что пишет автор в документации: «TCB turnaround»,разворот направления, чтобы запутать наблюдателя (docs/manual.en.md, строка 4345)слова автора, не доказательство; кода, который это доказывает, у нас нет

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

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

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

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

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

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

Три приёма до приветствия в одной таблице Итоговая таблица первой части выпуска про три приёма до приветствия: SYN с данными уводит с клиента SYN с 16 байтами и пакет-обломок, сервер подтверждает 1 и данные не берёт, приём обманул игрушку «первый решает» и не обманул «данные SYN не читает», а ломается на условиях, которые убивают единственный SYN; сброс перед приветствием шлёт RST со сроком 1, сервер его не получает и принимает 250 байт, приём обманул игрушку «сброс стирает память», а ломается при сроке 2 или без условий; ответ раньше вопроса шлёт подложный SYN-ACK, сервер отвечает сбросом, приветствие приходит с задержкой около 0,2 с, приём обманул игрушки «роли» и «строгое рукопожатие», условие срока жизни на нём не действует, а испорченная сумма действует. что мы знаем: три приёма, пять игрушек, один наш сервернаши игрушки и наш Linux что уходитс клиентачто делаетнаш серверкого из нашихигрушек обманулчемломается syndataSYN с даннымиSYN с 16 байтами, поумолчанию нули;IP 76; рядомпакет-обломок:протокол 255, TTL 1подтверждает 1,данные SYN в потокне берёт;приветствие 250 байтцеликомобманул «первыйрешает»; не обманул«данные SYN нечитает»условия бьют поединственному SYN:TTL 1 даёт «нетмаршрута», badsumдаёт таймаут rst, TTL 1сброс передприветствиемRST: номер 1,подтверждение 1 приснятом ACK, TTL 1,IP 52; следомприветствиесброс не получает;принимает 250 байтобманул «сбросстирает память»; безстратегии игрушкаблокируетTTL 2 или безусловий: серверзакрываетсоединение, уклиента Errno 104;badsum: серверигнорирует synackответ раньшевопросаSYN-ACK от клиента сподтверждением 0,затем настоящий SYNотвечает сбросом:номер 0, окно 0;клиент его неслушает; 250 байт,приветствие через0,2 собманул «роли» и«строгоерукопожатие»; безстратегии обеблокируютTTL не действует,badsum действует:сервер молчит каждый результат относится к нашим игрушкам и нашему серверу, не к настоящему оборудованию

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

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

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

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

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

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

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

Четыре урока, две проверки и карта выпуска Итог первой части выпуска: четыре урока, что результат значит только обход нашей игрушки и нашего сервера, что у SYN с данными нет запасного SYN, что сбросу нужно не дойти до сервера или быть отброшенным им и что условия срока жизни на ответе раньше вопроса не действуют, две проверки, в которых сброс с испорченной суммой обманывает игрушку, а сервер его игнорирует и принимает 250 байт, и сброс со сроком 1 умирает на роутере, тогда как с испорченной суммой доходит до сервера, два открытых вопроса про пропавшую первую копию приветствия и про правило против сообщений роутера и карта выпуска, где пройдены приёмы до приветствия, а срочный байт, окно, сторона сервера и код впереди. итог части: четыре урока и две проверки 1«сработало» значит «обошло нашу игрушку инаш сервер с Linux»; о настоящемоборудовании мы не знаем ничего 2у SYN с данными нет запасного SYN: условие,которое убило его, убило соединение 3rst: сервер не должен принять сброс; TTL 1 непускает его, badsum сервер отбрасывает 4synack: условия срока жизни не действуют, аbadsum действует проверь себя: rst:badsumкакую игрушку обманет и что с сервером? игрушка «сброс стирает память»: FORGET,суммы она не проверяетсервер получил RST и проигнорировал,соединение целое: 250 байт проверь себя: TTL 1 и badsumчем они различаются на пути пакета? клиентроутерсервер TTL 1умер на роутере badsumдошёл: сервер игнорирует результат одинаков, путь разный пока открыто куда пропала первая копия приветствияпри подложном SYN-ACKнужны счётчики ядра клиента правило против сообщений роутерав конфигурации по умолчанию не проверяли:что стало бы с клиентом карта выпуска ASYN сданными Bсброс и ответраньшевопроса Cсрочный байт Dокно Eсторонасервера Fпод капотом в следующих частях: срочный байт, окно, сторона сервера; построчный разбор кода в последних сценах

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

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

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

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

Лаборатория и рукопожатие без стратегии Лаборатория выпуска: клиент, роутер с игрушечным инспектором и сервер на Linux, пять миллисекунд на каждом выходе роутера и круг в десять, четыре точки записи, а ниже обычное рукопожатие без стратегии: SYN, SYN-ACK, ACK и приветствие в 250 байт. лаборатория выпуска: клиент, роутер с игрушкой, сервер клиенти программа nfqws2TTL пакетов 64 роутеригрушечный инспекторнаша модель, не оборудование серверLinux 6.18наш лабораторный 5 мс5 мс c0карта клиента r0вход роутера r1выход роутера s0карта сервера круг: туда пять и обратно пять миллисекунд, всего около десяти c0 стоит на сетевой карте клиента: входящий пакет она видит до работы программы «работает» здесь значит: приём обошёл нашу игрушку, а наш сервер получил настоящие данные.О настоящем оборудовании мы ничего не знаем. рукопожатие без стратегии: провод клиента, точка c0 флагиномерподтв.длинавремяклиентсервер 1S0—00 мс 2SA01010,14 мс 3A11010,20 мс 4PA1125010,29 мс в первом SYN: окно 64240, множитель окна 10; приветствие 250 байт; сервер потом подтверждает 251 и отвечает 14 байт рукопожатие: три пакета, потом приветствие

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

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

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

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

Две выдуманные игрушки про лишний байт Две выдуманные игрушки: звёздочка между «мос» и «ква» ломает слово для сортировщика, а получатель её вычёркивает, и бухгалтерские книги клиента и сервера расходятся на единицу из-за лишнего байта, пока сервер заранее не запишет, что его счёт начинается на единицу раньше. игрушки, выдуманные примеры слово «Москва» на лентесортировщик ищет слово целиком МОСКВАсортировщик: нашёл слово МОС*КВАсортировщик: слова нет, окошко гаснет правило получателя: звёздочку вычёркивать МОСКВАполучатель читает «Москва» так срочный байт задуман в руководстве:лишний байт посреди имени, которыйполучатель убирает сам. Получится ли этоу нашего получателя, проверим. две бухгалтерские книгиклиента и сервера: каждый ведёт свой счёт байтов книга клиентаотправил 250, жду подтверждение до 251 в посылку клали лишний байт: 251 книга сервера, как обычнопринял 251 байт, пишу: до 252 не сходятся на единицу записка серверу заранее:«мой счёт начинается на единицу раньше» книга сервера после запискипринял 251 байт с нуля: до 251 сходятся: лишний байт вернул единицу это игрушка, не утверждение о ядре Linux

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

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

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

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

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

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

Сдвиг номеров на единицу Пять пакетов запуска с срочным байтом в порядке отправки: номер SYN, подтверждение в SYN-ACK, ACK, приветствие и подтверждение сервера у ядра клиента и на проводе, от SYN, ушедшего в сеть, они дают 1 плюс 251 и 2 плюс 250, и оба раза получается 252. строка запуска oob с midsld: номера пяти пакетов, ядро клиента и провод у ядрана проводе от SYN, ушедшего в сетьпроводядро 1клиент: SYNseq403398165seq403398164decreasing outgoing seq : 403398165 => 403398164 01 2сервер: SYN,ACKack403398166ack403398165increasing incoming ack : 403398165 => 403398166 12 3клиент: ACKseq403398166seq403398165decreasing outgoing seq : 403398166 => 403398165 12 4клиент: приветствиеseq403398166seq403398165decreasing outgoing seq : 403398166 => 403398165 1 и 251 Б2 и 250 Б 5сервер: ACKack403398416ack403398416программа его не видит: функция уже выключилась 252252 проверка счёта на приветствии сервер: 1 + 251 = 252провод: номер 1 и 251 байт, 165 + 251 = 416 ядро клиента: 2 + 250 = 252у ядра номер 2 и 250 байт, 166 + 250 = 416 счёт сошёлся: клиент и сервер ждут один и тот же следующий номер

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

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

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

Сдвиг в коде и выключение функции В коде oob номер вычитает единицу, пока позиция от начала соединения не больше единицы, к подтверждению во входящем SYN-ACK прибавляет единицу, а после отправки срочного пакета функция выключает себя, поэтому следующий чистый ACK с номером 252 проходит без правки. функция oob в файле zapret-antidpi.lua, номера строк настоящие 1115local pos = pos_get(desync, 's', false)1116if pos<=1 then1117 local dseq = u32add(desync.dis.tcp.th_seq, -1)1118 DLOG("oob: decreasing outgoing seq : "…)1119 desync.dis.tcp.th_seq = dseq1120end исходящие: номер минус одинпозиция считается от начала соединения:SYN 0, ACK 1, первые данные 1 (rseq)позиция не больше 1: номер минус 1 1184local dack = u32add(desync.dis.tcp.th_ack, 1)1185DLOG("oob: increasing incoming ack : "…)1186desync.dis.tcp.th_ack = dack входящие: подтверждение плюс одинтолько входящий SYN-ACK: позицияне больше 1, иначе функция отключаетсяядро получает то, что ждёт 1160if not desync.replay then1161 instance_cutoff_shim(ctx, desync)1162end после приветствия: выключитьсясразу после отправки срочного пакетафункция выключает себя для соединения проверь себя: следующий пакет клиента, чистый ACK, номер у ядра 252 instance cutoff for 'oob_1_2' in=1 out=1packet: id=7 flags=A* lua 'pcap_1_1' : desync (строки oob_1_2 нет) 252 = 252на проводе тот же номерответ: нет, и причина не в позиции функция выключила себя на приветствии, поэтому этого пакета она уже не видит

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

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

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

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

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

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

Заголовок срочного пакета Обычное приветствие без стратегии и срочный пакет с лишним байтом различаются четырьмя числами: флаги PA и PAU, 250 и 251 байт данных, указатель срочности 0 и 105, длина IP 302 и 303. заголовок TCP: два байта указателя срочности и флаг URG, вы их уже видели порты номер подтверждение смещениеи флаги окно сумма указательсрочности флаг URG: в обычном пакете выключенв обычном пакете два нуля обычное приветствиезапуск без стратегии флагипередать данные и подтверждениеPA данных в пакетеприветствие целиком250 байт указатель срочностифлаг URG выключен0 длина IP20 + 32 + 250302 срочный пакетoob с меткой midsld флагидобавлена срочность URGPAU данных в пакетеприветствие плюс один байт251 байт указатель срочностипоказывает на лишний байт105 длина IP20 + 32 + 251303 флаги в числе: 0x18 и 0x38, добавился один бит 0x20, это и есть URG четыре числа различаются: на байт длиннее, флаг URG, указатель 105, длина IP

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

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

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

Где стоит лишний байт и как собран пакет В срочном пакете на 251 байт слева лежат 104 байта приветствия, затем лишний нулевой байт под номером 105, на который показывает указатель срочности, и справа 146 байт, а две строки кода собирают такое тело и включают флаг URG. тело срочного пакета: 251 байт, номера с единицы 104 байта приветствия 146 байт приветствия th_urp = 105 1104106251лишний байт 105, не в масштабе 65e102 78x103 61a104 00\0105 6dm106 70p107 6cl108 ← слева «exa» из имени, справа «mpl»; нулевой байт поставили мы 104 + 1 + 146 = 251 в коде: две строки 1153dis_oob.payload = string.sub(data, 1, urp-1) .. oob .. string.sub(data, urp) первые urp-1 = 104 байталишний байтостальные, с байта urp: 146 dis_oob.tcp.th_flags = bitor(dis_oob.tcp.th_flags, TH_URG)1154 флаги PA становятся PAU; указатель 105 уже записан раньше, строкой 1149

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

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

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

Что читает сервер при разных местах байта Наш Linux вынимает срочный байт из потока и читает оригинальные 250 байт: при указателе 105 двумя чтениями по 104 и 146 байт, при начале и конце одним чтением на 250, при позиции два чтениями по 2 и 248, и значение байта 00, X или 65 на это не влияет. ответ нашего сервера на Linux: подтверждение и чтения приложения запуск с midsld подтверждение сервера: 252принял 251 байт, как мы отправили приложение сервера прочитало 104146 два чтения, всего 250 байт хэш равен хэшу обычного запускалишний байт вынут из потока почему чтение режется так, не выясняли запускуказательбайтчтения urp=b000 в начале[250] urp=e25100 в конце[250] urp=2300[2, 248] midsld10500[104, 146] midsld, char=X10558 = X[104, 146] midsld, byte=6510541 = 65[104, 146] во всех шести запусках хэш равен оригиналу: сервер получил те же 250 байт флаги в пакете везде PAU; байт в начале и в конце читается одним чтением три проверенных значения байта, 00, X и 41, наш сервер вынул одинаково.Как поведут себя другие системы, мы не проверяли.

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

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

Срочный байт в запросе HTTP В запросе HTTP из 81 байта метка midsld даёт позицию 39, указатель срочности равен 40, пакет получается из 82 байт, а сервер читает 39 и 42 байта. запрос HTTP, 81 байт, имя узла с 33-го байта, смещение 32 GET /index.html HTTP/1.1Host: www.exa mple.comи ещё две строки заголовков шаги метка midsldпозиция 39 с нуля указатель срочности39 + 1 = 40 пакет81 + 1 = 82 байта сервер читает39 и 42 байта 3942 сервер: подтверждение 83, чтения по 39 и 42 байта, всего 81 то же устройство, что с приветствием: указатель на единицу больше позиции метки

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

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

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

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

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

Две игрушки и звёздочка в слове Две наши игрушки-инспектора, подстрока и разборщик, и выдуманный пример со словом Москва: звёздочка в начале оставляет слово целым внутри строки, а звёздочка посередине его ломает, и для нас «слеп» значит, что имя не нашлось. две игрушки: наши модели, не настоящее оборудование игрушка-подстрокаищет имя www.example.com как обычную строку внутри одного пакетаab игрушка-разборщикидёт по длинам полей приветствия, как по ступенькам обе придуманы нами; стоят на входе в роутер, до проверки срока жизни, и смотрят живые пакеты игрушка, выдуманный пример: слово на ленте и сортировщик *МОСКВАзвёздочка в начале: слово внутри ленты целосортировщик его найдёт МОС*КВАзвёздочка посередине: слова больше нетсортировщик не найдёт, а получатель вычеркнет звёздочку «слеп» значит: имя не нашлось. Для нас это хороший исход.Место лишнего символа решает всё.

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

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

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

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

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

Четыре запуска: две игрушки и два места байта Без стратегии обе игрушки роняют приветствие и сервер получает 0 байт, а с лишним байтом подстрока находит имя только при байте в начале, разборщик слеп в обоих местах, и в трёх запусках из четырёх сервер получает все 250 байт. точка отсчёта и четыре запуска, каждый с настоящим пакетом и журналом игрушки без стратегии: обе игрушки роняют обычное приветствие, сервер получил 0 байт 1подстрока, байт в началеurp по умолчанию: указатель 0 TOY DECISION BLOCK: host name foundby model substr in a packet of 251 bytes повторы, мс от первого SYN: 224, 444, 900, 1768, 3496каждый раз его роняют блоксервер: 0 байт 2разборщик, байт в началепервый байт данных 00 TOY tlsparse: first byte 00 walk result nilTOY DECISION BLIND: host name not found … ждёт тип записи 0x16, а там 00: разбор не начался слепасервер: 250 байт, [250] 3подстрока, байт посерединеуказатель 105, метка midsld TOY DECISION BLIND: host name not foundby model substr in this packet (len 251) подряд «www.example.com» в пакете нет слепасервер: 250 байт, [104, 146] 4разборщик, байт посерединеуказатель 105, метка midsld TOY tlsparse: first byte 16 walk result'www.exa\x00mple.co'TOY DECISION BLIND … www.exa00mple.com15 байт имени отсчитано, один лишний слепасервер: 250 байт, [104, 146] слеп: имя не нашлось. Всё это про наши игрушки и наш Linux.

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

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

Игрушка строгое рукопожатие Игрушка строгое рукопожатие в роутере видит SYN, SYN-ACK с подтверждением ровно на единицу больше и ACK, признаёт рукопожатие настоящим, а потом ищет имя подстрокой и в пакете с лишним байтом не находит, поэтому приветствие проходит. игрушка стоит в роутере и видит провод, а не клиента клиентроутерсервер 1SYNномер 0состояние 0 → 1: SYN клиента замечен 0→1 2SYN,ACKподтверждение 1состояние 1 → 2: подтверждение ровно на единицу больше 1→2 3ACKномер 1состояние 2 → 3: рукопожатие завершено, проверка включена 2→3 4PAU251 байт, указатель 105ищет имя подстрокой: не нашла: решения в журнале нет поиск игрушка видит рукопожатие таким, каким оно идёт по проводу:SYN-ACK подтверждает ровно тот номер, что ушёл в сеть.Правку входящего SYN-ACK клиент делает у себя, уже после роутера. сервер получил 250 байтчтениями по 104 и 146 байт дальше обычная подстрока: имя не найдено,решения в журнале нет, приветствие проходит

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

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

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

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

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

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

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

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

Без входящего диапазона соединение не открывается Если убрать диапазон входящих, SYN-ACK не доходит до функции и подтверждение остаётся на единицу меньше, чем ждёт ядро клиента, поэтому клиент пять раз за три секунды отправляет SYN, каждый раз отвечает сбросом на ответ сервера, и сервер не получает ни байта данных. запуск oob без диапазона входящих: провод клиента --lua-desync=oobи никакого --in-range=-s1диапазон входящих по умолчанию «x» значит «никогда»: SYN-ACK не доходит до функции клиентсервер попытка 1S0 мсномер 0SA10,1 мсподтв. 1R10,5 мссброс попытка 2S24,1 мсномер 0SA34,3 мсподтв. 1R34,7 мссброс попытка 3S1052,1 мсномер 0SA1056,9 мсподтв. 1R1057,3 мссбросSA1062,2 мсподтв. 1R1062,6 мссброс попытка 4S2072,2 мсномер 0SA2082,4 мсподтв. 1R2082,7 мссброс попытка 5S3096,2 мсномер 0SA3100,9 мсподтв. 1R3101,3 мссбросSA3106,3 мсподтв. 1R3106,7 мссброс почему такподтверждение в SYN-ACKостаётся на единицу меньше,чем ждёт ядро клиента:ядро отвечает сбросом но попытку не бросаетпо таймеру шлёт SYN заново:пять раз за три секунды на каждом SYN в журналеdecreasing outgoing seq итог: клиентerror: timed outсервер: нет пакетов данных

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

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

Ошибка после сдвига: дырка в один байт Если дать два символа вместо одного, функция останавливается с ошибкой, но SYN и ACK уже сдвинуты, поэтому приветствие уходит с номером 2, сервер ждёт номер 1 и шесть раз подтверждает одно и то же, а приложение сервера получает 0 байт. запуск oob:char=XY: провод клиента клиентсервер S0 мсномер 0 SA10,2 мсподтв. 1 A10,6 мсномер 1 PA 25011,0 мсномер 2 A21,1 мсподтв. 1 PA 250225,0 мсномер 2 A235,2 мсподтв. 1 PA 250441,0 мсномер 2 A451,1 мсподтв. 1 PA 250889,1 мсномер 2 A899,3 мсподтв. 1 PA 2501753,0 мсномер 2 A1763,2 мсподтв. 1 FA2021,8 мсзакрытие сервера A2028,8 мсномер 252 PA 2503481,0 мсномер 2 R3491,1 мссброс журналdesync ERROR.passing packet unmodified.один символ нужен, дали два ленточка номеров 12 … 251 сервер ждёт номер 1,клиент начал с номера 2между ними дырка в один байт,заполнить её некому шесть копий за 3,5 ссервер раз за разом подтверждаетодно и то же: «жду номер один»потом закрытие со стороны сервераи последний сброс приложение сервера: 0 байтклиент: reply b''

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

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

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

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

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

Срочный байт рядом с multisplit Если поставить срочный байт перед multisplit или после него, на проводе идут срочный пакет на 251 байт и две части по 104 и 146 байт со сдвигом на байт, сервер подтверждает 252 и дополнительно шлёт повторы, а приложение читает те же 104 и 146 байт. две строки запуска с соседями: порядок пакетов на проводе клиента срочный байт, потом multisplitoob:urp=midsld, затем multisplit:pos=midsld клиентсервер PAU 251номер 111,6 мс PA 104номер 112,0 мс PA 146номер 10512,2 мс Aподтв. 25222,1 мс PA 14ответ22,2 мс Aповтор 25222,3 мс Aповтор 25222,5 мс multisplit, потом срочный байтmultisplit:pos=midsld, затем oob:urp=midsld клиентсервер PA 104номер 212,3 мс PA 146номер 10612,8 мс PAU 251номер 113,0 мс Aподтв. 122,9 мс Aподтв. 123,1 мс Aподтв. 25223,4 мс PA 14ответ23,5 мс почему части лишние: те же байты, но номера сдвинуты на единицу срочный пакет: 1…104срочный пакет: «m» стоит на 106 часть 1: номер 1, 104 байтачасть 2: номер 105, 146 байт, «m» на 105 части накрывают те же номера со сдвигом; по счёту это повторы, что именно делает с ними ядро, не проверяли руководство: срочный байт не дружит с функциями, пересылающими изменённые данные:«will send duplicates without OOB»в обоих порядках приложение прочитало те же две части, 104 и 146 байт, хэш равен

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

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

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

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

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

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

Длинное приветствие: собрать, потом проиграть Длинное приветствие в 1484 байта ядро режет на 1448 и 36 байт, программа придерживает первый кусок, собирает его целиком и проигрывает как срочный пакет на 1485 байт, нарезанный на 1448 и 37, а второй кусок ядра в сеть не уходит. длинное приветствие, 1484 байта: что делает ядро и что делает программа 1 ядро режет приветствие1448 и 36 байт (не zapret2) 2 программа придерживаетпервый кусок, ждёт остальное 3 собрано целиком: 1484и проигрывается в функцию кусок ядра 1: 1448 байт 0граница на 1448справа кусок ядра 2: 36 байт, всего 1484 провод клиента: что ушло в сеть клиентсервер Sномер 00 мс SAподтв. 110,2 мс Aномер 110,5 мс AU 1448номер 1, указатель 105, без PSH11,2 мс A 37номер 1449, указатель 011,3 мс Aподтв. 144921,3 мс Aподтв. 148621,4 мс PA 14ответ сервера21,5 мс 1448 + 37 = 1485 = 1484 + 1: лишний байт добавил один1 + 1485 = 1486: это и подтвердил сервер оригинальный второй кусок ядра, 36 байт, в сеть не уходит:его байты уже лежат внутри срочного пакета, на проводе остаются два кускав журнале: «oob: dropping replay piece», срочный пакет режется по привычному лимиту порции

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

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

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

Флаг срочности только на куске с байтом Правило в коде оставляет флаг срочности и пересчитывает указатель только в куске, куда попал срочный байт: первый кусок на 1448 байт сохраняет указатель 105, второй с байта 1449 флага не получает, а сервер читает 104 и 1380 байт, всего 1484. срочный пакет в 1485 байт нарезан по лимиту порции кусок 1: байты 1…1448 лишний байт 105кусок 2: с байта 1449, 37 байт правило в коде, строки сверху вниз 1253if oob then 1254 if urp==0 and pos==1 then 1255 …th_flags = bitor(…, TH_URG) 1256 elseif urp>=pos and urp<(pos+len) then 1257 …th_flags = bitor(…, TH_URG) 1258 discopy.tcp.th_urp = urp-pos+1 1259 else 1260 …th_flags = bitand(…, bitnot(TH_URG)) 1261 discopy.tcp.th_urp = 0 1262 end пересчёт для двух кусков кусок 1: pos 1, len 1448105 >= 1 и 105 < 1449флаг остаётся, указатель105 - 1 + 1 = 105 кусок 2: pos 1449, len 37105 < 1449: байт не попалфлаг снят, указатель 0 на проводе AU 1448: флаг, указатель 105, без PSH A 37: без флага, указатель 0 приложение сервера читает 104чтение 2: 1380 байт два чтения, 104 и 1380 байт: 104 + 1380 = 1484 как в оригинале, хэш совпал

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

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

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

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

Что мы знаем про срочный байт Шесть вещей про срочный байт мы видели на нашем Linux и в нашей модели, а как себя поведут другие получатели, приложение, читающее срочные данные отдельно, настоящий инспектор, IPv6 и нарезка на уровне IP, мы не знаем. итог про oob: наш Linux и наша игрушечная модель 1начинается с SYN и требует диапазон входящихбез диапазона соединение не открывается код и наш тест 2сдвиг на единицу: три пакета клиента и подтверждение в SYN-ACKпосле приветствия функция выключается код и наш тест 3приветствие заменяется срочным пакетоморигинальный пакет выбрасывается код и наш тест 4на длинном приветствии флаг только на куске с байтомпроверен один случай: байт в первом куске код и наш тест 5после первого сдвига пути назад нетошибка оставляет соединение мёртвым наш тест 6наш Linux вынимает байт и читает оригиналчтения режутся по месту байта наш тест чего мы не знаем другие получателиWindows, BSD,телефоны срочные данныенаш сервер их нечитает отдельно настоящий инспекторкак он устроен,мы не знаем IPv6 и нарезка IPне пробовали; темавыпуска «Другие слои» мы знаем всё, что видели на нашем Linux и в нашей модели,и ничего сверх этого

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

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

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

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

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

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

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

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

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

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

Зажим окна у клиента в трёх пакетах Зажим окна у клиента в нашем тесте правит три пакета: в SYN окно 64240 становится 1 и множитель 10 становится 6, в подтверждении рукопожатия и в приветствии на 250 байт окно 63 становится 1, на приветствии функция отключается как на первом пакете с данными, а следующий пакет клиента уже несёт обычное окно 63, причём строка запуска разобрана на куски wssize, wsize=1 и scale=6, а диапазон входящих для неё не обязателен. зажим окна у клиента: строка запуска и три пакета на проводе клиента игрушка: почтальон и ящиквыдуманный примерклиент говорит: «в ящике место на одну штуку»; почтальон мог бы привезти всё сразу,но носит по одной руководство, замысел, не измерение«force the server to fragment its responses while the DPI is inspecting them» вы это видели: окно × множитель из SYN = лимит без подтверждения63 × 2^10 = 64512 байт точка отсчёта: без стратегии SYN клиентаокно 64240 · множитель 10приветствие, 250 байтокно 63ответ сервера, 6000 байт1448 · 1448 · 1448 · 1448 · 208 наш тестовый сервер отвечает на приветствие шестью тысячами байт: пять сегментов, их разрезание дальше строка запуска по кускам --in-range=a --lua-desync=wssize:wsize=1:scale=6 --in-range=aвходящие «всегда». Необязателен: wssize самавыключает входящуюсторону, строка 344.Без него окно тоже 1,множитель 6 wssizeзажим окна у клиента:правит исходящиепакеты, пока не дойдётдо первых данных wsize=1окно: число, котороезаписывается в полепакета scale=6множитель: число изSYN, программа умееттолько уменьшать его(10 → 6) пакет клиентаокно: ядро → проводмножительжурнал программы 1SYN0,00 мс · флаг S64240 → 110 → 6window size 64240 => 1scale factor 10 => 6 2ACK рукопожатия10,63 мс · флаг A63 → 1опции нетwindow size 63 => 1 3приветствие, 250 байт10,80 мс · флаги PA63 → 1опции нетwindow size 63 => 1wssize: forced cutoff 4следующий ACK клиента21,63 мс · флаг A63, не правится—функция уже отключена третий пакет первый с данными: функция отключается, четвёртый несёт окно ядра числа из нашего теста: провод клиента в лаборатории, круг 10 мс

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

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

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

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

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

Первый кусок ответа и код зажима окна Без стратегии наш сервер отвечает на 6000 байт пятью сегментами, а при зажиме окна у клиента с окном 1 и множителем 6 первый кусок ровно 64 байта, это 1 × 2^6, и всего получается 40 сегментов, в коде окно записывается в поле пакета, множитель в SYN только уменьшается, а на первом пакете с данными функция отключается, окно 1 без множителя оставляет множитель 10 и даёт первые два куска по 512 байт, а строка с окном 2 и множителем 6 по счёту даёт первый кусок 128 байт, как и показал запуск. что делает сервер: ответ в 6000 байт режется по окну клиента без стратегии: 5 сегментов1448144814481448208 с зажимом wssize:wsize=1:scale=6: 40 сегментов624 первый кусок: 64 байта = 1 × 2^6, на 20,97 мс 5 → 40 сегментов 64 ×1128 ×31192 ×7624 ×1, остаток код: что делает wssize на пакете клиентаlib 1451-1467 · antidpi 349-352 1451if arg.wsize then1453DLOG("wsize_rewrite: window size "..dis.tcp.th_win.." => "..wsize)1454dis.tcp.th_win = tonumber(arg.wsize)1462if scale>oldscale then1464elseif scale<oldscale then1466dis.tcp.options[i].data = bu8(scale)349if #desync.dis.payload>0 and (not desync.arg.forced_cutoff or …) then350DLOG("wssize: forced cutoff")351instance_cutoff_shim(ctx, desync) lib 1451-1456окно записывается вполе lib 1462-1467множитель только вниз antidpi 349-352первый пакет сданными: отключение другое значение wssize:wsize=1без scale: множитель остался 10, окно 1 × 1024 = 1024первые два сегмента по 512, вдвое меньше окна; правило, почему вдвое, не проверяли проверь себястрока wssize:wsize=2:scale=6, какой будет первый кусок ответа? ? 1282 × 2^6 = 128; в нашем запуске: 128, 384, 256, 256, … всего 22 сегмента

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

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

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

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

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

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

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

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

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

Множитель остаётся и ответ идёт кругами Множитель 6 из SYN остаётся навсегда, потому что в следующих пакетах опции множителя нет, поэтому окно 63, которое ядро клиента честно пишет по своему множителю 10, сервер читает с множителем 6 и получает 4032 байта вместо 64512, то есть в 16 раз меньше, первый кусок в 64 байта приходит на 20,97 мс, ACK клиента на 21,63 мс, следующая пачка на 31,77 мс, круг стоит около 10,8 мс, а все шесть тысяч байт приходят пятью пачками по 1, 10, 20, 8 и 1 куску, при том что окно 4032 само по себе дало бы по счёту около двух кругов. цена ложного окна: множитель из SYN остаётся навсегда игрушка: кафе и тарелкавыдуманный примертарелка весила 1024 грамма, потом договорились, что она весит 64; договорённость отменить нельзя.посетитель просит «63 тарелки»: 4032 грамма вместо 64512, в 16 раз меньше так и с окном: кто что считает ядро клиентао подмене не знает: множитель 10,пишет в пакет окно 63окно 63серверв SYN прочёл множитель 6,читает 63 × 2^6 = 4032 байта в следующих пакетах опции множителя нет, поэтому вернуть его нельзя без стратегии63 × 1024 = 64512 байт после отключения63 × 64 = 4032 байта × 161024 / 64 = 16 как это выглядит во времени: провод клиента клиентсервермс 1сервер: PA 64, первый кусок ответа20,97 2клиент: ACK, окно 6321,63 3сервер: пачка PA 192, 128, 128, 192, …31,77 круг между пачками: 31,77 − 20,97 = 10,8 мс; в лаборатории он около 10 мс весь ответ: пять пачек сервера, время от первого куска 0 мс1 кусок · 64 Б +10,8 мс10 кусков · 1536 Б +22,0 мс20 кусков · 2624 Б +32,3 мс8 кусков · 1152 Б +42,6 мс1 кусок · 624 Б пять кругов; окно 4032 само по себе дало бы по счёту около двух (6000 / 4032 ≈ 1,5) почему пять, а не два: не проверяли; по общим сведениям, важно число пакетов в полёте

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

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

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

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

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

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

Время ответа и отключение no Ответ сервера в нашем тесте занимает 10,5 мс без стратегии, 53,2 мс при зажиме окна у клиента, примерно в пять раз дольше, и 150,2 мс при принудительном отключении со значением no, примерно в четырнадцать раз дольше, потому что правило очереди в нашей лаборатории пропускает к программе только первые двенадцать пакетов клиента, они несут окно 1, а первые десять кусков ответа по 64 байта идут по одному за круг, а при окне 1 без множителя множитель остаётся 10 и весь ответ приходит за 32,6 мс кусками 512, 512, 1448, 88, 1024, 1448, 88 и 880 байт. сколько мы платим во времени: время получения всех 6000 байт, круг 10 мс без стратегии5 сегментов10,5 мс зажим окна, wssize:wsize=1:scale=640 сегментов53,2 мс× 5 то же, forced_cutoff=no43 сегмента150,2 мс× 14 53,2 / 10,5 ≈ 5,07 · 150,2 / 10,5 ≈ 14,3 принудительное отключение no: окно правится у каждого пакета, который отдали программе пакеты клиента по порядку SAPAAAAAAAAAAAAA… окно 1: ровно 12 пакетов клиентадальше окно 63 правило очереди в нашей лаборатории: к программе идут только первые 12 пакетов клиентатак было в выпуске про командную строку куски ответа сервера ещё 33 сегмента: 192, 128, … 560 первые десять кусков по 64 байта, каждый своим кругом; всего 43 сегмента руководство, не измерение«It reduces speed in any case» · «catastrophic drop in speed»мы мерили только на круге 10 мс; что это значит для живого канала, не знаем проверь себяwssize:wsize=1 без scale: множитель остался 10. Окно после приветствия такое же узкое? ? нетобычное окно 63 снова значит 63 × 1024 = 64512 байт; весь ответ за 32,6 мс вместо 53,2 512512144810241448880 сегменты: 512, 512, 1448, 88, 1024, 1448, 88, 880

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

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

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

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

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

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

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

Стенд, две игрушки и разрезанное имя Руководство задумало приём против TLS 1.2, но мы этого не проверяли: в нашем стенде ответ сервера это 6000 байт с текстом имени на выбранном смещении, игрушка по пакетам ищет имя в каждом пакете сервера отдельно, а сборщик склеивает ответ по номерам, без стратегии имя лежит внутри сегмента в 1448 байт, игрушка роняет ответ и клиент получает 0 из 6000, а при зажиме окна у клиента и имени на смещении 60 первый кусок в 64 байта кончается на «www.», остальные 11 байт лежат во втором, игрушка по каждому пакету говорит что не видит имя, и клиент получает все 6000 байт за 55,6 мс. может ли мелкая нарезка ответа спрятать в нём имя? руководство, замысел, не измерениеприём задуман против TLS 1.2: сервер присылает сертификат открытым текстом; мы этого не проверяли наш стендответ тестового сервера: 6000 байт «ABC…XYZ» с текстом www.example.com на выбранном смещенииусловная замена имени, лежащего открыто; о настоящих ответах и сертификатах ничего не знаем игрушка: строка букв режется на куски по 64выдуманный пример ЖЗИЙКЛМНОПРСТУМОСКВАЦЧШЩЭЮЯАБВГДконец кусканачало следующего читатель по кускам: «МОС» и «КВА» по отдельности, слова не видитчитатель, который склеивает по порядку: МОСКВА найдено игрушка по пакетамищет имя в каждом пакете сервераотдельно; ближе к «пакетному»в журнале: srv_pkигрушка-сборщиксклеивает ответ по номерам, занятоеместо не меняется; ближе к «первый записал»в журнале: srv_rf обе читают пакеты сервера, а не клиента запуск 1: без стратегии, игрушка по пакетам сегмент сервера 1448 байт, имя внутри (смещение 60)TOY DECISION BLOCK: host name inside one server packet (len 1448)клиент0 из 6000 запуск 2: wssize:wsize=1:scale=6, имя на смещении 60, игрушка по пакетам граница первого куска на 64 байтах режет имя45E46F47G48H77w77w77w2e.65e78x61a6dm70p6cl65e2e.63c6fo6dmконец куска 1, байты 56..63начало куска 2, байты 64 и дальшеимя 15 байт: 4 в куске 1 (www.) и 11 в куске 2 (example.com); игрушка про каждый пакет: BLINDTOY DECISION BLIND: no host name inside this server packet (len 64) клиент получил все 6000 байт за 55,6 мсбез стратегии: 10,5 мс

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

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

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

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

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

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

Граница куска и сетка из четырёх запусков Смещения имени выбрали мы, и результат зависит от того, где легла граница куска: игрушка по пакетам пропустила ответ при имени на 60 и роняет его при имени на 70, где имя целиком лежит во втором куске в 192 байта, игрушка-сборщик после второго куска имеет 256 байт подряд, находит имя на 60 и роняет ответ, клиент получает 64 из 6000, а для сборщика с именем на 70 запуска нет и по счёту ответ блокируется, как при 60. то же имя на смещениях 60 и 70: где легла граница куска байты ответа вокруг границы 63 | 64; в каждой клетке по одному байту, имя 15 байт имя на смещении 60: 60..7456w60ww.e64xamp68le.c72om76808488 имя на смещении 70: 70..8456606468www72.exa76mple80.com8488 на 60: 4 байта «www.» в куске 1 и 11 байт «example.com» в куске 2 (4 + 11 = 15)на 70: имя целиком после границы, в куске 2 на 192 байта запуск 3: игрушка по пакетам, имя на 70 кусок 1 (64) прошёл: BLIND, имени в нём неткусок 2 (192): TOY DECISION BLOCK: host name inside one server packet (len 192)дальше игрушка роняет весь ответклиент64 из 6000 запуск 4: игрушка-сборщик, имя на 60 после куска 2 у сборщика 64 + 192 = 256 байт подряд, имя в них целикомTOY DECISION BLOCK: host name found in the rebuilt server stream (256 contiguous bytes)ответ роняетсяклиент64 из 6000 итог: что получил клиент имя на 60имя на 70 игрушка по пакетам6000 из 6000, прошло 64 из 6000, блок игрушка-сборщик64 из 6000, блок ? блок, по счётутакого запуска у нас нет проверь себя: сборщик и имя на 70, блокирует ли он ответ? по счёту да: после куска 2 имя целиком в склеенном потоке, как при 60; это расчёт, не запуск 55,6 мсзапуск 2: все 6000 байт10,5 мсбез стратегии: те же 6000 байт смещения выбрали мы: это не измерение настоящего сертификата и не слова об инспекторе

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

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

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

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

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

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

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

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

Окно в ответе сервера и лесенка клиента Зажим окна в ответе сервера в нашем тесте правит один пакет, SYN-ACK, на проводе он несёт окно 1 и множитель 6 вместо 65160 и 10, после подтверждения рукопожатия клиент молчит 217 мс и посылает один байт, сервер подтверждает его и клиент сразу отправляет 107 и 72 байта, сервер отвечает 14 байтами и клиент отправляет последние 70 байт с флагом закрытия, то есть 1, 107, 72 и 70 в сумме дают приветствие в 250 байт, нарезанное самим клиентом. ложное окно в ответе сервера: кто режет приветствие игрушка: вход в залвыдуманный примерчеловек у входа: «сейчас пускаем одного»; один проходит сразу, остальные ждут, пока он вернётся.табличку повесили не на ворота, а в объявление сервера: очередь клиента выстраивается сама режим сервера: nfqws2 запускают на самом сервере, флаг server; подробности дальше в выпуске строка запускажурнал программы на сервере --lua-desync=wsize:wsize=1:scale=6wsizeзажим окна в ответе сервераwsize=1окно в пакетеscale=6множитель, только вниз LUA: wsize_rewrite: window size 65160 => 1LUA: wsize_rewrite: scale factor 10 => 6packet: id=2 pass modified. len 60 => 60instance cutoff for 'wsize_1_2' in=1 out=1правит один пакет, SYN-ACK; на следующемпакете сервера функция отключается провод клиента по порядку, время в мс клиентсервермс 1SYN клиента0,00 2SYN-ACK сервера: окно 1, множитель 6настоящие были 65160 и 1011,04 3ACK клиента, рукопожатие закончено11,10 пауза: клиент молчит 217 мс+217,4 4PA 1: первый байт приветствия228,51 5ACK сервера: подтверждено до 2239,01 6PA 107239,04 7PA 72239,05 8PA 14: ответ сервера, 14 байт239,07 9FPA 70: последние 70 с флагом закрытия239,17 1 + 107 + 72 + 70 = 250: то же приветствие, нарезанное самим клиентом

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

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

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

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

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

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

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

Другие значения окна и чего мы не знаем По счёту, а не по опыту, имя с байта 97 по 111 разрезано границей между вторым и третьим куском, 107 и 108, «www.example» остаётся в куске в 107 байт, а «.com» уходит в кусок в 72 байта, при окне 2 куски 2, 106, 72 и 70, при множителе 3 и без множителя те же 1, 107, 72 и 70, только множитель 0 даёт 1, 64, 64, 64 и 57, без сетевой задержки куски 1, 215 и 34, а в шести запусках на сервере первый байт ждал от 213,2 до 219,2 мс, причину этих размеров и паузы мы не нашли. куски приветствия при других значениях и повторы расчёт, не опыт: где в этих кусках лежит имя (байты 97..111, считая с нуля) 10772701 «www.example» в куске на 107«.com» в куске на 72 читатель, который смотрит на пакеты по одному, целиком имя не увидит; запуска с игрушкой на этом у нас нет строка запускаSYN-ACK на проводекуски клиентапервый байт wsize:wsize=1:scale=6окно 1, множитель 61 / 107 / 72 / 70213,2-219,2 мс wsize:wsize=2:scale=6окно 2, множитель 62 / 106 / 72 / 70215,2 мс wsize:wsize=1:scale=3окно 1, множитель 31 / 107 / 72 / 70212,4 мс wsize:wsize=1, без scaleокно 1, множитель 101 / 107 / 72 / 70212,3 мс wsize:wsize=1:scale=0окно 1, множитель 01 / 64 / 64 / 64 / 57214,1 мс без сетевой задержкиокно 1, множитель 61 / 215 / 34206,1 и 208,2 мс множитель 0: по счёту окно сервера 64 байта, умноженное на один; без сетевой задержки круг 0, а не 10 мс первый байт: время от ACK рукопожатия до первого байта приветствия повтор: шесть запусков на сервере при круге 10 мс, пауза до первого байта 210212214216218220222213,2214,5215,8217,4219,2 ×2 куски во всех шести запусках: 1, 107, 72, 70; без сетевой задержки они другие чего не знаемпочему именно 107, 72, 70 и почему первый байт ждёт почти 0,2 с: не нашлипо общим сведениям: пробный пакет по таймеру не короче 200 мс; это догадка, не измерение проверь себяокно в SYN-ACK стало 1, клиент хочет отправить 250 байт: сколько байт уйдёт в первом пакете? ? 1как в нашем запуске: остальное уйдёт только после подтверждения этого байта

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

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

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

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

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

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

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

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

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

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

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

Зажим окна на клиенте мёртв пока не спрятать исходящие Зажим окна wsize на клиенте в нашем тесте сначала не делает ничего, потому что первый вызов пришёл на SYN самого клиента без флага ACK и функция сразу отключилась, не дождавшись SYN-ACK сервера, а ответ пришёл за 11,1 мс, чтобы она дожила до SYN-ACK, нужны диапазон входящих «всегда» и диапазон исходящих «никогда», тогда окно 65160 заменяется на 1 и множитель 10 на 6 внутри клиента уже после точки захвата, ядро клиента режет своё приветствие на 1, 107, 72 и 70 байт после паузы около 212 мс и получает ответ за 223,4 мс вместо 11,1 мс, а на сервере и на клиенте куски приветствия одинаковые. ложное окно на клиенте: функция wsize и два диапазона серверSYN-ACKклиентна сервере: собственный пакетна клиенте: входящий пакетwsize правит именно SYN-ACK запуск на клиенте --in-range=a --lua-desync=wsize:wsize=1:scale=6 SYNSYN-ACK: окно 65160, множитель 10ACKPA 250: приветствие целикомответ 1448 × 4 + 208 ничего не происходит: ответ за 11,1 мсв журнале на первом пакете, SYN клиента: instance cutoff for 'wsize_1_2' in=1 out=1 код функции wsizeantidpi 317-325 319    if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then320        if wsize_rewrite(desync.dis, desync.arg) then321            return VERDICT_MODIFY323    else324        instance_cutoff_shim(ctx, desync) -- mission complete оба флага: правим окно иначе: отключение, миссия выполнена первый вызов пришёл на SYN клиента, без ACK: отключение; SYN-ACK функция так и не увидела чтобы функция дожила до SYN-ACK: два диапазона --out-range=x --in-range=a --lua-desync=wsize:wsize=1:scale=6 --in-range=a: входящие «всегда»иначе SYN-ACK до функциине дойдёт--out-range=x: исходящие «никогда»иначе функция выключитсяна собственном SYN * lua 'wsize_1_2' : out pos x0 x0 out of range x0-x0SYNLUA: wsize_rewrite: window size 65160 => 1LUA: wsize_rewrite: scale factor 10 => 6SYN-ACK проверь себя: захват на сетевой карте клиента покажет SYN-ACK с окном 1? сетевая карта и захватSYN-ACK: окно 65160,множитель 10nfqws2 внутри клиентаправит: окно 1,множитель 6ядро клиентавидит окно 1 и множитель 6,режет своё приветствие нет: захват стоит до правки, он покажет окно 65160 и множитель 10 последствия на проводе клиента, время в мс S 0,00SA 10,22A 11,77пауза 212,3PA 1 · 224,07ответ 1448 × 4 + 208 · 234,29PA 107 · 235,14PA 72 · 235,22FPA 70 · 235,61 11,1 мс → 223,4 мсклиент сам себя душит; это про наш Linux и нашу лабораторию зеркало: куски приветствия на проводе клиента круг 10 мсбез задержки wsize на сервере (правка при выходе)1 / 107 / 72 / 701 / 215 / 34 wsize на клиенте (правка при входе)1 / 107 / 72 / 701 / 215 / 34 на клиенте четыре запуска: те же куски; и там и там SYN-ACK доезжает до ядра клиента с окном 1 руководство, не измерение«From the client side, TCP segmentation techniques are preferredas they avoid speed penalties and offer more flexibility»

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

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

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

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

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

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

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

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

Итог части про срочный байт и ложное окно Итог части в четырёх строках: срочный байт даёт сдвиг на единицу, пакет с флагом и указателем, наш Linux вынимает байт и подстрока слепа посередине имени, зажим окна у клиента правит три пакета, первый кусок ответа 64 байта, ответ идёт в 5 раз дольше, а без отключения в 14, зажим в ответе сервера заставляет клиента резать приветствие на 1, 107, 72 и 70 после паузы около 0,2 с, а на клиенте функция мертва, пока не спрятать исходящие, и три вещи мы не знаем: других получателей, настоящий инспектор и причину размеров 107, 72, 70 и паузы, а на проверку ответы третий исходящий пакет с окном 63 в следующем и диапазоны входящих «всегда» и исходящих «никогда». итог части: срочный байт и ложное окнонаш Linux и наш стенд срочный байтoobсдвиг на единицу; пакет PAU на 251 байт; наш Linuxвынимает байт; подстрока слепа посередине имени;ловушки убивают соединениев нашем тесте зажим окна у клиентаwssizeSYN, ACK и приветствие с окном 1; SYN со множителем 6;первый кусок 64; 40 кусков вместо 5; цена в 5 раз повремени, без отключения в 14в нашем тестеруководство зажим на сервереwsize на сервереклиент режет своё приветствие 1 / 107 / 72 / 70 послепаузы около 0,2 св нашем тесте зажим на клиентеwsize на клиентемёртв, пока не спрятать исходящие; тогда душит самогоклиента: 223,4 мс вместо 11,1в нашем тестеруководство чего не знаем другие получателине проверялинастоящий инспекторнеизвестнопричина размеров и паузы107 / 72 / 70: не нашли проверь себя 1wssize:wsize=1:scale=6: на каком исходящем отключается, какое окно потом? ? № 363SYN, ACK, приветствие; дальше окно, что записало ядро проверь себя 2что нужно, чтобы wsize сработала на клиенте? ? --in-range=a --out-range=xиначе выключится на своём SYN или не увидит SYN-ACK дальше в выпускевыпуск продолжается ловушки окна серверная сторона код все выводы относятся к нашему Linux и нашей модели

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

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

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

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

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

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

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

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

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

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

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

Окно в сто байт: остановка и ход без сборки Когда сервер подменяет окно в SYN-ACK на 100 байт с множителем 0, при включённой сборке приветствия программа придерживает первые 100 из 250 байт, ядро их не подтверждает, и клиент шлёт те же 100 байт пять раз за 1,94 с, а приложение сервера получает 0 байт, при отключённой сборке сервер подтверждает 101 с окном 64 и клиент досылает куски 64, 64 и 22, всего 250 байт, причём кусок в 1 байт программа признаёт неизвестным и пропускает, а кусок в 100 байт распознаёт как начало приветствия. окно в сто байт: провод клиента, программа стоит на сервере игрушка: приёмнаявыдуманный пример«Приносите не больше ста листов за раз». Вы принесли сто листов, это начало документа; помощник удвери не расписывается, пока нет продолжения: вы ждёте подписи, он ждёт продолжения. wsize:wsize=100:scale=0в SYN-ACK сервера окно 100 байт и множитель окна 0 сборка приветствия включенаприветствие 250 байт, окно позволяет только сто клиентсервер 1SYN клиентаS · win 64240 · WS 100,00 мс 2SYN-ACK сервера, подменённыйSA · win 100 · WS 0 · TTL 6332,35 мс 3ACK клиентаA · win 6332,43 мс 4приветствие, первые 100 байтPA · rel 1 · len 10032,55 мс 5та же сотня, копия 2PA · rel 1 · len 100269,18 мс 6копия 3PA · rel 1 · len 100509,19 мс 7копия 4PA · rel 1 · len 1001013,17 мс 8копия 5PA · rel 1 · len 1001973,18 мс пять копий однихи тех же ста байтот первой до пятой1,94 с журнал программы, первая копияTLS client hello is PARTIALприветствие частичное: пришло 100 из 250starting reassemble. now we have 100/250DELAY desync until reasm is complete (#1)копии 2-5 так же: (#2) … (#5)packet: id=4 dropпридержан: len=152 = 52 заголовка + 100 данных приложение сервера: 0 байт · ответ клиенту: пусто · ядро пакет не получило сборка отключена: --reasm-disable=tls_client_helloSYN-ACK тот же: окно 100, множитель 0 клиентсервер 1приветствие, 100 байтPA · rel 1 · len 10011,60 мс 2подтверждение сервера: 101A · win 64 · множитель 022,18 мс 3клиент шлёт по 64 байтаPA · rel 101 · len 64234,66 мс 4ещё 64 байтаPA · rel 165 · len 64510,63 мс 5последние 22 байта и конецFPA · rel 229 · len 22521,25 мс в заголовке 64;множитель мы обнулили,клиент читает 64 байта приложение сервера читает100646422= 250, ответ получен окно в 1 байт: остановки не былотип нагрузки: приветствие TLS · запрос HTTP · неизвестно кусок в 1 байтpayload_type=unknownpacket: id=4 pass unmodified кусок в 100 байтTLS client hello is PARTIALсборка включается, пакет придержан где проходит граница между 1 и 100 байтами, мы не искали: проверены только эти два значения

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

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

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

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

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

Список имён и порядок строк Профиль со списком имён на SYN не срабатывает, потому что имя сайта появится только в приветствии, поэтому зажим окна оставил SYN нетронутым с окном 64240 и множителем 10, окно 1 получило только приветствие, а ответ сервера пришёл теми же пятью сегментами 1448, 1448, 1448, 1448 и 208 байт, и порядок строк решает судьбу окна в SYN с данными, если syndata стоит первой, копия уходит с окном 64240 и множителем 10, а если сначала зажим окна, то с окном 1 и множителем 6, потому что копия делается в коде сразу после ворот, а подтверждение и оба куска приветствия несут окно 1 в обоих запусках. две ловушки окна: список имён и порядок строк профиль выбран по имени сайта: на SYN имени ещё нет, оно появится только в приветствиио профилях подробно в выпуске «Профили: как программа выбирает» SYN клиентаS · win 64240 · WS 10SYN нетронут SYN-ACK сервераSA · win 65160 · WS 10 ACK клиентаA · win 63 приветствие 250 БPA · win 1единственный пакет с ложью ответ сервера14481448144814484 × 1448 + 208 = 6000 пять сегментов, как без стратегии: те же длиныLUA: wsize_rewrite: window size 63 => 1LUA: wssize: forced cutoff окно приветствия на ответ не повлияло, почему, не выясняли третья ловушка: порядок стратегий в строке --lua-desync=syndata --lua-desync=wssize:wsize=1:scale=6 syndataкопирует пакет и отправляет копиюwssizeправит пакет, который программа держит сама проверь себя: какое окно в SYN с данными, если syndata стоит первой? ? пакетsyndata, потом wssizewssize, потом syndata SYN с данными, 16 байт win 64240 · WS 10 win 1 · WS 6 ACK клиента win 1 win 1 кусок приветствия, 104 Б win 1 win 1 кусок приветствия, 146 Б win 1 win 1 в обоих запусках дальше стоит multisplit:pos=midsld, поэтому приветствие идёт кусками 104 и 146 байт код syndata: копия сразу после воротlua/zapret-antidpi.lua, строки 397-398 397 if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then398 local dis = deepcopy(desync.dis) правка окна от wssize приходит позже копии документация проекта, не наша проверка«Если перепутать порядок следования, то syndata будет отправлена без wssize»

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

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

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

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

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

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

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

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

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

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

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

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

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

Роль и ярлык при флаге сервера Флаг сервера не меняет роль отправителя, а переворачивает ярлык направления: в нашем тесте SYN клиента при программе на клиенте имеет роль клиент и ярлык исходящий, а при программе на сервере с флагом роль остаётся клиент, а ярлык становится входящий, SYN-ACK и ответ сервера из 14 байт имеют роль сервер и ярлык входящий у клиента, но исходящий у сервера, цель поиска профиля в режиме сервера это адрес клиента и порт 443, а стратегия synack_split видит SYN клиента вне своего диапазона и выключается, а SYN-ACK сервера в диапазоне и работает. режим сервера: что флаг меняет, а что нет клиентроутерсервер nfqws2 до сих пор nfqws2 --server клиенту ничего ставить не нужно игрушка: письмо от Анны к Борисувыдуманный пример разбираем ящик АнныАннаБорисисходящееотправительполучатель разбираем ящик БорисаАннаБорисвходящееотправительполучатель роль: клиент или серверклиент это тот, кто первым прислал SYN;флаг сервера роль не меняет ярлык: «входящий» или «исходящий»слово, которое программа клеит на пакет;флаг сервера его переворачивает пакетпрограмма на клиентепрограмма на сервере, с флагом SYN клиента роль клиент · ярлык «исходящий»track_direction=out fixed_direction=out роль клиент · ярлык «входящий»track_direction=out fixed_direction=in SYN-ACK сервера роль сервер · ярлык «входящий»track_direction=in fixed_direction=in роль сервер · ярлык «исходящий»track_direction=in fixed_direction=out ответ, 14 байт роль сервер · ярлык «входящий»track_direction=in fixed_direction=in роль сервер · ярлык «исходящий»track_direction=in fixed_direction=out цель поискаадрес сервера, порт 443ip=10.2.0.2 port=443адрес клиента, порт 443ip=10.1.0.1 port=443 порт в фильтрах профиля всегда серверный, адрес для списков в режиме сервера клиентский диапазон «эй» (всегда) относится к пакетам, которые отправляет сам сервер: synack_split 'synack_split_1_2' : in pos x0 x0 out of range x0-x0SYN клиента: вне диапазона, стратегия выключается'synack_split_1_2' : out pos a0 a0 in range a0-a0SYN-ACK сервера: в диапазоне, стратегия работает

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

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

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

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

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

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

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

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

Правила ядра сервера и две ошибки Правила ядра сервера зеркальны правилам клиента, потому что цепочки prerouting и postrouting поменяны местами, поставочный пример для сервера обходится одним правилом на пакеты сервера, пока клиент отправил не больше 15 пакетов, а в нашем тесте правила клиента на сервере не дали ни одной записи о пакете и приложение получило 250 байт, при верных правилах без флага сервера multisplit с позицией 5 режет приветствие клиента, и приложение сервера читает 5 и 245 байт, тогда как с флагом режется ответ сервера на 5 и 9 байт, а synack_split без флага выключается на SYN клиента и провод остаётся прежним. правила ядра для очереди и две ошибки на сервере камеру на другом конце дороги поворачивают зеркально: выезд стал въездомвыдуманный пример клиент: два правила, в каждом направлении первые 12 пакетовreadme, строки 83 и 88, условие метки опущеноpostroutingtcp dport {80,443} ct original packets 1-12 queue num 200 bypassклиент отправляетpreroutingtcp sport {80,443} ct reply packets 1-12 queue num 200 bypassклиент получает цепочки меняются местами: prerouting и postrouting сервер: те же два правила, цепочки поменяныв лаборатории написаны вручнуюpreroutingtcp dport 443 ct original packets 1-12 queue num 200 bypassсервер получаетpostroutingtcp sport 443 ct reply packets 1-12 queue num 200 bypassсервер отправляет установка меняет слова в правиле клиента (nft.sh, строки 327-330): так строится правило ответовэто не то, что установка делает для сервера: правила сервера мы писали вручную пример для сервера из поставкиinit.d/custom.d.examples.linux/40-webserver --serverфлаг сервераfake + multisplitдля ответа сервераsynack_splitдля пустых пакетовf="tcp sport {$WEBSERVER_PORTS} $first_packets"одно правило, только пакеты сервера: tcp sport {80,443} ct original packets 1-15строки 36-38считает пакеты клиента, до 15 ошибка 1: правила клиента на сервере в журнале программызаписей о пакетах: 0провод чистыйкак у обычного сервераприложение сервера250 байт целиком ошибка 2: правила верные, флага нет--payload=all --lua-desync=multisplit:pos=5:payload=all с флагом: режется ответ сервераS>C PA len 5S>C PA len 9клиент прочитал первые пять букв ответаприложение сервера: [250] без флага: режется приветствие клиентаC>S PA len 250S>C A rel_ack 6LUA: multisplit: split pos: 5приложение сервера: [5, 245]на проводе приветствие целое;куски, по нашему чтению, внутри сервера synack_split без флага: первым видит SYN клиента и выключается, провод без измененийinstance cutoff for 'synack_split_1_2' in=1 out=1

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

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. Подключение у клиента завершилось примерно через двадцать две миллисекунды.

Расщеплённый SYN-ACK на проводе клиента В режиме по умолчанию synack_split заменяет SYN-ACK сервера двумя копиями с изменёнными флагами: сначала пакет только с SYN, номер подтверждения в нём остался, затем чистый ACK с опциями SYN-ACK, а обломок с протоколом 255 и сроком жизни 1 виден между сервером и роутером и клиента не достигает, в нашем тесте клиент на Linux отвечает на голый SYN пакетом SYN-ACK, это по общим сведениям одновременное открытие, отправляет ACK и посылает приветствие в 250 байт через 22,11 мс после первого SYN, когда от сервера приходит ещё один ACK. synack_split в режиме по умолчанию: пакеты на проводе клиента игрушка: две открытки вместо однойвыдуманный примерВместо одной открытки «согласен, вот мой номер» приходят две: «вот мой номер» и «согласен».Получатель на первую отвечает: «вы тоже хотите начать?» и ждёт. --server --lua-desync=synack_split:mode=synackmode по умолчанию synack клиентсерверроутер 1SYN клиентаS · win 642400,00 мс 2первый пакет сервера: только SYNS · ACK снят, поле ack осталось11,04 мс 3второй пакет сервера: чистый ACKA · rel_ack 1 · опции как у SYN-ACK11,07 мс +обломок: протокол 255, TTL 1, 60 байтсумма IP неверна · виден у роутера и серверана проводе клиента его нет 4клиент отвечает пакетом SYN-ACKSA · rel_ack 1 · win 6424011,07 мс 5ACK клиентаA · rel_seq 1 · rel_ack 1 · win 6311,08 мс 6ещё один ACK сервераA · rel_seq 1 · rel_ack 121,89 мс 7приветствие, 250 байтPA · rel_seq 1 · len 25022,11 мс опции: MSS, SACKOK,TS, WS=10 одновременноеоткрытие, по общимсведениям обычный ACK клиентаопции: NOP NOP TSACK из расщепленияопции: MSS SACKOK TS NOP WS=10 обе части — копии одного пакета: функция меняет в копиях только флаги;опции, окно и номера остаются от настоящего SYN-ACK. Нужно ли это для обхода, не знаем клиент: соединение открыто за 22,1 мс; приветствие ушло после ещё одного ACK сервера SYN-ACK клиента и ACK сервера на 11,07 мс: порядок между ними менялся от прохода к проходу

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

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

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

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

Три режима synack_split и два края У стратегии synack_split три режима: по умолчанию synack шлёт SYN и затем ACK, режим syn шлёт один SYN, а режим acksyn сначала ACK и потом SYN, и в нашем тесте во всех трёх клиент считает соединение открытым примерно через 22 мс и во всех трёх уходит обломок, на клиенте та же стратегия выключается на первом пакете и провод остаётся обычным, неизвестный режим даёт ошибку и уходит обычный SYN-ACK, а в режиме syn после ответа клиента SYN-ACK сервер присылает чистый ACK на 21,49 мс. три режима расщепления SYN-ACK: сигнатуры на проводе клиента --server --lua-desync=synack_split:mode=synack mode=synackS, затем A · по умолчаниюmode=synтолько Smode=acksynA, затем S сигнатуры на проводе клиента: C клиент, S сервер, PA250 приветствие; жёлтым куски расщепления synackоткрыто 22,1 мсC:SS:SC:SAS:AC:AS:AC:PA250сервер шлёт SYN и ACK подряд; приветствие уходит после ещё одного ACK synоткрыто 21,7 мсC:SS:SC:SAS:AC:PA250S:A...один SYN; после ответа клиента SYN-ACK сервер присылает чистый ACK acksynоткрыто 21,6 мсC:SS:AS:SC:SAS:AC:PA250...сначала ACK, потом SYN, дальше то же самое, что в режиме syn во всех трёх режимах уходит обломокстратегия выбрасывает SYN-ACK, первый пакет сервера, и программа подменяет его обломком два края на клиентепервым видит SYN клиента, а не SYN-ACK,и выключаетсяinstance cutoff for 'synack_split_1_2' in=1 out=1провод обычный, соединение обычное неизвестный режим: mode=badsynack_split: bad mode 'bad'desync ERROR. passing packet unmodified.уходит обычный SYN-ACK, приложение [250]соединение обычное проверь себя: mode=syn. Какой пакет сервер отправит после ответа клиента SYN-ACK? ? S>C [S]11,01 мссервер: только SYNC>S [SA]11,04 мсответ клиентаS>C [A]21,49 мсчистый ACK: ответ

Итак. Стратегия 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?

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

Цена расщепления SYN-ACK Расщепление SYN-ACK стоит серверу один круг, в нашем тесте пауза между ACK клиента и приветствием у обычного сервера 0,09 мс, а в режиме synack около 11 мс при круге в 10 мс, в режимах syn и acksyn клиент шлёт приветствие пять раз с паузами 216, 216, 463 и 849 мс, сервер подтверждает данные только после пятой копии примерно через 1,8 с после первого SYN, а счётчики сервера показывают четыре отвергнутых эха метки времени, один повтор SYN-ACK и один тайм-аут, причём это наше чтение, ядро сервера мы не открывали. цена расщепления: пауза перед приветствием и медленные режимы игрушка: две встречи на часахвыдуманный примерВ первой «здравствуйте» и сразу разговор, во второй после «здравствуйте» пауза на выяснение, кто когопоприветствовал: на часах одно деление и десять. запускACK клиентаприветствиеразрыв, мс без стратегии, контроль10,5510,640,09 synack, запуск 111,0822,1111,03 synack, запуск 210,5421,2110,67 synack, запуск 310,6621,3610,70 круг 10 мс: netem 5 + 5 клиент ждал ещё одного ACK от сервера; в запуске 1 приветствие ушло через 0,22 мс после его прихода режимы syn и acksyn: приветствие клиента пять раз, мс от первого SYNпровод клиента 050010001500клиент: приветствиесервер 1 2 3 4 5 216 мс 216 мс 463 мс 849 мс пустые ACK со старым номером: 32,34 · 251,86 · 464,10 · 928,67 повтор SYN-ACK самим сервером, 1038,90 ack 251: данные подтверждены, 1776,13 счётчики сервера до и послережимы syn и acksyn:TSEcrRejected +4TCPSynRetrans +1 · TCPTimeouts +1режим synack:все три счётчика 0 метки времени: согласуется, не доказываетэхо метки у копий 1-4 равно метке последнегопакета сервера перед ней; у пятой тоже,поэтому по меткам копии не отличитькод ядра не открывали проверь себя: круг стал 20 мс вместо 10, какая пауза у режима synack?по счёту около 20 мс, один круг; такой запуск мы не делали

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

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

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

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

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

Строгая игрушка и три режима расщепления Наша игрушка строгое рукопожатие ждёт SYN клиента, SYN-ACK сервера с верным подтверждением и ACK клиента и только потом ищет имя сайта в пакетах клиента, без стратегии она блокирует приветствие и приложение сервера получает 0 байт, а при расщеплении SYN-ACK в любом из трёх режимов она записывает, что вместо SYN-ACK пришёл S или A, объявляет поток недействительным и не проверяет его, поэтому приветствие доходит и приложение получает 250 байт, но в режимах syn и acksyn ответ приходит лишь через 1,8 с, что делает настоящий инспектор, нам неизвестно. что даёт расщепление против строгой игрушки руководство проекта, не наша проверкаМногие инспекторы ждут на SYN обычный SYN-ACK; ответ на SYN становится другим SYN, потом клиент шлётSYN-ACK серверу; инспектор теряет, где клиент, где сервер. Мы это не проверяли, о настоящемоборудовании нам ничего не известно. игрушка «строгое рукопожатие»: экзаменатор с чек-листом из трёх клетоквыдуманный пример 0ждёт SYN клиента 1ждёт SYN-ACKсервера с вернымподтверждением 2ждёт ACKклиента 3ищет имя впакетах клиента пришло не то: поток объявлен недействительным и больше не проверяется режимзапись игрушкиприложениеответ без стратегииstate 0->1: client SYN seenstate 1->2: server SYN,ACK seen with the right ackstate 2->3: client ACK seen: handshake complete, inspection onDECISION BLOCK: host name in a client packetafter a clean handshake[]пусто synackstate 0->1: client SYN seenDECISION INVALID: expected the server SYN,ACK, got Sfrom server: flow is not a clean handshake, never inspected[250]есть, 33 мс synstate 0->1: client SYN seenDECISION INVALID: expected the server SYN,ACK, got Sfrom server: flow is not a clean handshake, never inspected[250]на 1,78 с acksynstate 0->1: client SYN seenDECISION INVALID: expected the server SYN,ACK, got Afrom server: flow is not a clean handshake, never inspected[250]на 1,76 с запись игрушки без префикса; ответ считается от первого SYN; приложение читает 250 байт проверь себя: режим acksyn. Какую букву игрушка запишет вместо ожидаемого SYN-ACK? ? букву A: первым от сервера пришёл ACK, поток объявлен недействительным итог, в нашем тесте на Linux 6.18 с нашими игрушкамиsynack стоит один круг, syn и acksyn около 1,8 сстрогая игрушка проиграла во всех трёх режимах: приветствие дошлонужны флаг сервера и правило ядра, которое отдаёт программе пакеты сервера

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

Итак. Режим 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 нёс два байта данных, и соединение открылось.

Шесть шагов syndata в коде Функция syndata состоит из шести шагов: ворота пропускают только голый SYN, глубокая копия принимает все правки, заготовка берётся из аргумента blob или это 16 нулевых байт, условия применяются к единственному SYN, поэтому срок жизни 1 убивает рукопожатие, а плохая сумма оставляет клиента без ответа, tls_mod получает только заготовку и потому rnd и sni работают, давая SYN в 673 байта, а dupsid ничего не делает и оставляет 680 байт, а шестой шаг отправляет копию в 76 байт и возвращает выбросить для оригинала. syndata шаг за шагом: код и что видно в нашем тестеlua/zapret-antidpi.lua игрушка: ксерокопию письма с дописанными строками отправляют, оригинал рвутвыдуманный пример 1воротастроки 396-397396if desync.dis.tcp then397  if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN thenпроходит только голый SYN; другой пакет рукопожатия, например ACK, выключает экземпляр:«задача выполнена»instance cutoff for 'syndata_1_2' in=1 out=1: запись в журнале на первом ACK 2глубокаякопиястрока 398398    local dis = deepcopy(desync.dis)все правки идут в копию: что исправлено раньше, попадает в неё, что позже, нет; отсюдапорядок со wssize 3заготовкастрока 399399    dis.payload = blob(desync, desync.arg.blob, "\x00" x 16)blob (zapret-lib.lua, строки 534-560): нет аргумента: 16 нулевых байт; «0x» и цифры: эти байты;имя: заготовка. В коде строка 399 длиннее: 16 раз \x00blob 'nosuchblob' unavailable, затем desync ERROR. passing packet unmodified.обычный SYN ушёл без изменений, соединение заработало 4условиястрока 400400    apply_fooling(desync, dis)применяются к единственному SYN: срок жизни 1 убивает рукопожатие, плохая суммаоставляет клиента без ответасрок 1: SYN len 16, ICMP type 11 на 5,10 мс, Errno 113 No route to hostbadsum: SYN len 16 на 0,00, 1004,51 и 2027,95 мс, error: timed out 5tls_modстроки 401-404401    if desync.arg.tls_mod then402      local pl = tls_mod_shim(desync, dis.payload, desync.arg.tls_mod, nil)настоящее приветствие не передано (четвёртый аргумент nil): rnd и sni работают, dupsidкопировать нечегоchange SNI : www.microsoft.com => google.com size_delta=-7, SYN 673 = 680 - 7tls_mod=dupsid: SYN 680, строки applied dupsid нет (dupsid внутри if payload, protocol.c:884) 6отправкастроки 405-408405    if b_debug then DLOG("syndata: "..hexdump_dlog(dis.payload)) end406    if rawsend_dissect_ipfrag(dis, desync_opts(desync)) then407      return VERDICT_DROP408    endуспех: оригинал выбрасывается; провал: оригинальный SYN уходит как естьrawsend_dissect repeats=1 size=76: 20 + 40 + 16 = 76 байт проверь себя: syndata:blob=0x1603. Сколько байт данных будет в SYN? ? два байта: «0x» и четыре цифры; в нашем тесте SYN len 2, и соединение открылось

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

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

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

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

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

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

Подмена первого пакета после syndata Когда syndata выбрасывает первый пакет роли, функция подмены первого пакета копирует исходный пакет, записывает в заголовок IP протокол 255 и срок жизни 1, не пересчитывает контрольную сумму и возвращает изменить, так что в нашем тесте на проводе уходит обломок в 60 байт, отличающийся от оригинала двумя байтами, а остальные выброшенные пакеты, повторный SYN и приветствие oob, исчезают по-настоящему, и при нулевой задержке на линии в пяти запусках из шести SYN-ACK приходил раньше обломка, счётчик недействительных пакетов у клиента вырос на шесть, но соединение всё равно открылось. оригинал выброшен: что тогда? подмена первого пакетаnfq2/desync.c 1вызов: стратегии вернули «выбросить», пакет исходящийстрока 1660, разбита на две1660return (!ps.bReverseFixed && (ps.verdict & VERDICT_MASK) == VERDICT_DROP) ?    ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt) : ps.verdict; комментарий автора (строки 643-645): выбрасывание первого пакета рвёт запись о соединениипричина по словам автора; в лаборатории нет трансляции адресов, защиту мы не проверяли 2условие: счётчик пакетов роли равен единицестрока 646 646(ctrack && (params.server ? ctrack->pos.server.pcounter : ctrack->pos.client.pcounter) == 1) || режим клиента: счётчик клиента, первый пакет клиента · режим сервера: счётчик сервера 3три действиястроки 653-672 653        memcpy(mod_pkt, dis->data_pkt, dis->len_pkt);664        ((struct ip*)mod_pkt)->ip_p = 255;665        ((struct ip*)mod_pkt)->ip_ttl = 1;668        return VERDICT_MODIFY | VERDICT_NOCSUM;672  return VERDICT_DROP; копия всего исходного пакета протокол 255 срок жизни 1 вернуть «изменить», сумму не считать иначе настоящее выбрасывание байты заголовка IP: оригинал и обломок в нашем тесте оригиналобломок4545000000003c3ce8e8414140400000400106ff3e3e75750a0a0101000001010a0a020200000202TTL 64, протокол 6TTL 1, протокол 255 отличаются два байта; сумма 3e 75 прежняя и неверна, верная 7c 7c; 40 байт TCP равны журнал: applying linux postnat conntrack workaround, packet: id=1 pass modified. len 60 => 60 остальные выброшенные пакеты исчезают по-настоящему: счётчик уже не один повторный SYN через секундуpacket: id=2 drop, обломок один приветствие oob, третий пакет клиентаpacket: id=4 drop synack_split, режим сервераpass modified. len 60 => 60: заменён первый пакет сервера обломок от сервера виден у роутера и сервера, клиент его не получает оговорка про лабораторию, только про syndata: нулевая задержка на линии круг 10 мсSYN-ACK в establishedinvalid: +0как обычно без задержки, пять из шести: SYN-ACK раньше обломкаinvalid +6, insert_failed +1, drop +1на проводе нет подтверждения рукопожатия,но соединение открылось SYN len 160,00 мсSYN-ACK0,04 мсобломок0,12 мсPA 250 байт0,15 мс объяснение наше, ядро мы не читали; по общим сведениям на длинных путях круг больше времени работыпрограммы, но мы это не проверяли

Итак. В 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 со сроком жизни шестьдесят четыре, и сервер ответил сбросом. Условие функция просто не прочитала.

Копии пакета: rst и synack Игрушка с тремя копиями чека и код функций rst и synack: rst копирует приветствие, заменяет флаги на сброс, применяет условия строки запуска и отправляет без вердикта, поэтому в нашем тесте сброс со сроком жизни 1 ушёл на 0,06 мс раньше приветствия с тем же номером, подтверждением, окном 63 и идентификатором IP 27216 при длине IP 52 байта, а synack копирует SYN, добавляет флаг ACK, отправляет сразу и тоже не возвращает вердикт, поэтому условие срока жизни 1 не прочитано, подложный SYN-ACK ушёл со сроком 64, а сервер ответил сбросом. rst и synack: что берут, что подменяют, что возвращаютвыдуманный пример игрушка: три копии чека зачёркнуто всё,написано «отмена»rst принятоприписана отметка«принято»synack разрезана пополамsynack_split rst: сброс до приветствиястроки 427-437 427direction_cutoff_opposite(ctx, desync)428if direction_check(desync, "any") and payload_check(desync) then429if replay_first(desync) then431dis.payload = ""432dis.tcp.th_flags = TH_RST + (desync.arg.rstack and TH_ACK or 0)433apply_fooling(desync, dis)434apply_ip_id(desync, dis, nil, "none")437rawsend_dissect_ipfrag(dis, desync_opts(desync)) выключает только встречное направление ворота: известный тип нагрузки при сборке по частям только перед первой данные пусты флаги заменены на сброс условия применяются к сбросу идентификатор IP не трогается отправка сразу; return нет вердикта нет: настоящее приветствие идёт следом, как есть в нашем тесте: rst:ip_ttl=1, провод клиентамс от SYN 1флагиRseq1ack1win63len0ttl1id27216IP, байт5219,63 2флагиPAseq1ack1win63len250ttl64id27216IP, байт30219,69 сброс на 0,06 мс раньше приветствияполе подтверждения то же, флага ACK нетIP 52 = 20 + 32 (TCP 20 + опции 12) synack: ответ раньше вопросастроки 299-305 299if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK)==TH_SYN then300local dis = deepcopy(desync.dis)301dis.tcp.th_flags = bitor(dis.tcp.th_flags, TH_ACK)303rawsend_dissect_ipfrag(dis, desync_opts(desync))305instance_cutoff_shim(ctx, desync) -- mission complete ворота: голый SYN копия SYN к флагам добавлен ACK, и всё сразу отправка: без условий, return нет → на любом другом пакете: экземпляр выключен срок жизни функция не читает; плохую сумму и повторы читает сама отправка в нашем тесте: synack:ip_ttl=1, провод клиентамс от первого пакета 1флагиSAseq0ack0win64240ttl64id51333IP, байт60подложный: срок 64, а не 10,00 2флагиSseq0ack0win64240ttl64id51333IP, байт60настоящий SYN, тот же id0,08 3флагиRseq0ack0win0ttl63IP, байт40сервер отвечает сбросом10,70 ip_ttl=1 функция не прочитала, поэтому подложный SYN,ACK дошёл со сроком 64

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

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

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

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

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

Расщепление ответа и три функции рядом Код synack_split с режимом synack, который отправляет копию без ACK и копию без SYN от одного пакета и возвращает «выбросить», если обе ушли, и «пропустить», если нет, в нашем тесте оба куска ушли со 60 байтами IP и с одинаковыми опциями SYN, а обычный ACK занимает 52 байта, сравнение rst, synack и synack_split показывает, что срок жизни читает только apply_fooling, а плохую сумму и повторы читает сама отправка, и проверка: подложный SYN,ACK с плохой суммой дошёл до сервера на 0,00 мс, настоящий SYN прошёл на 0,05 мс, а ответа на подложный пакет не было. synack_split: расщепление ответа серверастроки 256-284 256if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then257local mode = desync.arg.mode or "synack"265elseif mode=="synack" then267dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_ACK))269if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end270dis.tcp.th_flags = bitand(desync.dis.tcp.th_flags, bitnot(TH_SYN))272if not rawsend_dissect_ipfrag(dis, options) then return VERDICT_PASS end273return VERDICT_DROP284error("synack_split: bad mode '"..mode.."'") ворота: оба флага по умолчанию режим synack режим synack; есть ещё syn и acksyn копия без ACK не ушла: пропустить → вторая копия без SYN, от того же пакета не ушла: пропустить обе ушли: настоящий SYN,ACK выброшен → неизвестный режим: ошибка, уходит обычный SYN,ACK в нашем тесте: режим synack, два куска ответа на проводе клиентамс от SYN 1флагиSseq0ack1win65160ttl63id0IP, байт60опцииMSS SACKOK TS WS=1011,04 2флагиAseq0ack1win65160ttl63id0IP, байт60опцииMSS SACKOK TS WS=1011,07 оба куска от одного пакета: опции SYN остались и у чистого ACKобычный ACK клиента короче: 52 байта, опции NOP NOP TS три функции рядом берётменяетусловиявердиктдругой пакет rstкопиюприветствияфлаги на RST,данные пустыда, черезapply_foolingнет: пропуститьвыключает тольковстречноенаправлениежурнал: in=1 out=0 synackкопию SYNдобавляет ACKнет, прямаяотправканет: пропуститьвыключаетэкземплярцеликомжурнал: in=1 out=1 synack_splitкопию SYN,ACKснимает ACK илиSYNнет, прямаяотправкавыбросить послеотправки,пропустить принеудачевыключаетэкземплярцеликомжурнал: in=1 out=1 плохую сумму и повторы читает сама отправка (строки 423-431); срок жизни и остальноечитает только apply_fooling, а из трёх его зовёт один rst проверь себяsynack с условием «плохая сумма»: дойдёт ли подложный SYN,ACK до сервера? ? дойдёти будет молча отброшен: сумма неверна, а её читает сама отправка клиентсерверкарта сервера, мс 1подложный SYN,ACK, сумма BAD0,00 2настоящий SYN0,05 3SYN,ACK сервера на настоящий SYN0,07 ответа нет настоящий SYN прошёл, а на подложный пакет ответа сервера не было

Итак. 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, и экземпляр выключается раньше, чем придёт ответ сервера. Мы даже разрешили входящие пакеты, и всё равно в нашем тесте приветствие ушло одним пакетом, а ответ сервера пришёл теми же пятью сегментами, что и без стратегии.

Переписчик окна и функция wsize Игрушка с табличкой «мест нет» на двери и код переписчика окна и функции wsize: переписчик пишет число wsize в поле окна как есть и может только уменьшить множитель окна, а опцию множителя сам не добавляет, поэтому в нашем тесте SYN получил окно 1 вместо 64240 и множитель 6 вместо 10, а подтверждение только окно 1 вместо 63, что при множителе 6 даёт 64 байта вместо 1024, wsize переписывает только SYN,ACK, поэтому на сервере клиентское ядро получило окно 1 и множитель 6, а на клиенте экземпляр выключился на собственном SYN и ответ пришёл сегментами 1448, 1448, 1448, 1448 и 208. переписчик окна и функция wsizeвыдуманный пример игрушка: табличка «мест нет» на двери, пакет это дверь на одну дверьSYNSYN,ACKмест нетACKданныеwsize на каждую по очередиSYNмест нетACKмест нетданныемест нетдальшеwssize по очереди, пока не вошёл с вещами: до первых данных включительно переписчик окна: функция wsize_rewriteстроки 1449-1471 1451if arg.wsize then1454dis.tcp.th_win = tonumber(arg.wsize)1457if arg.scale then1459local i = find_tcp_option(dis.tcp.options, TCP_KIND_SCALE)1462if scale>oldscale then1464elseif scale<oldscale then1466dis.tcp.options[i].data = bu8(scale)1471return b задан wsize число пишется в поле окна как есть задан scale → ищет опцию множителя; если её нет, ничего не добавляет больше: «не увеличиваем», только запись в журнал меньше: меняем множитель можно только уменьшить «да», если что-то изменила в нашем тесте: wssize:wsize=1:scale=6, пакеты клиента 1SYN, окно64240 → 1SYN, множитель10 → 6в журнале: window size 64240 => 1; scale factor 10 => 6 2ACK, окно63 → 1множительнет опцииопции множителя нет: поменялось только окно окно 1 при множителе 6 это 1 × 2^6 = 64 байта; при множителе 10 было бы 1 × 2^10 = 1024согласуется с первым куском ответа сервера в 64 байта; согласие не доказывает причину функция wsize: один SYN,ACKстроки 319-324 319if bitand(desync.dis.tcp.th_flags, TH_SYN + TH_ACK) == (TH_SYN + TH_ACK) then320if wsize_rewrite(desync.dis, desync.arg) then321return VERDICT_MODIFY324instance_cutoff_shim(ctx, desync) -- mission complete → ворота: TCP и оба флага переписчик что-то изменил вердикт «изменить» → любой другой TCP-пакет: экземпляр выключен на сервере: wsize работаетSYN,ACK сервера: окно 65160 → 1,множитель 10 → 6клиентское ядро получило SYN,ACKименно с этими числами на клиенте: wsize молчитпервым экземпляр видит собственный SYN,выключается раньше ответа сервера:in=1 out=1, хотя входящие разрешеныответ: 1448, 1448, 1448, 1448, 208как без стратегии

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

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

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

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

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

Функция wssize, лента окон и проверка Код wssize переписывает окно только в исходящих пакетах и выключает экземпляр на первом пакете с данными уже после переписи, поэтому в нашем тесте SYN несёт окно 1 и множитель 6, подтверждение и приветствие окно 1, а дальше окно 63, при запрещённом отключении окно 1 осталось у 12 пакетов клиента из 28, потому что правило ядра присылает программе только первые 12 пакетов клиента, ответ сервера начался десятью кусками по 64 байта, а в проверке множитель 12 больше множителя 10 из SYN, поэтому переписчик пишет «не увеличиваем» и возвращает «нет», это чтение кода, а не запуск. функция wssize: исходящие пакеты до первых данных включительностроки 343-354 343local verdict = VERDICT_PASS344direction_cutoff_opposite(ctx, desync)345if direction_check(desync) then346if wsize_rewrite(desync.dis, desync.arg) then347verdict = VERDICT_MODIFY349if #desync.dis.payload>0 and (not desync.arg.forced_cutoff orin_list(desync.arg.forced_cutoff, desync.l7payload)) then350DLOG("wssize: forced cutoff")351instance_cutoff_shim(ctx, desync)354return verdict сначала выключает встречное направление дальше только исходящие пакеты переписчик зовётся на каждом исходящем если что-то изменил: вердикт «изменить» есть данные → и тип подходит, если задан forced_cutoff экземпляр выключен, а этот пакет уже переписан в нашем тесте: окно в пакетах клиента по порядкумс от SYN SYNwin 1 · WS 60,00 ACKwin 110,63 PA 250win 110,80 ACKwin 6321,63 ACKwin 63… переписаны: ложь кончается на первых данныхдальше честное окно ядра клиента с запретом отключения: forced_cutoff=no, пакеты клиента (28) окно 1: 12 пакетовокно 63: 16 пакетов правило ядра присылает программе только первые 12 пакетов клиента, поэтому ложь кончается сама ответ сервера начинался десятью кусками по 64 байта 6464646464646464646411-й и 12-й: 768, 768 проверь себяв SYN множитель окна 10, а в аргументе scale стоит 12: что сделает переписчик? ? ничего с множителемпишет «не увеличиваем» и возвращает «нет» 1461local oldscale = u8(dis.tcp.options[i].data)1462if scale>oldscale then1463DLOG("wsize_rewrite: not increasing scale factor")1471return b oldscale = 10 12 > 10: да строка в журнал, поля не тронуты b остался false: wsize не задан это чтение кода: такой запуск мы не делали

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

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

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

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

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

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

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

Ворота, сдвиг и ответ сервера в oob Игрушка про рукопись, где нумерацию начинают на единицу раньше, и код первых трёх шагов функции oob: ворота требуют память о соединении и голый SYN первым, сдвиг вычитает 1 из номера последовательности у SYN, подтверждения и первых данных, а у входящего SYN,ACK прибавляет 1 к номеру подтверждения, поэтому в нашем тесте SYN ушёл с номером 403398164 вместо 403398165, ответ сервера с подтверждением 403398165 стал для ядра 403398166, подтверждение и приветствие ушли с номером 403398165 вместо 403398166, а без диапазона входящих клиент отвечал на SYN,ACK сбросом через 10,49 мс. oob: ворота, сдвиг номера и ответ серверавыдуманный пример игрушка: рукопись; лишняя страница в начало, нумерация с единицей раньше как считает читатель1234 как пронумеровал автор0123 читатель = ядро клиентана единицу раньшечитателя, считающего по-старому, просят: «в подтверждении прибавьте единицу» oob: первые три шага в кодестроки 1098-1187 1098if not desync.track then return end1106if bitand(desync.dis.tcp.th_flags, TH_SYN+TH_ACK)~=TH_SYN then1107DLOG("oob: must be applied since the very beginning of the tcp connection - SYN packet")1108instance_cutoff_shim(ctx, desync)1115local pos = pos_get(desync, 's', false)1117local dseq = u32add(desync.dis.tcp.th_seq, -1)1119desync.dis.tcp.th_seq = dseq1121if pos==0 then1184local dack = u32add(desync.dis.tcp.th_ack, 1)1186desync.dis.tcp.th_ack = dack1187return VERDICT_MODIFY нужна память о соединении → ворота: первым пакетом должен быть голый SYN → иначе запись в журнал: приём надо применять с самого начала и отключение: с середины потока сдвигать нельзя относительный номер: SYN 0, ACK и первые данные 1 пока номер не больше 1: минус один у SYN, подтверждения и первых данных SYN и пустой ACK возвращают «изменить» входящий SYN,ACK: подтверждение плюс один сервер подтвердил сдвинутый номер, ядро ждёт настоящий в нашем тесте: числа из журнала программыдо правки → после правки 1SYN клиента, номер403398165403398164−1 2SYN,ACK сервера, подтверждение403398165403398166+1 3ACK клиента, номер403398166403398165−1 4приветствие, номер403398166403398165−1 все четыре числа взяты из журнала программы; на проводе клиента те же номера после правки чтобы функция увидела SYN,ACK, нужен диапазон входящих до эс одинбез него в нашем тесте клиент отвечал на SYN,ACK сервера сбросом через 10,49 мссервер подтвердил сдвинутый номер, а ядро клиента ждало настоящий

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

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

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

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

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

Байт, сборка пакета и ловушка в oob Код oob про байт, место и сборку пакета и две ловушки: в нашем тесте метка midsld дала позицию 104 и указатель срочности 105, данные 250 байт стали 251 байтом с флагом URG, сервер подтвердил 252, приложение прочитало 104 и 146 байт, а при ошибке в аргументе char из двух символов приветствие уходило со старым номером 2 вместо 1 шесть раз на 11,04, 225,03, 440,99, 889,05, 1752,95 и 3480,96 мс, сервер подтверждал 1, приложение получило 0 байт, и так же кончился запуск с меткой method, которую программа не смогла разрешить. oob: байт, место и сборка пакетастроки 1129-1164 1129local oob = desync.arg.char or (desync.arg.byte and bu8(desync.arg.byte) or nil) or "\x00"1131error("oob: OOB must be exactly one byte")1135if not desync.arg.urp or desync.arg.urp=='b' then1138elseif desync.arg.urp=='e' then1142urp = resolve_pos(data, desync.l7payload, desync.arg.urp)1149dis_oob.tcp.th_urp = urp1153dis_oob.payload = string.sub(data, 1, urp-1) .. oob .. string.sub(data, urp)1154dis_oob.tcp.th_flags = bitor(dis_oob.tcp.th_flags, TH_URG)1156if not rawsend_dissect_segmented(desync, dis_oob) then1161instance_cutoff_shim(ctx, desync)1164return VERDICT_DROP → байт: char или byte, по умолчанию нулевой байт ровно один, иначе ошибка b или не задан: в начало, указатель 0 e: в конец, указатель длина + 1 → метка превращается в позицию переводчиком меток для метки указатель = позиция с нуля + 1 → данные до позиции, байт, остальное к флагам добавлен флаг срочности пакет отправляется экземпляр выключается оригинал выброшен в нашем тесте: urp=midsld на приветствии TLS, данные пакета было104 байта146 байт стало104 байта146 байт срочный байт: позиция 104, указатель 105 250 + 1 = 251; 104 + 146 = 250; картинка не в масштабе: байт показан шире провод клиента и ответ серверамс от SYN; seq и ack относительные 1флагиPAUseq1ack1len251urp105IP, байт303приветствие со срочным байтом11,79 2флагиAack2521 за SYN плюс 251: сервер подтвердил всё приложение сервера: 104 и 146 байт ловушка: char=XY, в аргументе два символа ошибка на приветствии: функция выходит, приветствие уходит со старым номером 2, а сервер ждёт 1 PA 250 · seq 211,04 мсack 1 PA 250 · seq 2225,03 мсack 1 PA 250 · seq 2440,99 мсack 1 PA 250 · seq 2889,05 мсack 1 PA 250 · seq 21752,95 мсack 1 PA 250 · seq 23480,96 мссброс шестое приветствие уже после FIN сервера; приложение сервера получило 0 байт в журнале: LUA ERROR ... OOB must be exactly one byte, затем desync ERROR. passing packet unmodified. проверь себяаргумент urp равен method, а приветствие TLS: что будет? ? мёртвое соединениеметки method в приветствии нет oob: cannot resolve urp marker 'method'экземпляр отключён, приветствие уходит со старым номером, сдвиг назад не отменитькак и в ловушке выше: приветствие повторялось, приложение сервера получило 0 байт

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

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

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

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

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

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

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

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

Флаг сервера в коде и ярлык направления Где в программе читается флаг сервера: он разбирается в одной строке, где без значения включён, а значение ноль выключает, ярлык «входящий» получается как «пакет от сервера» исключающее ИЛИ флаг, поэтому при включённом флаге пакет клиента получает ярлык «входящий», что в журнале нашего теста видно как track_direction=out и fixed_direction=in, флаг также выбирает счётчики для диапазонов, порт серверный и адрес клиентский для фильтров профиля и поля Lua, а автоматические списки имён и общий кэш сроков жизни в режиме сервера выключены, и эти два места мы прочитали только в коде. разбор флагаnfq2/nfqws.c, строки 2490-2491 2490case IDX_SERVER:2491params.server = !optarg || atoi(optarg); без значения включён, --server=0 выключает первое место: ярлык направленияnfq2/desync.c, строка 1291 1291ps->bReverseFixed = ps->ctrack ? (ps->bReverse ^ params.server): (ps->bReverse = ifin && *ifin && (!ifout || !*ifout)); → ярлык «входящий» = «пакет от сервера» исключающее ИЛИ флаг → без записи о соединении: по именам интерфейсов, флаг не применяется (только код) исключающее ИЛИ: одинаковые значения дают «нет», разные дают «да» от серверафлагвходящийрежим и чей пакетжурнал нетнетнетрежим клиента, пакет клиентаtrack_direction=out fixed_direction=out данетдарежим клиента, пакет сервераtrack_direction=in fixed_direction=in нетдадарежим сервера, пакет клиентаtrack_direction=out fixed_direction=in даданетрежим сервера, пакет сервераtrack_direction=in fixed_direction=out флаг выключен: ярлык совпадает с ролью; включён: ярлык перевёрнут. Это мы видели в журналах остальные места, где читается флагnfq2/desync.c, последняя строка nfq2/lua.c 930pos = tpos ? (bIncoming ^ params.server) ? &tpos->server : &tpos->client : NULL;1177*sdport = saport((struct sockaddr *)((bReverseFixed ^ params.server) ? src : dst));998lua_pushf_bool(params.L, "outgoing", !bIncoming);1762lua_getfield(L, -2, (params.server ^ bIncoming) ? "server" : "client"); → счётчики для диапазонов: у «исходящего» в режиме сервера счётчики роли сервер → фильтры профиля: порт всегда серверный, адрес в режиме сервера клиентский Lua: поле «исходящий» с тем же ярлыком → в файле lua.c: поля direct и reverse выбираются по ярлыку что в режиме сервера выключенотолько чтение кода, не запускали 507if (params.server) return; // no autohostlists in server mode3225if (params.server && dp->hostlist_auto)794if (ctrack && !params.server)1336ps->ctrack->incoming_ttl = ttl46(dis->ip, dis->ip6); нет автоматических списков имён → в nfqws.c: если профилю они нужны, программа при запуске выходит с сообщением → общий кэш сроков жизни между соединениями выключен: первый пакет и так входящий → срок жизни виден из первого входящего пакета и в режиме сервера как поведёт себя ip_autottl на сервере, мы не проверялипо подсчёту: 19 строк на Си с этим флагом и одна на Lua

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

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

Теперь итог выпуска «Фокусы рукопожатия». Мы работали вокруг начала передачи. 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.

Итог выпуска, непроверенное и следующий выпуск Итог выпуска Фокусы рукопожатия в восьми строках и серый список того, чего мы не проверяли: syndata кладёт данные в SYN, но сервер Linux берёт только сам SYN, rst со сроком 1 умирает на роутере, synack вызывает безвредный сброс, oob после сдвига не отменить, ложь про окно режет приветствие клиента, флаг сервера переворачивает ярлыки, synack_split стоит один круг или около 1,8 с, а обломок подменяет первый пакет только у приёмов, которые его выбрасывают, при этом настоящий ТСПУ, другие получатели, ядро, причина 107, 72 и 70, IPv6 с фрагментами и сеть за преобразованием адресов остались непроверенными, а следующий выпуск про другие слои. итог выпуска: фокусы рукопожатия, наш Linux, наши игрушки приёмчто делаетчто мы видели в нашем тесте syndataSYN с даннымисервер на Linux берёт только сам SYN; игрушка «первыеданные решают» обманута, «данные SYN не читает» нет;условия убивают единственный SYN rstсброс передприветствиемсо сроком 1 умирает на роутере, с плохой суммой доходит, носервер игнорирует; срок 2 и чистый сброс разрываютсоединение synackSYN,ACK раньше SYNсервер отвечает безвредным сбросом; игрушка с теми жеролями обманута oobсрочный байт и сдвиг наединицупосле сдвига пути назад нет: ошибка оставляет мёртвоесоединение wssize, wsizeложь про окноответ начинается с 64 байт; клиент режет приветствие на 1,107, 72, 70; окно 100 байт со сборкой останавливаетсоединение --serverярлыки наоборот,зеркальные правилазабытый флаг и чужие правила: ловушки synack_splitSYN,ACK на два пакетацена: один круг или около 1,8 с обломокподмена первого пакетатолько у приёмов, что выбрасывают первый пакет: syndata иsynack_split не проверялио них мы ничего не утверждаем настоящий ТСПУ другие получатели: Windows, BSD, телефоны,быстрое открытие код ядра: метки времени, пропавшееподтверждение причина 107, 72 и 70 IPv6 и фрагменты IP сеть за преобразованием адресов дальше: слои пакета, которых мы пока не касались IPv6 фрагменты IP HTTP QUIC UDP tls_mod следующий выпуск: «Другие слои»

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

См. также


🤖 Эти статьи открыты — можно обучать на них ИИ

При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.