todo/Zapret2/ts-and-fooling.md
loop-uh 08b4491545
Some checks failed
Published content check / validate (push) Failing after 3s
Завершить переезд базы знаний на Forgejo
Обновить правила репозитория и подписи исходников в 99 заметках, не затрагивая пользовательские незакоммиченные файлы. Сделать Forgejo Actions содержательным: проверять опубликованный commit, а не пустое рабочее дерево после checkout.
2026-08-07 08:27:28 +03:00

571 lines
52 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: 2026-06-20
tags:
- zapret
- zapret1
- zapret2
- winws
- winws2
- tcp
- fooling
- timestamp
- tcp-options
- paws
- rfc7323
aliases:
- ts fooling
- tcp_ts
- ts и fooling
- fooling
- почему фейк должен умереть
- как убить фейк
- TCP options
- PAWS
link: https://datatracker.ietf.org/doc/html/rfc7323
---
# 💀 `ts` и fooling: почему фейк должен умереть
> [!summary] Главная мысль
> Fake-пакет в [[desync|десинхронизации]] должен дойти до DPI, но **умереть** до того, как его примет настоящий сервер. Способ "правильно убить fake" называется fooling. Самый капризный из них — `ts`: он не добавляет TCP timestamps с нуля, а работает только если настоящий TCP-сеанс уже идёт с timestamp option. На Windows timestamps обычно выключены, поэтому `ts` как единственный TCP fooling там опасен — fake может перестать быть "плохим" и сломать соединение.
Эта заметка отвечает на три вопроса, которые чаще всего возникают про fake-пакеты: **зачем** они нужны, **почему** они обязаны умирать, и **как** заставить их умирать правильно на разных платформах. Параметр `ts` разобран как главный пример, потому что именно он ломается тише и обиднее всего.
Полный справочник параметров `--lua-desync` (включая `tcp_ts`, `tcp_md5`, `tcp_seq` и прочие) живёт в [[desync]] — здесь без дублирования, упор на смысл и подводные камни.
Связанные заметки: [[desync]], [[fake]], [[fakedsplit]], [[fakeddisorder]], [[основные флаги]], [[оркестратор]], [[windivert]].
---
## TL;DR
- **Fake обязан умереть до сервера.** Иначе сервер получит мусорный ClientHello/HTTP-запрос и нормальное соединение сломается.
- **fooling — это контролируемая порча fake**, чтобы серверный TCP/IP стек отбросил его, а DPI успел разобрать и сделать неверный вывод.
- **У каждого fooling свой "слой смерти"** (checksum, seq, ack, timestamp, TTL, IPv6-заголовки) и своё место, где он не проходит (NAT, роутер, сервер).
- **`ts` смещает `TSval`** настоящего timestamp назад, чтобы сервер счёл fake "слишком старым". Работает только если timestamps согласованы в начале TCP-сеанса.
- **Платформа решает всё.** Windows по умолчанию без timestamps; за домашним NAT отмирают `badsum`/`datanoack`; на самом роутере наоборот могут ожить. Один и тот же fooling бывает отличным в одном месте и бесполезным в другом.
---
## Зачем нужен [[fake]] и почему он обязан умереть
Начнём с того, что вообще делает fake, потому что без этого `ts` и fooling выглядят набором магических флагов.
Когда zapret отправляет fake, у этого пакета **две разные аудитории**, и им нужно прямо противоположное.
Для DPI fake должен выглядеть достаточно настоящим: DPI должен успеть разобрать его как TLS, HTTP, QUIC или другой payload и сделать неправильный вывод о соединении (например, увидеть в SNI безобидный сайт).
Для сервера этот же fake должен быть плохим: серверный TCP/IP стек должен отбросить его ещё до прикладного уровня. Тогда настоящий пакет придёт следом и будет обработан нормально, как будто никакого fake и не было.
> [!example] На пальцах: разговор под прослушкой
> Представь, что между тобой и собеседником (сервером) сидит прослушка (DPI). Ты хочешь, чтобы прослушка решила, будто разговор безобидный. Для этого ты вслух произносишь подставную фразу — её **должна** услышать прослушка (она ближе), но **не должен** услышать собеседник, иначе он переспросит "что?" и собьётся.
> Значит, подставную фразу надо сказать так, чтобы она "не долетела" до собеседника: либо слишком тихо (это TTL), либо на ломаном языке, который его телефон отбракует как помеху (это `badsum`, `badseq`, `ts`). Вот эта "управляемая порча" и есть **fooling** — способ заставить fake умереть, не дойдя до сервера.
> [!info] Формула fooling
> Fake должен быть **«достаточно настоящим»** для DPI и **«достаточно неправильным»** для сервера. fooling добавляет ровно ту неправильность, которая убивает fake на пути к серверу, но не мешает DPI его разобрать.
> [!danger] Что будет, если fake НЕ умрёт
> Самая неприятная поломка — это не "обход не сработал". Хуже, когда fake доходит до сервера живым: сервер примет его как реальную часть TCP-потока, поверх ляжет настоящий пакет, и нормальный TLS/HTTP-поток развалится. Поэтому "fake не дошёл до сервера" — это не побочный эффект, а **обязательное условие** работы стратегии.
---
## Что такое fooling
fooling — это и есть та самая контролируемая "неправильность", которой портят fake, чтобы он гарантированно умер на пути к серверу.
В zapret1 fooling задаётся **готовыми режимами**: `ts`, `md5sig`, `badseq`, `badsum`, `datanoack` и так далее через флаг `--dpi-desync-fooling=`.
В zapret2 то же самое разложено на **низкоуровневые параметры** Lua-функций: `tcp_ts=N`, `tcp_md5`, `tcp_seq=N`, `tcp_ack=N`, `badsum`, `tcp_flags_unset=ACK` и другие. Это даёт больше гибкости, но требует понимать, что именно делает каждый параметр — потому что "магического" поведения по умолчанию у них меньше (см. [[desync]] для полного списка).
---
## Что такое TCP options (и зачем fooling с ними играет)
Чтобы понять `ts`, `md5sig` и `tcp_ts_up`, надо сначала понять, **куда** они вообще пишут. У TCP-пакета есть две разные зоны, которые fooling может портить.
**Фиксированные поля** заголовка есть всегда: sequence number (порядковый номер байта в потоке), acknowledgment number (что подтверждено), флаги (`SYN`, `ACK`, `RST`…), checksum (контрольная сумма), window (размер окна). По ним бьют `badseq` (портит seq), `badack`/`tcp_ack` (портит ack), `badsum` (портит checksum), `datanoack` (снимает флаг ACK).
**TCP options** — это переменная часть в конце TCP-заголовка, куда складываются «дополнительные возможности», о которых стороны договариваются. Timestamp — одна из таких опций. По опциям бьют `ts`/`tcp_ts` (меняет timestamp-опцию), `md5sig`/`tcp_md5` (добавляет MD5-опцию), `tcp_ts_up` (переставляет timestamp-опцию вперёд), `tcp_nop_del` (удаляет опции-заполнители NOP).
> [!example] Как устроена одна TCP option
> Почти каждая опция — это три части подряд: **kind** (1 байт — что за опция), **length** (1 байт — полная длина опции вместе с этими двумя байтами) и **data** (содержимое). Исключение — две однобайтовые опции без length и data: End of Option List (kind 0) и No-Operation / NOP (kind 1).
> Пример timestamp-опции в байтах (kind 8, длина 10, дальше 4 байта `TSval` и 4 байта `TSecr`):
> ```text
> 08 0A | 11 22 33 44 | 00 00 00 00
> ▲ ▲ ▲ ▲
> │ │ │ └ TSecr (4 байта) — эхо timestamp той стороны
> │ │ └ TSval (4 байта) — timestamp отправителя
> │ └ length = 10 (2 байта заголовка + 8 байт данных)
> └ kind = 8 (Timestamps)
> ```
> На практике перед timestamp часто идут два NOP (`01 01`) — просто чтобы выровнять опцию по 4 байта. Поэтому порядок опций вообще можно «двигать» — это и делает `tcp_ts_up`.
Распространённые TCP options (номера kind — по реестру IANA):
| kind | Опция | Зачем нужна | Стандарт |
| --- | --- | --- | --- |
| 0 | End of Option List | Отметка конца списка опций | RFC 9293 |
| 1 | No-Operation (NOP) | Заполнитель для выравнивания | RFC 9293 |
| 2 | Maximum Segment Size (MSS) | Максимальный размер сегмента | RFC 9293 |
| 3 | Window Scale | Множитель размера окна | RFC 7323 |
| 4 | SACK Permitted | Согласие на выборочные подтверждения | RFC 2018 |
| 5 | SACK | Сами выборочные подтверждения | RFC 2018 |
| 8 | **Timestamps (`TSval`/`TSecr`)** | Замер RTT и защита от старых пакетов (PAWS) | RFC 7323 |
| 19 | MD5 Signature | Подпись сегмента (используется в BGP) | RFC 2385 |
| 29 | Authentication Option (AO) | Современная замена MD5 | RFC 5925 |
| 34 | TCP Fast Open | Данные уже в SYN | RFC 7413 |
> [!check] Исходники zapret = стандарт
> Номера kind в zapret заданы в `nfq2/darkmagic.h` (`TCP_KIND_TS=8`, `TCP_KIND_MD5=19`, `TCP_KIND_SCALE=3`, `TCP_KIND_MSS=2`, `TCP_KIND_SACK_PERM=4`, `TCP_KIND_NOOP=1`, `TCP_KIND_END=0`) и совпадают с реестром IANA и RFC. zapret1 в `nfq/darkmagic.c` использует те же числа напрямую (`tcp_find_option(tcp,8)` — поиск timestamp по kind 8). То есть это не «выдумка zapret», а ровно те значения, что лежат в спецификации.
> [!important] Главное свойство: возможность согласуется один раз — в начале
> Ключевой момент для fooling по опциям: **поддержка timestamp (как и window scale, SACK) согласуется один раз — в первом обмене (`SYN` и `SYN,ACK`), и её нельзя «включить» в середине соединения**. Если timestamp не предложили в SYN — дальше в этом сеансе его не будет. (Это не значит, что опции вообще не появляются по ходу: например, SACK-блоки и MD5-подпись идут в обычных сегментах. Но именно timestamp-сеанс задаётся на старте.) Поэтому нельзя просто «дописать timestamp в один пакет»: для сервера это не сделает сеанс timestamp-сеансом. Отсюда и растёт вся капризность `ts` — подробно ниже.
---
## Как заставить fake умереть: каталог способов
Универсального fooling нет. Каждый способ убивает fake **на своём слое** (контрольная сумма, sequence, ack, timestamp, TTL, IPv6-заголовки) и **ломается в своём месте** (NAT, роутер, конкретный сервер, конкретный DPI). Выбор зависит не от названия, а от среды.
| Способ (zapret1 / zapret2) | Как убивает fake | Где ломается |
| --- | --- | --- |
| `ts` / `tcp_ts=N` | Смещает `TSval` так, что сервер считает пакет слишком старым (PAWS) | Нужны согласованные TCP timestamps. На Windows по умолчанию выключены |
| `md5sig` / `tcp_md5` | Добавляет TCP MD5 option со случайными байтами — Linux-сервер обычно отбрасывает | Не универсально. Увеличивает TCP header → на больших TLS ClientHello (Kyber, малая split-позиция) возможен `message too long` (переполнение MTU) |
| `badsum` / `badsum` | Портит контрольную сумму — пакет битый для любого узла | NAT/conntrack/роутер часто отбрасывает fake раньше DPI. На самом роутере живёт лучше, чем за ним |
| `badseq` / `tcp_seq=N` | Уводит sequence за TCP-окно — сервер отбрасывает | Внимательный DPI тоже следит за окном и может не поверить fake. В zapret1 дефолт смещения `-10000` |
| (bad ack) / `tcp_ack=N` | Портит ACK при валидном seq — сервер отбрасывает | На Linux надёжно дропается **только** если timestamp option идёт первой → нужен `tcp_ts_up` (см. ниже) |
| `datanoack` / `tcp_flags_unset=ACK` | Снимает ACK с data-пакета — сервер такое не принимает | NAT с masquerade (типично на домашних роутерах) часто режет. Зависит от места запуска |
| `tcp_flags_set=SYN` | Делает невалидную комбинацию флагов — сервер не примет, DPI примет | Работает не на всех серверах; через NAT может отбрасываться |
| TTL / `ip_ttl=N`, `ip_autottl` | Fake "не долетает": умирает по дороге, не дойдя до сервера | Надо угадать число хопов до DPI. Маршрут меняется → fake умрёт рано или пробьётся до сервера. Стоковые прошивки роутеров фиксируют исходящий TTL |
| `hopbyhop` / `ip6_hopbyhop` | IPv6 extension header: DPI разберёт, сервер отбросит (или провайдер отфильтрует) | Только IPv6. `hopbyhop2` (два заголовка) дропается всеми ОС гарантированно, но может резаться сетью |
> [!tip] Как выбирать fooling
> Не по названию, а по **месту запуска, протоколу, NAT, серверу и pcap**. Один и тот же режим бывает отличным на роутере и бесполезным за роутером. Подбор делается тестами (`blockcheck`/pcap), а не на слух.
---
## `ts` подробно: что делает и почему капризный
`ts` стоит особняком, потому что ломается тише всех: он не падает с ошибкой, а просто молча перестаёт портить fake.
### Что именно делает `ts`
`ts` работает с **TCP timestamp option** (kind 8 — см. раздел про TCP options выше). Внутри неё два 32-битных значения: `TSval` (timestamp отправителя) и `TSecr` (timestamp второй стороны, который отправитель возвращает обратно). Для `ts` важен именно `TSval`.
Смысл техники: взять timestamp из настоящего пакета и **сместить `TSval` назад**. Это запускает на сервере механизм **PAWS** (Protection Against Wrapped Sequences — защита от «обернувшихся» порядковых номеров, RFC 7323): сервер отбрасывает сегменты со «слишком старым» timestamp. "Состаренный" fake умирает на сервере по PAWS, а DPI обычно не проверяет timestamp так строго и спокойно разбирает payload.
> [!quote] RFC 7323, правило PAWS (R1)
> «If there is a Timestamps option in the arriving segment, `SEG.TSval < TS.Recent`, TS.Recent is valid … then treat the arriving segment as not acceptable … and drop the segment.»
> Суть: если в пришедшем сегменте timestamp **меньше** последнего запомненного сервером (`TS.Recent`), сегмент считается старым и отбрасывается. Сдвиг `TSval` на `-600000` искусственно делает fake «старым» — и сервер дропает его по PAWS.
> [!note] Смещение — со знаком, и диапазон имеет значение
> Смещение `ts` может быть и отрицательным, и положительным, но на практике используют отрицательное ("назад"). По документации zapret1 рабочий инкремент — примерно **от `-100` до `-0x80000000`**. Поэтому и `-1000`, и `-600000` попадают в рабочий диапазон. В zapret1 дефолт `ts` — `-600000`.
> [!example] zapret1 и zapret2
> В zapret1 это выглядит так:
> ```bash
> --dpi-desync-fooling=ts
> --dpi-desync-ts-increment=-600000
> ```
>
> В zapret2 прямого режима `ts` нет — он пишется как параметр fooling:
> ```bash
> --lua-desync=fake:tcp_ts=-600000
> ```
> [!warning] В zapret2 нужно явное значение
> `tcp_ts` требует число. Нельзя просто написать `tcp_ts` и ждать "дефолта как в zapret1". При портировании стратегии из zapret1 смещение надо выбрать руками (часто `-600000`, чтобы повторить старое поведение).
### Почему `ts` не работает без согласованных timestamps
Это главный подвох. TCP timestamps — **не** произвольная добавка к любому пакету. Они согласуются в начале соединения.
Клиент должен предложить timestamp option в SYN. Сервер должен ответить timestamp option в SYN,ACK. Только после этого весь TCP-сеанс считается сеансом с timestamps, и у data-пакетов появляется timestamp option.
Если клиент не предложил timestamps или сервер их не поддержал, дальше соединение идёт **без них** — и портить `ts`-ом будет попросту нечего.
> [!quote] RFC 7323, согласование timestamp
> «A TCP MAY send the TSopt in an initial `<SYN>` segment … and MAY send a TSopt in `<SYN,ACK>` only if it received a TSopt in the initial `<SYN>` segment.» И далее: «Once TSopt has been successfully negotiated, that is both `<SYN>` and `<SYN,ACK>` contain TSopt, the TSopt MUST be sent in every non-`<RST>` segment for the duration of the connection.»
> Перевод сути: сервер вставит timestamp в ответ, **только** если увидел его в SYN клиента. Если согласовали — timestamp обязан быть в каждом дальнейшем пакете. Если не согласовали — его не будет нигде, и `ts` нечего портить. RFC 7323 обновляет более старый RFC 1323.
> [!danger] Главное ограничение `ts`
> Если в настоящем TCP-пакете нет timestamp option, `ts` не на чем работать. "Вставить timestamp в fake руками" недостаточно: TCP-сеанс не был согласован как timestamp-сеанс, и сервер не обязан применять к такому fake логику PAWS. Fake перестаёт быть "плохим" — и может дойти до сервера живым.
Поэтому zapret2 **не создаёт** timestamp option с нуля. В `lua/zapret-lib.lua` он ищет `TCP_KIND_TS`, проверяет длину данных (8 байт) и меняет `TSval`, только если option уже есть. Если её нет — в debug уходит сообщение:
```text
apply_fooling: timestamp tcp option not present or invalid
```
Это не ошибка запуска и не падение стратегии, а сигнал: к этому конкретному пакету `tcp_ts=N` по сути не применился.
> [!note] Как читать это сообщение
> Оно не говорит "Windows виновата". Оно сообщает только факт: в данном TCP-пакете не было валидной timestamp option. Причина может быть в Windows, в настройках клиента, в сервере или просто в том, что [[profile|Профиль]] применился не к тому пакету.
### `cond_tcp_has_ts` — не стрелять вхолостую
Вместо того чтобы вручную гадать, есть ли в пакете timestamp, zapret2 даёт встроенную проверку времени выполнения.
Функция `cond_tcp_has_ts(desync)` возвращает true, только если диссект — это TCP и в нём присутствует timestamp option (в `lua/zapret-auto.lua`). В документации (`docs/manual.md`) она же фигурирует под именем `cond_tcp_ts`.
> [!tip] Зачем это нужно
> Через [[оркестратор|условный оркестратор]] можно повесить `ts`-зависимый fake на условие `cond_tcp_has_ts` — тогда этот вариант стратегии сработает **только** на пакетах, реально несущих timestamp option, а не будет молча "пустеть" на всех остальных. Это честнее, чем надеяться на ручную проверку pcap, и полезно для стратегий "поставил и забыл".
---
## `tcp_ts_up` — это не `ts`
`tcp_ts_up` легко спутать с `tcp_ts`, но это разные вещи. `tcp_ts=N` меняет значение `TSval`. А `tcp_ts_up` только **переносит уже существующую** timestamp option в начало списка TCP options.
Автор zapret2 описывает это как странный, но практический workaround: Linux стабильно отбрасывает пакеты с валидным `seq` и плохим `ack` **только если timestamp option идёт первой**. Старый nfqws1 случайно всегда ставил timestamp первым, потому что не сохранял порядок TCP options — и потому "старое работало, а новое нет". `tcp_ts_up` повторяет старое поведение.
> [!note] Главное про `tcp_ts_up`
> Он не включает timestamps и не создаёт timestamp option. Если в пакете её нет — поднимать нечего.
Типичная zapret2-связка для HTTP:
```bash
--lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up
```
> [!info] Это порт "badseq без badseq" из zapret1
> Связка `tcp_ack=-66000:tcp_ts_up` — это документированный эквивалент zapret1-конструкции `--dpi-desync=fakedsplit --dpi-desync-fooling=badseq --dpi-desync-badseq-increment=0`, то есть **плохой ACK при валидном seq**. `tcp_ts_up` здесь нужен, чтобы воспроизвести старый порядок опций (timestamp первым), на котором Linux надёжно дропает такие сегменты.
> [!note] Куда именно вешается `tcp_ts_up` в split-функциях
> Плохой ACK (`tcp_ack`) применяется к **fake** — именно fake должен умереть на сервере. К **оригинальным** (настоящим) сегментам применяется только `tcp_ts_up` (плюс `ip_id`), их ACK остаётся валидным, поэтому они нормально доходят до сервера. Сам `tcp_ts_up` ставит timestamp option первой: без этого Linux ненадёжно отбрасывает пакет с валидным seq и плохим ACK.
---
## Отличие zapret1 и zapret2
В zapret1 `ts` — готовый режим fooling:
```bash
--dpi-desync-fooling=ts
--dpi-desync-ts-increment=-600000
```
Внутри код ищет TCP option kind `8`. Если timestamps есть — option копируется в fake, `TSval` смещается, `TSecr` остаётся без изменения. Если timestamps нет — option в fake не добавляется. То есть zapret1 тоже **не превращает** обычный TCP-сеанс в timestamp-сеанс.
В zapret2 готовое слово `ts` разложено на явные Lua-параметры:
```bash
--lua-desync=fake:tcp_ts=-600000
```
или, например, связка с плохим ACK:
```bash
--lua-desync=fakedsplit:tcp_ack=-66000:tcp_ts_up
```
> [!important] При портировании из zapret1
> zapret1 `ts` по умолчанию ближе к `tcp_ts=-600000`. А в `blockcheck2` стандартный тест использует `tcp_ts=-1000`. Оба смещения рабочие, но это **разные** значения, и нельзя молча считать их одним и тем же поведением.
---
## Как правильно атаковать сервер на разных платформах
Это самая практическая часть. Один и тот же [[profile|Профиль]] ведёт себя по-разному в зависимости от того, **где** запущен zapret и **чья ОС** открывает исходящий TCP-сеанс. Именно платформа определяет, какой fooling вообще имеет шанс сработать.
> [!example] Почему место запуска решает всё
> fooling убивает fake на пути к серверу. Но на этом пути стоят промежуточные узлы — NAT домашнего роутера, провайдерский NAT, conntrack. Они могут убить fake **раньше**, чем тот дойдёт до DPI (тогда обход не сработает), или, наоборот, пропустить то, что должно было умереть. Поэтому "рабочий" fooling — всегда привязан к конкретной точке запуска.
### Windows-клиент напрямую
TCP-сеанс открывает сама Windows, а она по умолчанию держит timestamps **выключенными**. Значит, `ts` "из коробки" работать не будет — fake не получит порчу и может дойти до сервера живым.
Проверка состояния:
```bat
netsh interface tcp show global
```
Включение:
```bat
netsh interface tcp set global timestamps=enabled
```
> [!warning] Три практических нюанса для Windows
> 1. Настройку надо включать на **каждом** клиентском устройстве — нельзя молча рассчитывать, что у всех пользователей [[preset|пресета]] timestamps уже включены.
> 2. После включения проверяй **новые** соединения. Уже открытый TCP-сеанс не станет timestamp-сеансом задним числом.
> 3. [[profile|Профиль]], который держится **только** на `ts`, без этой настройки — ненадёжный Windows-профиль.
### Linux-клиент напрямую
Linux обычно включает TCP timestamps по умолчанию. Поэтому `--dpi-desync-fooling=ts` на Linux часто работает сразу, без дополнительных настроек — это ровно та ситуация, на которую `ts` и рассчитан.
### За домашним роутером (через NAT)
Если трафик клиента проходит через NAT домашнего роутера (masquerade), часть fooling **умирает в NAT раньше DPI**:
- `badsum` — битую контрольную сумму роутер/conntrack часто режет на месте.
- `datanoack` / `tcp_flags_unset=ACK` — пакеты с битыми/снятыми флагами Linux-NAT с masquerade обычно не пропускает.
То, что зависит от заголовков самого TCP, а не от валидности пакета для NAT (`ts`, `badseq`, разумный `tcp_ack`, сегментация), переживает NAT лучше. Но финальное слово — за pcap.
### На самом роутере
Если zapret запущен **на роутере** (исходящий сеанс открывает роутер), картина меняется: `badsum` и `datanoack` отсюда могут работать, потому что fake уже не проходит через "свой" NAT по дороге наружу.
> [!warning] Transparent proxy (`tpws`, `squid`) — отдельный случай
> Пакетный фильтр (`nfqws`/`winws`) **не открывает** TCP-сеанс сам — его открывает конечный клиент, и timestamps согласует ОС клиента (Windows). А вот transparent proxy (`tpws`, `squid`) **терминирует** соединение клиента и открывает **новый** наружу — тогда timestamps для внешнего плеча согласует ОС роутера (Linux), и настройка Windows-клиента на это плечо уже не влияет. Поэтому при proxy проверять timestamps надо на устройстве, которое реально открывает соединение к серверу.
| Платформа / место | TCP timestamps | Что обычно живёт | Что чаще ломается |
| --- | --- | --- | --- |
| Windows-клиент напрямую | Выкл. по умолчанию (включается `netsh`) | После включения timestamps — `ts`; сегментация | `ts` без `netsh`; зависит от устройства |
| Linux-клиент напрямую | Вкл. по умолчанию | `ts` из коробки; большинство fooling | — |
| За домашним роутером (NAT/masquerade) | Зависит от клиента | `ts`, `badseq`, сегментация | `badsum`, `datanoack` (режет NAT) |
| На самом роутере | Зависит от ОС роутера | `badsum`, `datanoack`, TTL | TTL требует подбора хопа |
| Через transparent proxy (`tpws`/`squid`) | Зависит от **ОС роутера**, не клиента | fooling по ОС роутера (Linux) | настройка Windows-клиента на внешнее плечо не влияет |
---
## Почему `blockcheck` может вводить в заблуждение
`blockcheck.sh` (zapret1) и `blockcheck2.sh` (zapret2) на Windows **сами** пытаются включить timestamps через `netsh`. Это удобно для теста, но искажает интерпретацию результата.
Если blockcheck показал, что `ts` сработал — это может означать не "у пользователя всё было готово", а "blockcheck сначала включил системную настройку, а потом тестировал".
> [!tip] Как правильно читать результат
> Нашёл blockcheck рабочий `ts` — отдельно помни, что на Windows он мог предварительно выполнить `netsh interface tcp set global timestamps=enabled`. Для пользовательского [[preset|пресета]] эту настройку всё равно надо документировать.
В zapret1 blockcheck после успешного `ts` печатает предупреждение:
```text
WARNING ! although ts fooling worked it will not work without timestamps being enabled in the client OS. In windows timestamps are DISABLED by default.
```
Предупреждение есть **в blockcheck**, но обычный runtime `winws.exe` такую фразу при каждом запуске писать не обязан.
---
## Что видно в логах
В zapret1 `winws.exe` с `--dpi-desync-fooling=ts` может молча собрать fake без timestamp-порчи, если в исходном пакете timestamps нет. В коде сначала вызывается `tcp_find_timestamps()`; если option нет, указатель `timestamps` равен `NULL`, и `fill_tcphdr()` не добавляет timestamp option в fake. Никакого крика в лог.
В zapret2 чуть прозрачнее — Lua-функция пишет debug-сообщение:
```text
apply_fooling: timestamp tcp option not present or invalid
```
Но это именно debug: без `--debug` обычный `DLOG` не виден.
> [!example] Debug в zapret2
> На консоль:
> ```bash
> winws2 --debug ...
> ```
> В файл:
> ```bash
> winws2 --debug=@ts-debug.log ...
> ```
> [!warning] Отсутствие строки не доказывает успех
> Нет строки про timestamp — это не значит, что `ts` сработал. Возможно, debug не включён, пакет не попал в нужный [[profile|Профиль]] или Lua-функция не вызывалась на этом payload.
---
## Как проверять `ts` правильно
Сначала — настройка Windows:
```bat
netsh interface tcp show global
```
Если timestamps выключены:
```bat
netsh interface tcp set global timestamps=enabled
```
После этого проверяй **новые** соединения.
Лучший способ проверки — pcap. Смотреть надо не только fake, а **весь TCP handshake**, потому что timestamps согласуются именно там.
> [!check] Признаки, что `ts` действительно применим
> - В SYN от клиента есть TCP timestamp option.
> - В SYN,ACK от сервера тоже есть timestamp option.
> - В data-пакете, к которому применяется `fake`/`fakedsplit`, timestamp option на месте.
> - В fake виден смещённый `TSval`, и сервер не отвечает на fake как на валидный payload.
Если timestamp option нет уже в SYN — дальше искать `ts` бессмысленно: такой TCP-сеанс не timestamp-сеанс.
---
## Как писать это в [[preset|пресетах]] и UI
Если в пресете есть TCP-[[profile|Профиль]], который держится только на `ts`, для Windows рядом нужен явный текст. Хорошая формулировка:
> [!warning] Windows и `ts`
> Для `ts`-fooling на Windows нужны включённые TCP timestamps:
> ```bat
> netsh interface tcp set global timestamps=enabled
> ```
> Без этого TCP-фейки могут доходить до сервера и ломать соединение.
Если нужен пресет "поставил и забыл" — не держи весь TCP только на `ts`. Тестируй альтернативы и комбинации: `md5sig`, `badseq`, `datanoack`, TTL/autottl, `seqovl`. Либо гейти `ts`-вариант через `cond_tcp_has_ts`, чтобы он не работал вхолостую. Но каждую комбинацию проверяй pcap/debug, а не выбирай на ощущение.
---
## Где здесь `seqovl`
`seqovl` лучше не воспринимать как ещё один обычный fooling — это другой приём.
В обычном fake мы стараемся, чтобы сервер **полностью отбросил** поддельный сегмент. В `seqovl` идея тоньше: сервер принимает только нужную часть потока из-за **пересечения TCP sequence numbers** (подробный разбор `seqovl` — в [[multisplit]]).
Поэтому `seqovl` интересен там, где header-fooling плохо проходит или слишком зависит от NAT. Но это не "автоматическая замена", а отдельная логика, требующая аккуратной настройки позиции и понимания payload.
---
## 🎣 Готовые обманки: как fooling собирают на практике
Вся теория выше — про то, **почему** fake умирает. А вот как этих «убийц fake» совмещают в реальных стратегиях. Примеры взяты из официального `zapret2/docs/readme.md` — это типовые комбинации, показанные автором как «примерно так строятся стратегии».
> [!warning] Это примеры, а не магия
> Показаны только desync-части (без обвязки `--filter`/`--qnum`). Что именно сработает у тебя — зависит от конкретного DPI и провайдера, поэтому любую комбинацию гоняй через `blockcheck2` и проверяй pcap, а не копируй вслепую. Ценность ниже — увидеть, **как** складывают fooling и **что в каждой обманке убивает fake**.
**1. TLS-фейк, который сервер отбросит по MD5-опции** *(readme.md:107)*
```bash
--payload=tls_client_hello \
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid
```
DPI видит поддельный ClientHello (со случайным SNI) и решает «сайт безобидный». Сервер дропает fake из-за лишней TCP MD5-опции (`tcp_md5`). Настоящий ClientHello проходит следом.
**2. HTTP-фейк + MD5** *(readme.md:108)*
```bash
--payload=http_req --lua-desync=fake:blob=fake_default_http:tcp_md5
```
То же для HTTP: подставной запрос отравляет DPI, MD5-опция убивает fake на сервере.
**3. Сегментация + `seqovl`, вообще без порчи заголовков** *(readme.md:109)*
```bash
--payload=tls_client_hello,http_req \
--lua-desync=multisplit:pos=1:seqovl=5:seqovl_pattern=0x1603030000
```
Здесь отдельный fake не нужен: ClientHello режется, а перекрытие sequence (`seqovl`) подсовывает DPI ложное начало потока. Хорошо там, где header-fooling не проходит через NAT (см. [[multisplit]]).
**4. Fake, который «не долетает» до сервера по TTL** *(readme.md:155)*
```bash
--lua-desync=fake:blob=0x00000000:ip_ttl=5:ip6_ttl=3 \
--lua-desync=multidisorder:pos=5,endhost-1
```
fake с маленьким TTL умирает по дороге, не дойдя до сервера, — а DPI его уже увидел. Следом идёт реальная сегментация с обратным порядком.
**5. Плохой ACK + timestamp сверху (= zapret1 `badseq` с `increment=0`)** *(readme.md:237)*
```bash
--payload=http_req \
--lua-desync=fakedsplit:pos=method+2:tcp_ack=-66000:tcp_ts_up
```
Наша главная связка из разбора `tcp_ts_up`: fake с валидным seq и плохим ACK сервер надёжно отбрасывает, потому что timestamp-опция поднята вперёд.
**6. Автоподбор TTL + MD5 как страховка** *(readme.md:355)*
```bash
--lua-desync=fake:blob=fake_default_http:ip_autottl=-2,3-20:ip6_autottl=-2,3-20:tcp_md5 \
--lua-desync=fakedsplit:ip_autottl=-2,3-20:ip6_autottl=-2,3-20:tcp_md5
```
`ip_autottl` сам подбирает TTL по hop-расстоянию, а `tcp_md5` — подстраховка: если fake всё-таки долетит до сервера, тот отбросит его по MD5-опции.
**7. Fake + MD5 + сдвиг seq + повторы** *(readme.md:367)*
```bash
--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000:repeats=6 \
--lua-desync=multidisorder:pos=midsld
```
Несколько «убийц fake» сразу (MD5-опция + увод `seq` за окно), а `repeats=6` шлёт fake шесть раз, чтобы наверняка попасть в окно анализа DPI.
> [!tip] «Обманка на `ts`» для Windows — отдельно
> Самостоятельная `ts`-обманка вроде `--lua-desync=fake:tcp_ts=-600000` имеет смысл только при включённых TCP timestamps (Linux — да, Windows — после `netsh`). Чтобы она не работала вхолостую, повесь её на условие `cond_tcp_has_ts` (см. [[оркестратор]]) или держи запасной вариант на `md5sig`/TTL.
---
## Частые ошибки
> [!failure] "В fake можно просто добавить timestamp option"
> Нет. Timestamp option согласуется в начале TCP-сеанса. Если сеанс без timestamps, добавленная руками option не делает его timestamp-сеансом — и сервер не отбросит fake по PAWS.
> [!failure] "Если в логах нет warning, значит всё хорошо"
> Нет. В zapret1 runtime может молчать. В zapret2 сообщение про отсутствие timestamp идёт через `DLOG`, то есть видно только с `--debug`.
> [!failure] "blockcheck показал `ts`, значит у пользователей Windows всё готово"
> Нет. blockcheck на Windows сам пытается включить timestamps через `netsh`.
> [!failure] "`tcp_ts_up` включит timestamps"
> Нет. `tcp_ts_up` только переставляет **уже существующую** timestamp option в начало списка TCP options.
> [!failure] "`ts` самый безопасный fooling"
> Он аккуратный, когда timestamps есть. Если timestamps нет — fake перестаёт быть плохим для сервера и может его сломать.
---
## Мини-шпаргалка
```text
zapret1:
--dpi-desync-fooling=ts
--dpi-desync-ts-increment=-600000 (диапазон ~ -100 .. -0x80000000)
zapret2:
--lua-desync=fake:tcp_ts=-600000 (значение обязательно)
--lua-desync=fakedsplit:tcp_ack=-66000:tcp_ts_up (= badseq increment=0)
Проверить Windows:
netsh interface tcp show global
Включить Windows timestamps:
netsh interface tcp set global timestamps=enabled
zapret2 debug-признак, что ts не применился:
apply_fooling: timestamp tcp option not present or invalid
Гейт "только если есть timestamp" (zapret2):
cond_tcp_has_ts (в мануале: cond_tcp_ts)
```
---
## Источники в коде
> [!note] Проверено по локальным исходникам
> Заметка не основана на догадках про DPI. Основные утверждения сверены с локальными исходниками zapret1 и zapret2.
**zapret1:**
- `docs/readme.md``ts` прибавляет к `TSval` инкремент (дефолт `-600000`, рабочий диапазон `-100 .. -0x80000000`); сервер отбрасывает пакеты с `TSval` в определённых пределах; Windows timestamps выключены по умолчанию (`netsh ... timestamps=enabled`); там же слабые места `md5sig`, `badsum`, `badseq` (дефолт `-10000`), TTL, `datanoack` (NAT/masquerade).
- `nfq/darkmagic.c``tcp_find_timestamps()` ищет TCP option kind `8`; `fill_tcphdr()` добавляет timestamp option только если `timestamps != NULL`; при `FOOL_TS` меняется `TSval`, `TSecr` копируется без изменения.
- `blockcheck.sh` — на Windows включает timestamps через `netsh`; после успешного `ts` печатает warning про Windows.
**zapret2:**
- `lua/zapret-lib.lua``tcp_ts=N` ищет существующий `TCP_KIND_TS` (длина данных 8 байт); если option нет, пишет `apply_fooling: timestamp tcp option not present or invalid`; `tcp_ts_up` только двигает существующую option в начало; `tcp_md5` по умолчанию добавляет 16 случайных байт.
- `lua/zapret-antidpi.lua` — в `fakedsplit`/`fakeddisorder`/`hostfakesplit` к оригинальным сегментам применяется только `tcp_ts_up` (+ `ip_id`), остальной fooling — на fake.
- `lua/zapret-auto.lua``cond_tcp_has_ts(desync)` возвращает true, если диссект TCP и присутствует timestamp option.
- `docs/readme.md` — связка `tcp_ack=-66000:tcp_ts_up` — эквивалент zapret1 `--dpi-desync-fooling=badseq --dpi-desync-badseq-increment=0`; объяснение, почему Linux дропает плохой ack только при timestamp-опции сверху.
- `docs/manual.md``tcp_ts` работает только при наличии timestamp option; `--debug` включает `DLOG`; conditional описан как `cond_tcp_ts`.
- `blockcheck2.sh` — на Windows тоже включает TCP timestamps через `netsh`; стандартный тест использует `tcp_ts=-1000`.
- `nfq2/darkmagic.h` — числовые kind TCP options (`TCP_KIND_TS=8`, `TCP_KIND_MD5=19`, `TCP_KIND_SCALE=3`, `TCP_KIND_MSS=2`, `TCP_KIND_SACK_PERM=4`, `TCP_KIND_NOOP=1`, `TCP_KIND_END=0`) — совпадают с IANA/RFC.
**Стандарты:**
- [RFC 7323](https://datatracker.ietf.org/doc/html/rfc7323) (обновляет [RFC 1323](https://datatracker.ietf.org/doc/html/rfc1323)) — формат TSopt (Kind 8, Length 10, `TSval`/`TSecr` по 4 байта), правило согласования (TSopt в `SYN` и `SYN,ACK`) и механизм PAWS (отбрасывание сегмента при `SEG.TSval < TS.Recent`).
- [IANA TCP Option Kind Numbers](https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml#tcp-parameters-1) — официальный реестр номеров kind; значения в `darkmagic.h` совпадают с ним.
---
## 📚 См. также
- [[desync]] — общий справочник по `--lua-desync` и полный список fooling-параметров (`tcp_ts`, `tcp_md5`, `tcp_seq`, `ip_ttl`…)
- [[fake]] — что такое fake-пакет и как он отравляет состояние DPI
- [[fakedsplit]], [[fakeddisorder]] — стратегии, где fake совмещён с сегментацией (там и применяется `tcp_ack`/`tcp_ts_up`)
- [[оркестратор]] — условная логика, через которую вешается `cond_tcp_has_ts`
- [[multisplit]] — разбор `seqovl` (скрытый фейк внутри сегмента) и сегментации
- [[основные флаги]] — где fooling стоит в общей картине аргументов
- 🔗 [RFC 7323](https://datatracker.ietf.org/doc/html/rfc7323) — TCP Timestamps, согласование и PAWS (обновляет RFC 1323)
- 🔗 [Официальный manual zapret2](https://github.com/bol-van/zapret2/blob/master/docs/manual.md) — первоисточник по параметрам
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/ts-and-fooling.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).