---
date: 2026-02-17
tags:
  - zapret
  - zapret2
  - nfqws2
  - lua
  - lua-desync
  - antidpi
  - tcp
  - hostfakesplit
  - fake
  - hostname
aliases:
  - hostfakesplit
description: "Функция hostfakesplit в zapret2/nfqws2: разрез TCP-сегментов по границам hostname с фейковыми SNI, аргументы host, midhost, disorder_after, примеры."
---

> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki2.zapret.moe/Zapret2/desync/hostfakesplit](https://wiki2.zapret.moe/Zapret2/desync/hostfakesplit)

# `hostfakesplit` — TCP-сегментация с фейковым hostname (zapret2 / nfqws2)

## Коротко и по-простому

Когда браузер открывает сайт по HTTPS, первым сообщением с данными он отправляет TLS ClientHello («привет от клиента»). В нём браузер перечисляет, какое шифрование умеет, и открытым текстом называет нужный сайт: имя лежит в поле SNI (Server Name Indication, «указание имени сервера»). У обычного HTTP без шифрования то же имя стоит в строке `Host:` запроса. ТСПУ (Технические средства противодействия угрозам — оборудование [[DPI/DPI|DPI]], то есть глубокой инспекции трафика, которое стоит у провайдера) ищет это имя в первом пакете соединения и по нему решает, что делать с соединением дальше.

Имя сайта занимает в запросе небольшой участок: перед ним идут служебные данные запроса, после него — остальное. В HTTP-запросе имя стоит после `Host:`, в ClientHello лежит внутри поля SNI. Границы имени `hostfakesplit` находит сама, по названиям-маркерам. Маркер — это название места в данных, которое движок вычисляет по устройству запроса: `host` — первый байт имени, `endhost` — байт сразу за его последним байтом. Так это выглядит в HTTP (метки под строкой показывают границы имени):

```
GET /watch HTTP/1.1\r\nHost: www.youtube.com\r\n...
                             ^host          ^endhost
```

Эти маркеры работают, только если движок распознал запрос как HTTP или как TLS ClientHello; общий список маркеров — в разделе [[Zapret2/desync/multisplit#Относительные маркеры|Относительные маркеры]] статьи про `multisplit`, а как `hostfakesplit` их использует — в разделе [[#Основные точки разреза: host и endhost|Основные точки разреза]].

HTTP-запросы и TLS-соединения передаются по протоколу TCP, а TCP отправляет данные кусками — сегментами. У каждого байта в соединении есть порядковый номер (sequence number, коротко seq), и сервер раскладывает принятые куски в свой буфер строго по этим номерам. Поэтому ему безразлично, пришли данные одним пакетом или несколькими и в каком порядке куски доехали: исходный поток он всё равно соберёт. Почему это гарантирует именно TCP, разобрано в нюансе [[Zapret2/desync/multisplit#1. Работает только с TCP — и почему|«Работает только с TCP»]] статьи про `multisplit`.

Ещё одно понятие: фейк. Это сегмент с ложными данными, здесь — с подложным именем сайта. Чтобы сервер не принял фейк за настоящие данные и не сломал соединение, фейк «портят» так, чтобы сервер его отбросил, а DPI мог прочитать: такая порча называется fooling ([[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]). В команде из быстрого старта это `tcp_md5`: к сегменту добавляется TCP-опция с подписью MD5 (16 байт, по умолчанию случайные), и Linux-сервер обычно такой сегмент отбрасывает. Способ не универсальный, другие собраны в [[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталоге способов]]. Без fooling сервер примет фейки и получит два противоречивых имени на одном месте потока, поэтому для `hostfakesplit` он обязателен.

Теперь сама техника, по шагам. `hostfakesplit` перехватывает исходящий ClientHello или HTTP-запрос и находит в нём границы имени. Затем генерирует фейковое имя той же длины, что и настоящее: функция `genhost` собирает случайный домен вроде `u9a7bk2.org`, а если подсказать шаблон (`host=vk.com`), получится поддомен вида `e8nzn.vk.com`. После этого функция отправляет пять сегментов (это базовый вариант, без `midhost` и `disorder_after`): данные до имени, фейковое имя, настоящее имя, ещё раз то же фейковое имя и данные после имени. Фейки уходят с fooling и с тем же seq, что и настоящее имя; настоящим сегментам fooling не достаётся (исключение — опция `tcp_ts_up`). В конце функция выбрасывает перехваченный оригинал (если не задан `nodrop`), чтобы те же данные не ушли второй раз. Если границы имени найти нельзя (например, это не HTTP и не ClientHello), она ничего не делает.

Если fooling сработал, сервер отбрасывает оба фейка, принимает настоящие сегменты и собирает ровно тот запрос, который отправил браузер. DPI на месте имени видит три кандидата с одним и тем же seq и одной и той же длиной: фейк, настоящее имя, фейк. Длина у всех трёх одинаковая, поэтому по размеру настоящее имя не отличить, и DPI приходится выбирать, какое из имён «правильное». Он может принять за настоящее одно из фейковых имён; если он выберет фейк, обход удался. Что видит каждая сторона, показано в разделе [[#Порядок отправки сегментов|Порядок отправки сегментов]].

Команда из быстрого старта `hostfakesplit:tcp_md5` находит в ClientHello границы имени сайта (маркеры `host` и `endhost`) и отправляет пять сегментов: данные до имени, фейковое имя, настоящее имя `youtube.com`, ещё раз фейковое имя и данные после имени. Фейковое имя (на схеме `u9a7bk2.org`) той же длины и ложится на тот же seq, поэтому на месте имени ТСПУ видит три кандидата; сервер отбрасывает фейки по неверной подписи TCP MD5. Как читать схему: строки — пакеты в порядке отправки, подписанные в тех же обозначениях, что и таблицы в этой статье; по горизонтали — место данных в TCP-потоке (номер последовательности, seq); в колонке справа — что сделал с пакетом сервер; внизу — буфер сервера, в который принятые куски встают по своим seq.

![[desync-anim-hostfakesplit.svg|Анимация hostfakesplit: имя youtube.com уходит между двумя фейковыми именами той же длины и с тем же seq]]

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
```

Строка `--payload=tls_client_hello` значит «срабатывать только на TLS ClientHello», а `--lua-desync=hostfakesplit:tcp_md5` включает функцию с fooling `tcp_md5`. Обе строки пишут в [[Zapret2/profile#Анатомия профиля|профиль]] — набор правил Zapret 2 для определённого трафика. Остальные готовые команды собраны в разделах [[#Быстрый старт|Быстрый старт]] и [[#Практические примеры|Практические примеры]]. К этой схеме можно добавить несколько опций. Аргумент `midhost=` режет настоящее имя ещё на две части, например посередине домена второго уровня (`midhost=midsld`; у `www.youtube.com` домен второго уровня — `youtube`): вместо одного настоящего сегмента с именем уйдут два ([[#midhost — разрез внутри hostname|подробнее]]). Аргумент `disorder_after` режет данные после имени ещё на две части и отправляет их в обратном порядке ([[#disorder_after — обратный порядок хвоста|подробнее]]). Флаги `nofake1` и `nofake2` отключают первый или второй фейк, а `repeats=N` отправляет каждый фейк N раз (настоящие сегменты — по одному разу). Все аргументы разобраны в разделе [[#Полный список аргументов|Полный список аргументов]].

Чем `hostfakesplit` отличается от соседей. [[Zapret2/desync/multisplit#Зачем нужен multisplit|multisplit]] только режет настоящие данные и фейков не шлёт; места разреза там задаёт аргумент `pos`, а у `hostfakesplit` `pos` нет: границы всегда стоят по имени. [[Zapret2/desync/fakedsplit#Зачем нужен fakedsplit|fakedsplit]] режет по одной произвольной позиции и заливает фейки паттерном (нулями и т.п.), а `hostfakesplit` знает, где стоит имя, и подмешивает осмысленное фейковое имя той же длины. [[Zapret2/desync/fakeddisorder#Зачем нужен fakeddisorder|fakeddisorder]] и [[Zapret2/desync/multidisorder#Зачем нужен multidisorder|multidisorder]] отправляют куски в обратном порядке; у `hostfakesplit` обратный порядок необязательный и касается только данных после имени. Все отличия сведены в таблицу в разделе [[#Отличия от других функций сегментации|Отличия от других функций сегментации]].

Когда техника не сработает. Функция понимает только HTTP-запрос и TLS ClientHello: для QUIC (это не TCP) и других протоколов границ имени не найти, и она тихо ничего не делает. Приёма `seqovl` (скрытого фейка внутри сегмента, [[Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента|разбор]]) у неё нет. Обход удаётся, только если DPI выберет из трёх кандидатов фейк, а не настоящее имя, поэтому рабочую комбинацию для своего провайдера подбирают опытным путём и проверяют по [[Zapret2/verify-strategy#Короткий чек-лист честной проверки|чек-листу честной проверки]]. В боевых примерах `hostfakesplit` ставят после [[Zapret2/desync/fake#Зачем нужен fake|fake]] — отдельного пакета-обманки с ложным ClientHello; см. примеры [[#12. Комбинация: fake → hostfakesplit (два инстанса)|12]] и [[#13. Боевой пример для YouTube (TLS)|13]].

---

## Оглавление

- [Коротко и по-простому](#коротко-и-по-простому)
- [Справка: где функция в коде и родственники](#справка-где-функция-в-коде-и-родственники)
- [Зачем нужен hostfakesplit](#зачем-нужен-hostfakesplit)
- [Быстрый старт](#быстрый-старт)
- [Откуда берутся данные](#откуда-берутся-данные)
- [Основные точки разреза: host и endhost](#основные-точки-разреза-host-и-endhost)
- [Генерация фейкового hostname (genhost)](#генерация-фейкового-hostname-genhost)
- [Дополнительные точки разреза](#дополнительные-точки-разреза)
  - [midhost — разрез внутри hostname](#midhost--разрез-внутри-hostname)
  - [disorder_after — обратный порядок хвоста](#disorder_after--обратный-порядок-хвоста)
- [Полный список аргументов](#полный-список-аргументов)
  - [A) Собственные аргументы hostfakesplit](#a-собственные-аргументы-hostfakesplit)
  - [B) Standard direction](#b-standard-direction)
  - [C) Standard payload](#c-standard-payload)
  - [D) Standard fooling](#d-standard-fooling)
  - [E) Standard ipid](#e-standard-ipid)
  - [F) Standard reconstruct](#f-standard-reconstruct)
  - [G) Standard rawsend](#g-standard-rawsend)
- [Порядок отправки сегментов](#порядок-отправки-сегментов)
- [Разделение opts: оригиналы vs фейки](#разделение-opts-оригиналы-vs-фейки)
- [Поведение при replay / reasm](#поведение-при-replay--reasm)
- [Место в общем скелете](#место-в-общем-скелете)
- [Псевдокод алгоритма](#псевдокод-алгоритма)
- [Нюансы и подводные камни](#нюансы-и-подводные-камни)
- [Отличия от других функций сегментации](#отличия-от-других-функций-сегментации)
- [Миграция с nfqws1](#миграция-с-nfqws1)
- [Практические примеры](#практические-примеры)
- [📚 См. также](#-см-также)

---

## Справка: где функция в коде и родственники

Паспорт функции для тех, кто сверяется с исходниками zapret2: в каком файле и на какой строке лежит код, какой ключ первой версии zapret она заменяет и как объявлена.

**Файл:** `lua/zapret-antidpi.lua:707`
**nfqws1 эквивалент:** `--dpi-desync=hostfakesplit`
**Сигнатура:** `function hostfakesplit(ctx, desync)`

`hostfakesplit` — специализированная функция TCP-сегментации с замешиванием фейковых hostname-сегментов. Она предназначена **исключительно** для payload, содержащих имя хоста (`http_req` и `tls_client_hello`). Функция автоматически определяет границы hostname в payload, генерирует фейковый hostname той же длины (через `genhost`), и отправляет последовательность из реальных и фейковых TCP-сегментов. После успешной отправки выносит `VERDICT_DROP`, чтобы оригинальный пакет не ушёл.

> [!note] Проще говоря
> Payload — полезные данные пакета без сетевых заголовков: сам HTTP-запрос или сам ClientHello. `http_req` и `tls_client_hello` — [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|типы payload]], то есть названия распознанных протоколов. Hostname — имя сайта, то самое, которое ищет DPI. `genhost` — функция, которая генерирует фейковое имя нужной длины ([[#Генерация фейкового hostname (genhost)|подробно]]). `VERDICT_DROP` — решение «перехваченный пакет не отправлять»; как движок складывает такие решения от разных функций, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадии 8 жизненного цикла]].

Родственные функции: [[multisplit]] (базовая сегментация), [[multidisorder]] (обратный порядок), [[fakedsplit]] (фейки по произвольной позиции), [[fakeddisorder]] (фейки + обратный порядок), [[tcpseg]] (диапазон), [[oob]] (urgent byte).

Общий для всех техник дурения порядок работы — восемь стадий от отсева чужого транспорта до вердикта — разобран в [[жизненный цикл desync-функции]]; здесь описано только то, чем `hostfakesplit` от этого скелета отличается.

---

## Зачем нужен hostfakesplit

DPI ищет hostname в TCP-потоке: заголовок `Host:` в HTTP, поле SNI в TLS ClientHello. Если DPI работает попакетно (не реассемблирует TCP), достаточно разрезать payload по границам hostname. Но продвинутые DPI умеют реассемблировать поток и собрать hostname из нескольких сегментов.

`hostfakesplit` решает обе проблемы одновременно:

1. **Разрез по границам hostname:** payload разрезается точно по маркерам `host` и `endhost`, так что ни один сегмент не содержит полный hostname в "чистом" виде
2. **Замешивание фейков:** между реальными сегментами вставляются фейковые TCP-сегменты с тем же TCP sequence, но с **другим** hostname (сгенерированным `genhost`). DPI, пытающийся реассемблировать поток, может принять фейковый hostname за настоящий
3. **Fooling на фейках:** фейковые сегменты отправляются с fooling-опциями (TTL, md5sig, badseq и т.д.), поэтому сервер их отбрасывает, а DPI — нет

Сервер корректно собирает поток: фейки отбрасываются благодаря fooling, реальные сегменты доставляются через стандартный TCP-механизм.

> [!note] Проще говоря
> DPI ищет имя сайта в пакетах. Самый простой DPI разбирает каждый пакет отдельно и не склеивает их обратно в поток (склейка называется реассемблированием, от английского reassembly), поэтому его сбивает обычный разрез. Продвинутый DPI поток склеивает и может собрать имя из нескольких сегментов. Против него `hostfakesplit` подмешивает к настоящим сегментам фейковые с ложным именем: на одном месте потока лежат разные имена, и DPI приходится выбирать. Fooling здесь — способ испортить фейки так, чтобы сервер их выбросил; подробно — [[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]], короткие описания способов — [[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталог способов]].

**Ключевое отличие от [[fakedsplit]]:** `hostfakesplit` автоматически знает где hostname, генерирует осмысленный фейковый hostname той же длины и имеет опцию `midhost` для дополнительного разреза внутри hostname. [[fakedsplit]] работает по произвольной позиции и заливает фейк паттерном (0x00 и т.п.).

Как отправляет пакеты `fakedsplit` и что при этом видит DPI, показано в разделе [[Zapret2/desync/fakedsplit#Последовательность отправки (6 пакетов)|Последовательность отправки (6 пакетов)]]. Про обычный разрез без фейков — [[Zapret2/desync/multisplit#Зачем нужен multisplit|Зачем нужен multisplit]]. Сводная таблица отличий — в разделе [[#Отличия от других функций сегментации|Отличия от других функций сегментации]].

---

## Быстрый старт

Ниже четыре готовые строки для [[Zapret2/profile#Анатомия профиля|профиля]] Zapret 2, от самой короткой к более сложным. Флаг `--payload=…` работает как фильтр для всех вызовов `--lua-desync`, которые идут после него: они срабатывают только на данных указанного типа. Поэтому `--payload` пишут перед `--lua-desync` (подробно — в [[последовательность аргументов]]). Опция `tcp_md5` в каждой строке — fooling для фейков ([[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]); без него функция вредна, см. нюанс [[#2. Fooling обязателен|«Fooling обязателен»]].

Минимально (fooling обязателен для фейков!):

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
```

Функция срабатывает только на TLS ClientHello, режет его по границам имени сайта и вставляет два фейка с fooling `tcp_md5`.

С шаблоном для фейкового hostname:

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:host=google.com
```

Шаблон `host=google.com` подсказывает, на что должно быть похоже фейковое имя: например, `k7z2a.google.com`. Длина фейка по-прежнему равна длине настоящего имени ([[#Генерация фейкового hostname (genhost)|Генерация фейкового hostname]]).

С дополнительным разрезом посередине hostname:

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:midhost=midsld
```

Настоящее имя режется посередине домена второго уровня и уходит двумя сегментами ([[#midhost — разрез внутри hostname|midhost]]).

С disorder хвостовой части:

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after
```

Данные после имени режутся ещё на две части и уходят в обратном порядке: сначала последний байт, потом остальное ([[#disorder_after — обратный порядок хвоста|disorder_after]]).

---

## Откуда берутся данные

Внутри `hostfakesplit` данные (`data`) выбираются в следующем порядке приоритетов:

```
1. blob_or_def(desync, desync.arg.blob)    — если задан blob= и он существует
2. desync.reasm_data                        — если есть реассемблированные данные
3. desync.dis.payload                       — текущий пакет (fallback)
```

**Следствие:** маркеры `host`, `endhost`, `midsld` и прочие работают по тем данным, которые реально выбраны. Если задан `blob=myblob`, маркеры разрешатся только если `myblob` содержит валидный TLS ClientHello или HTTP-запрос, который zapret может распознать.

> [!note] Проще говоря
> Функция сначала спрашивает: «мне явно подсунули blob (заготовленные данные вместо настоящих, см. [[blob]])? — беру его». Если нет: «движок собрал запрос из нескольких пакетов (reasm)? — беру собранное». Если и этого нет: «беру тело текущего пакета». Маркеры `host` и `endhost` ищут имя в тех данных, которые выиграли этот выбор. Та же цепочка общая для большинства функций дурения: [[Zapret2/жизненный цикл desync-функции#Стадия 4. Выбор данных: blob → reasm → payload|стадия 4 жизненного цикла]]; на примере `multisplit` она разобрана в разделе [[Zapret2/desync/multisplit#Откуда берутся данные для нарезки|Откуда берутся данные для нарезки]].

---

## Основные точки разреза: host и endhost

`hostfakesplit` **не принимает** произвольный `pos=`. Вместо этого две основные точки разреза определяются автоматически через вызов:

```lua
local pos = resolve_range(data, desync.l7payload, "host,endhost-1", true)
```

Это разрешает два маркера с `strict=true`:
- `pos[1]` = **host** — первый байт имени хоста (начало `Host:` значения в HTTP, начало SNI в TLS)
- `pos[2]` = **endhost-1** — последний байт имени хоста (байт перед `endhost`)

Если хотя бы один маркер не разрешается (например, payload = `unknown` или `quic_initial`), функция логирует "host range cannot be resolved" и ничего не делает.

**Таким образом:** `hostfakesplit` работает **только** с `http_req` и `tls_client_hello`.

Разрешить маркер — значит превратить его название в конкретный номер байта в выбранных данных. Вот как выглядят разрешённые позиции на HTTP-запросе:

```
GET /watch HTTP/1.1\r\nHost: www.youtube.com\r\n...
                             ^host          ^endhost
```

`pos[1]` и `pos[2]` — первая и вторая позиции из результата вызова: начало имени и его последний байт. Запись `endhost-1` — это [[Zapret2/desync/multisplit#Арифметика маркеров|арифметика маркеров]]: «на один байт левее `endhost`». Какие типы бывают у payload и почему `unknown` или `quic_initial` не подходят, разобрано в заметке про [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|тип payload]]; все маркеры — в разделе «[[Zapret2/desync/multisplit#Относительные маркеры|относительные маркеры]]».

> [!note] Проще говоря
> Функция сама находит начало и конец имени сайта в запросе. Если запрос не похож ни на HTTP, ни на TLS ClientHello, хотя бы один из двух маркеров не найдётся, и она ничего не сделает.

---

## Генерация фейкового hostname (genhost)

Фейковый hostname генерируется вызовом:

```lua
fakehost = genhost(pos[2] - pos[1] + 1, desync.arg.host)
```

- **Длина фейка** всегда равна длине реального hostname (`endhost - host`). Это критически важно: DPI видит hostname той же длины на том же TCP sequence, и не может отличить по размеру
- **С шаблоном** (`host=vk.com`): генерируется случайный поддомен, например `e8nzn.vk.com`. Если реальный hostname короче шаблона — шаблон обрезается слева
- **Без шаблона**: генерируется случайный домен с одним из стандартных TLD (`com`, `org`, `net`, `edu`, `gov`, `biz`). Если длина < 7 — случайная строка без точек

> [!note] Проще говоря
> Фейк должен занимать ровно столько же байт, сколько настоящее имя, поэтому его собирает `genhost`: ей передают нужную длину и, если есть, шаблон. Шаблон — необязательная подсказка (`host=vk.com`): фейк становится случайным поддоменом этого домена. TLD (top-level domain, домен верхнего уровня) — последняя часть имени, вроде `com` или `org`; без шаблона функция выбирает один из стандартных. Не путайте аргумент `host=` (шаблон фейка) с маркером `host` (начало настоящего имени в запросе): названия совпадают, но это разные вещи.

Примеры генерации (шаблон `google.com`, реальный hostname длиной 16):

```
h82aj.google.com    (len=16, template="google.com")
```

Примеры без шаблона:

```
k3x.net             (len=7)
b8c54a              (len=6, без TLD)
u9a7bk2.org         (len=11)
```

---

## Дополнительные точки разреза

### midhost — разрез внутри hostname

`midhost` задает позицию для **дополнительного** разреза внутри реального hostname. Реальный hostname отправляется не одним сегментом, а двумя.

```
midhost=<posmarker>
```

Маркер разрешается через `resolve_pos`. Типичные значения: `midsld`, `sld`, `endsld`, `host+5`, и т.д.

**Ограничение:** разрешенная позиция `midhost` должна быть строго внутри hostname:

```
host + 1  <=  midhost  <=  endhost - 1
```

Если позиция выходит за эти границы (т.е. `midhost <= pos[1]` или `midhost > pos[2]`), разрез внутри hostname не происходит, и hostname отправляется одним сегментом. Функция логирует "midhost is not inside the host range".

**Без midhost:**

```
  Один сегмент: [весь реальный hostname]
```

**С midhost=midsld:**

- сегмент 3a: от `host` до `midhost-1` — первая часть hostname;
- сегмент 3b: от `midhost` до `endhost-1` — вторая часть hostname.

> [!note] Проще говоря
> Обычно настоящее имя уходит одним сегментом. С `midhost` оно уходит двумя, и в каждом лежит только часть имени. Значения `midsld`, `sld`, `endsld` и `host+5` — те же маркеры, что описаны в разделе «[[Zapret2/desync/multisplit#Относительные маркеры|относительные маркеры]]», а `+5` — [[Zapret2/desync/multisplit#Арифметика маркеров|арифметика маркеров]]. `resolve_pos` — вызов, который превращает маркер в номер байта. Если позиция выходит за пределы имени, разреза не будет, см. нюанс [[#5. midhost за пределами hostname молча игнорируется|5]].

### disorder_after — обратный порядок хвоста

`disorder_after` задает позицию для **дополнительного** разреза хвостовой части (всё что после hostname) и отправки двух результирующих частей в **обратном** порядке.

```
disorder_after=<posmarker>
```

**Особый случай:** если маркер — пустая строка (`disorder_after` без `=значения`, т.е. просто `:disorder_after`), используется маркер `"-1"` (последний байт payload).

```lua
disorder_after_pos = resolve_pos(data, desync.l7payload,
    desync.arg.disorder_after == "" and "-1" or desync.arg.disorder_after)
```

**Ограничение:** разрешенная позиция должна быть **строго больше** `pos[2] + 1` (т.е. после endhost):

```
disorder_after_pos  >  pos[2] + 1
```

Если это условие не выполняется, disorder не происходит, и хвост отправляется одним сегментом в прямом порядке.

**Без disorder_after:**

```
  Один сегмент: [endhost → конец данных]
```

**С disorder_after (порядок отправки перевернут!):**

- сначала уходит последняя часть: от `disorder_after` до конца данных;
- затем средняя: от `endhost` до `disorder_after-1`.

> [!note] Проще говоря
> «Хвост» — всё, что стоит в запросе после имени. `disorder_after` режет его ещё на две части и отправляет вторую раньше первой. Такой обратный порядок ([[Zapret2/desync/multidisorder#Обратный порядок отправки — суть disorder|Обратный порядок отправки — суть disorder]]) может помешать DPI, который ждёт данные по порядку. Отрицательные маркеры вроде `-1` и `-20` отсчитываются от конца данных ([[Zapret2/desync/multisplit#Типы маркеров|Типы маркеров]]). Про пустое значение см. нюанс [[#6. disorder_after с пустым значением = "-1"|6]].

DPI, ожидающий данные по порядку, может не собрать хвост корректно.

---

## Полный список аргументов

Формат вызова:

```
--lua-desync=hostfakesplit[:arg1[=val1][:arg2[=val2]]...]
```

Все `val` приходят в Lua как строки. Если `=val` не указан, значение = пустая строка `""` (в Lua это truthy), поэтому флаги пишутся просто как `:optional`, `:nodrop`, `:nofake1`.

> [!note] Проще говоря
> Аргументы делятся на собственные (раздел A) и стандартные (разделы B–G): стандартные понимают и другие функции дурения, общий обзор есть в [[desync]]. Каждый аргумент отделяется от имени функции двоеточием, а флаг пишется без `=значения`.

### A) Собственные аргументы hostfakesplit

#### `host`

- **Формат:** `host=<str>`
- **Тип:** строка (шаблон hostname)
- **По умолчанию:** не задан (случайный домен со стандартным TLD)
- **Описание:** Шаблон для генерации фейкового hostname через `genhost`. Фейк будет выглядеть как `random.template`, например для `host=vk.com` и реального hostname длиной 12 символов: `e8nzn.vk.com`
- **Рекомендация:** задавайте домен, похожий на реальный (тот же TLD, популярный сервис), чтобы фейк выглядел правдоподобно для DPI-эвристик
- **Не путать:** аргумент `host=` — шаблон для фейка, а маркер `host` — начало настоящего имени в запросе; это разные вещи с одинаковым названием
- **Примеры:**
  - `host=google.com` — фейк вида `k7z2a.google.com`
  - `host=vk.com` — фейк вида `e8nzn.vk.com`
  - без `host=` — фейк вида `r4k2m.net` или `b8c54a` (случайный)

#### `midhost`

- **Формат:** `midhost=<posmarker>`
- **Тип:** строка (маркер позиции)
- **По умолчанию:** не задан (реальный hostname отправляется одним сегментом)
- **Описание:** Дополнительный разрез реального hostname на два сегмента. Маркер разрешается через `resolve_pos`. Должен быть строго внутри `host+1..endhost-1`, иначе игнорируется
- **Примеры:**
  - `midhost=midsld` — разрез посередине SLD (самый популярный вариант)
  - `midhost=sld` — разрез по началу SLD
  - `midhost=endsld` — разрез по концу SLD
  - `midhost=host+5` — 5 байт от начала hostname

#### `nofake1`

- **Формат:** `nofake1` (флаг, без значения)
- **Описание:** Не отправлять **первый** фейковый сегмент (fake1, перед реальным hostname). Полезно для экспериментов: некоторые DPI реагируют только на один из фейков

#### `nofake2`

- **Формат:** `nofake2` (флаг, без значения)
- **Описание:** Не отправлять **второй** фейковый сегмент (fake2, после реального hostname). Аналогично `nofake1`, но для второго фейка
- **Подробнее:** такие же флаги есть у [[Zapret2/desync/fakedsplit#nofake1..nofake4 — отключение отдельных фейков|fakedsplit]] (там фейков до четырёх)

#### `disorder_after`

- **Формат:** `disorder_after[=<posmarker>]`
- **Тип:** строка (маркер позиции) или пустая строка
- **По умолчанию:** не задан (хвост отправляется одним сегментом в прямом порядке)
- **Описание:** Дополнительный разрез хвостовой части (после hostname) и отправка в обратном порядке. Если значение = пустая строка (`:disorder_after` без `=...`), используется маркер `"-1"` (последний байт payload). Позиция должна быть > `endhost`, иначе игнорируется
- **Примеры:**
  - `disorder_after` — разрез по `-1` (последний байт), disorder всего хвоста
  - `disorder_after=-10` — разрез за 10 байт до конца payload
  - `disorder_after=sniext+50` — разрез через 50 байт после начала SNI extension data

#### `blob`

- **Формат:** `blob=<blobName>`
- **Тип:** имя blob-переменной
- **По умолчанию:** не задан
- **Описание:** Заменить текущий payload/reasm на указанный blob. Blob должен содержать валидный HTTP-запрос или TLS ClientHello, иначе маркеры `host`/`endhost` не разрешатся
- **Подробнее:** что такое blob и как его объявлять — в заметке [[blob]]; что внутри стандартных blob-ов — в разделе [[Zapret2/desync/fake#Стандартные blob-ы|Стандартные blob-ы]] статьи про `fake`
- **Примеры:**
  - `blob=fake_default_tls` — стандартный TLS-фейк
  - `blob=my_custom_ch` — предзагруженный blob с модифицированным ClientHello

#### `optional`

- **Формат:** `optional` (флаг, без значения)
- **Описание:** Мягкий режим:
  - Если задан `blob=...` и blob отсутствует — `hostfakesplit` **ничего не делает** (тихий skip, без ошибок)
- **Использование:** защита от ошибок при использовании blob, которые могут отсутствовать

#### `nodrop`

- **Формат:** `nodrop` (флаг, без значения)
- **Описание:** После успешной отправки сегментов **не выносить** `VERDICT_DROP` (вместо этого вернуть `VERDICT_PASS`). Оригинальный пакет тоже будет отправлен
- **Предупреждение:** в боевых профилях `nodrop` нежелателен — оригинал создаст дублирование и может ухудшить обход
- **Подробнее:** [[#9. nodrop создает дублирование|нюанс 9]]; то же для `multisplit` — [[Zapret2/desync/multisplit#4. nodrop создаёт дублирование|нюанс 4]]; `VERDICT_PASS` и `VERDICT_DROP` — [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8 жизненного цикла]]

---

### B) Standard direction

| Параметр | Значения | По умолчанию |
|:---------|:---------|:-------------|
| `dir` | `in`, `out`, `any` | `out` |

Фильтр по направлению пакета. `hostfakesplit` по умолчанию работает только с исходящими (`out`).

Направление говорит, в какую сторону идёт пакет: от вашего компьютера к серверу (исходящий) или обратно (входящий). При первом вызове функция отсекает себя от противоположного направления: [[Zapret2/жизненный цикл desync-функции#Стадия 2. Отсечение противоположного направления|стадия 2 жизненного цикла]].

- `dir=out` — только исходящие (от клиента к серверу)
- `dir=in` — только входящие (от сервера к клиенту)
- `dir=any` — оба направления

---

### C) Standard payload

| Параметр | Значения | По умолчанию |
|:---------|:---------|:-------------|
| `payload` | список типов через запятую | `known` |

Фильтр по типу payload на уровне Lua. Это **дополнительный** фильтр к `--payload=...` на уровне профиля.

Проще говоря: тип payload — название распознанного протокола ([[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|тип payload]]). Основной отбор лучше делать флагом `--payload=…` на уровне профиля: он работает в быстром ядре программы, а аргумент `payload=` проверяется уже в Lua ([[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|стадия 5 жизненного цикла]]).

- `payload=known` — только распознанные протоколы
- `payload=tls_client_hello,http_req` — конкретные типы
- `payload=all` — любой payload, включая `unknown`

**Важно:** `hostfakesplit` требует payload с hostname (`http_req`, `tls_client_hello`). Даже при `payload=all`, если payload = `unknown`, маркеры `host`/`endhost` не разрешатся и функция ничего не сделает. Поэтому фильтр `payload` здесь влияет только на то, дойдет ли код до попытки resolve_range.

---

### D) Standard fooling

Модификации L3/L4 заголовков. В `hostfakesplit` fooling и repeats применяются **только к фейковым** сегментам. К оригиналам — только `tcp_ts_up`.

Fooling — намеренная порча заголовков пакета, чтобы сервер отбросил фейк, а DPI его прочитал ([[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]). L3/L4 — это заголовки IP (сетевой уровень) и TCP (транспортный). Таблица ниже — полный набор опций; как убивают фейк и где ломаются основные из них, описано в разделе «[[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталог способов]]».

| Параметр | Описание | Пример |
|:---------|:---------|:-------|
| `ip_ttl=N` | Установить IPv4 TTL | `ip_ttl=6` |
| `ip6_ttl=N` | Установить IPv6 Hop Limit | `ip6_ttl=6` |
| `ip_autottl=delta,min-max` | Автоматический TTL (delta от серверного TTL) | `ip_autottl=-2,40-64` |
| `ip6_autottl=delta,min-max` | Аналогично для IPv6 | `ip6_autottl=-2,40-64` |
| `ip6_hopbyhop[=HEX]` | Вставить extension header hop-by-hop | `ip6_hopbyhop` |
| `ip6_hopbyhop2[=HEX]` | Второй hop-by-hop header | `ip6_hopbyhop2` |
| `ip6_destopt[=HEX]` | Destination options header | `ip6_destopt` |
| `ip6_destopt2[=HEX]` | Второй destination options | `ip6_destopt2` |
| `ip6_routing[=HEX]` | Routing header | `ip6_routing` |
| `ip6_ah[=HEX]` | Authentication header | `ip6_ah` |
| `tcp_seq=N` | Сместить TCP sequence (+ или -) | `tcp_seq=-10000` |
| `tcp_ack=N` | Сместить TCP ack (+ или -) | `tcp_ack=-66000` |
| `tcp_ts=N` | Сместить TCP timestamp | `tcp_ts=-100` |
| `tcp_md5[=HEX]` | Добавить TCP MD5 option (16 байт) | `tcp_md5` |
| `tcp_flags_set=LIST` | Установить TCP-флаги | `tcp_flags_set=FIN,PUSH` |
| `tcp_flags_unset=LIST` | Снять TCP-флаги | `tcp_flags_unset=ACK` |
| `tcp_ts_up` | Поднять TCP timestamp option в начало заголовка | `tcp_ts_up` |
| `tcp_nop_del` | Удалить все TCP NOP опции | `tcp_nop_del` |
| `fool=<func>` | Кастомная Lua-функция fooling | `fool=my_fooler` |

**Критически важно:** fooling **обязателен** для `hostfakesplit`. Без fooling фейковые hostname будут приняты сервером, что вызовет ошибку соединения. Минимально рекомендуемый fooling: `tcp_md5` или `ip_ttl=1`.

> [!note] Проще говоря
> Фейки — это ложные данные. Если сервер их не отбросит, он получит два противоречивых имени на одном месте потока. Fooling делает так, что сервер выбрасывает именно фейки, а настоящие сегменты принимает: настоящим fooling не достаётся, кроме `tcp_ts_up`.

**Заметка про tcp_ts_up:** это единственная fooling-опция, которая применяется и к оригинальным сегментам. Она перемещает TCP timestamp option в начало заголовка, что улучшает совместимость с badseq-fooling на серверах с Linux TCP-стеком.

Подробнее про эту опцию — в заметке [[Zapret2/ts-and-fooling|ts и fooling]].

---

### E) Standard ipid

IP ID — идентификатор в заголовке IPv4-пакета. Эти параметры задают, какие значения получат отправляемые сегменты.

| Параметр | Описание | По умолчанию |
|:---------|:---------|:-------------|
| `ip_id=seq` | Последовательные IP ID | `seq` |
| `ip_id=rnd` | Случайные IP ID | — |
| `ip_id=zero` | Нулевые IP ID | — |
| `ip_id=none` | Не менять IP ID | — |
| `ip_id_conn` | Сквозная нумерация IP ID в рамках соединения | — |

`ip_id` применяется и к фейкам, и к оригиналам (через `ipid = desync.arg` в обоих `opts`).

---

### F) Standard reconstruct

Реконструкция — сборка готового raw-пакета («сырого», со всеми заголовками) перед отправкой.

| Параметр | Описание |
|:---------|:---------|
| `badsum` | Испортить L4 (TCP) checksum при реконструкции raw-пакета |

**Важно:** `reconstruct` (включая `badsum`) применяется **только к фейкам**. У оригиналов `reconstruct = {}` (пустая таблица). Это логично: если испортить checksum на оригинале, сервер его отбросит.

---

### G) Standard rawsend

Rawsend — отправка готовых сегментов в сеть самой функцией. Эти параметры управляют отправкой.

| Параметр | Описание |
|:---------|:---------|
| `repeats=N` | Отправить каждый сегмент N раз |
| `ifout=<iface>` | Интерфейс для отправки |
| `fwmark=N` | Firewall mark (только Linux) |

**Важно:** `repeats` применяется **только к фейкам**. Оригиналы используют `rawsend_opts_base`, в которой repeats отсутствует. Фейки используют `rawsend_opts`, включающую `repeats`. Таким образом `repeats=3` означает, что каждый фейковый сегмент отправится 3 раза, а каждый реальный — 1 раз.

`ifout` и `fwmark` применяются и к фейкам, и к оригиналам.

---

## Порядок отправки сегментов

В этом разделе показано, в каком порядке уходят сегменты и с каким seq. Реальный сегмент несёт настоящие данные запроса и доходит до сервера, фейк несёт ложное имя и должен быть отброшен сервером. Пометки `opts_orig` и `opts_fake` — два набора опций: для реальных сегментов и для фейков ([[#Разделение opts: оригиналы vs фейки|Разделение opts]]). Seq сегмента — место его данных в потоке: `seq=host` значит «с того байта, где начинается имя». Настоящие части идут слева направо (кроме случая `disorder_after`), а второй фейк возвращается на то же место, где стоит имя.

### Базовый вариант (без midhost, без disorder_after)

![[wiki-anim-z2-hostfake-base.svg|Анимация: базовый hostfakesplit — пять сегментов, фейк вокруг имени сайта]]

### С midhost (hostname делится на две части)

![[wiki-anim-z2-hostfake-midhost.svg|Анимация: hostfakesplit с midhost — имя делится на две части, шесть сегментов]]

### С disorder_after (хвост в обратном порядке)

![[wiki-anim-z2-hostfake-disorder.svg|Анимация: hostfakesplit с disorder_after — хвост уходит в обратном порядке]]

### Полный вариант (midhost + disorder_after + оба фейка)

```
Payload:
  [before_host][host_p1][host_p2][after_1][after_2]
                ^host    ^mid     ^endhost ^disord

Отправка (7 сегментов):
  1.  [before_host]      seq=0              opts_orig
  2.  [FAKE hostname]    seq=host           opts_fake (fake1)
  3a. [host_p1]          seq=host           opts_orig
  3b. [host_p2]          seq=midhost        opts_orig
  4.  [FAKE hostname]    seq=host           opts_fake (fake2)
  5a. [after_2]          seq=disorder_after opts_orig (ПЕРВЫМ)
  5b. [after_1]          seq=endhost        opts_orig (ВТОРЫМ)
```

Обратите внимание на seq: у обоих фейков и у первой части настоящего имени он один и тот же (`seq=host`); при `midhost` вторая часть имени идёт со своим seq (`seq=midhost`).

### ASCII-диаграмма: вид на уровне TCP sequence

```
TCP sequence (байты):
  0          host     midhost  endhost  disord   end
  |           |         |        |        |       |
  v           v         v        v        v       v
  [before_host][=hostname=area=][=after_host_area=]

Порядок отправки пакетов по времени (сверху вниз):

  t=1  |-before-|                                        seq=0          REAL
  t=2            |====FAKE_HOST====|                     seq=host       FAKE
  t=3            |--p1--|                                seq=host       REAL
  t=4                    |---p2---|                      seq=mid        REAL
  t=5            |====FAKE_HOST====|                     seq=host       FAKE
  t=6                                       |--after2--| seq=disord    REAL
  t=7                              |after1--|            seq=endhost   REAL
```

На схеме время идёт сверху вниз, а по горизонтали отложено место данных в потоке. Полоса `FAKE_HOST` покрывает то же место, что обе части имени (`p1` и `p2`), поэтому на месте имени оказываются два фейка и настоящее имя, разрезанное на `p1` и `p2`.

**Что видит DPI:** на одном и том же TCP sequence (`host`) приходят разные данные — реальный hostname и фейковый. DPI должен выбрать, какой из них "правильный". Если DPI выбирает фейк — обход успешен.

**Что видит сервер:** фейковые пакеты отброшены благодаря fooling (инвалидный TTL, md5sig, badseq и т.д.). Реальные сегменты корректно реассемблируются TCP-стеком.

> [!note] Проще говоря
> DPI видит на месте имени три кандидата и должен угадать настоящий; сервер фейки выбрасывает и собирает запрос из настоящих сегментов. Те же две картинки на примере `fakedsplit` — в разделах [[Zapret2/desync/fakedsplit#Что видит DPI|Что видит DPI]] и [[Zapret2/desync/fakedsplit#Что видит сервер|Что видит сервер]].

---

## Разделение opts: оригиналы vs фейки

Перед отправкой функция собирает два набора настроек: `opts_orig` для настоящих сегментов («оригиналов») и `opts_fake` для фейков. Именно это различие позволяет испортить только фейки. Тот же приём использует [[Zapret2/desync/fakedsplit#Два набора опций: opts_orig vs opts_fake|fakedsplit]]; у `multisplit` такого разделения нет, там fooling достаётся всем сегментам ([[Zapret2/desync/multisplit#6. Fooling применяется ко ВСЕМ сегментам|нюанс 6]]).

Это ключевая особенность `hostfakesplit` (и аналогичных fake-функций):

```lua
opts_orig = {
    rawsend    = rawsend_opts_base(desync),   -- ifout, fwmark. БЕЗ repeats
    reconstruct = {},                          -- пустой (без badsum)
    ipfrag     = {},                           -- пустой (ipfrag не используется)
    ipid       = desync.arg,                   -- ip_id применяется
    fooling    = {tcp_ts_up = desync.arg.tcp_ts_up}  -- ТОЛЬКО tcp_ts_up
}

opts_fake = {
    rawsend    = rawsend_opts(desync),         -- ifout, fwmark, repeats
    reconstruct = reconstruct_opts(desync),    -- badsum
    ipfrag     = {},                           -- пустой (ipfrag не используется)
    ipid       = desync.arg,                   -- ip_id применяется
    fooling    = desync.arg                    -- ВСЕ fooling-опции
}
```

| Что | Оригиналы | Фейки |
|:----|:----------|:------|
| fooling | Только `tcp_ts_up` | Все (`ip_ttl`, `tcp_md5`, `tcp_seq`, ...) |
| reconstruct (badsum) | Нет | Да |
| repeats | Нет (всегда 1 раз) | Да |
| ipfrag | Нет | Нет |
| ip_id | Да | Да |
| ifout, fwmark | Да | Да |

> [!note] Проще говоря
> Настоящие сегменты уходят почти нетронутыми: из fooling-опций им достаётся только `tcp_ts_up` (а `ip_id`, `ifout` и `fwmark` действуют на оба набора). Фейки получают весь fooling, порчу контрольной суммы (`badsum`) и повторы (`repeats`). Поле `ipfrag` (IP-фрагментация, см. [[Zapret2/desync/multisplit#F) Standard ipfrag|F) Standard ipfrag]]) в обоих наборах пустое: `ipfrag = {}`.

---

## Поведение при replay / reasm

![[desync-anim-hub-reasm.svg|Анимация: большой ClientHello приходит тремя пакетами, ядро собирает reasm_data, функция режет весь буфер на первой части, остальные части дропаются]]

Многопакетный запрос `nfqws2` придерживает, собирает в буфер `reasm_data` и перепроигрывает через desync-функции. `hostfakesplit` использует штатную развилку из общего скелета desync-функций ([[жизненный цикл desync-функции]], стадия 6) без изменений: на первой части определяет по собранному буферу границы имени хоста, генерирует фейковый hostname той же длины и отправляет всю нарезку; остальные части дропает. Если отправка сорвалась, флаг «реасм уже отправлен» не ставится и оставшиеся части проходят как есть.

Специфика функции здесь важная: имя хоста ищется по **всему** реасму, поэтому приём работает и тогда, когда `Host:` или SNI физически лежали во втором пакете запроса.

> [!note] Проще говоря
> Если запрос не влез в один пакет, движок придерживает пакеты, собирает запрос целиком (reasm — повторная сборка) и затем проигрывает придержанные части заново (replay). `hostfakesplit` ищет имя во всём собранном запросе, отправляет всю нарезку на первой части, а остальные части выбрасывает. Как это устроено, описано в [[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|стадии 6 жизненного цикла]] и в разделе [[Zapret2/структура desync и диссекта#Приём многопакетных пейлоадов (reasm / decrypt / replay)|Приём многопакетных пейлоадов]]; на примере `multisplit` — в разделе [[Zapret2/desync/multisplit#Поведение при replay / reasm|Поведение при replay / reasm]].

---

## Место в общем скелете

Тело `hostfakesplit` построено по тому же шаблону, что и все остальные функции дурения: восемь стадий от отсева чужого транспорта до вердикта. Разобраны они один раз в [[жизненный цикл desync-функции]] — здесь только отклонения:

| Стадия общего скелета | Что делает `hostfakesplit` |
|:----------------------|:----------------|
| 1. Отсев транспорта | только TCP; на не-TCP делает `instance_cutoff` (кроме связанного ICMP) |
| 2. Направление | `dir=out` по умолчанию, отключается от входящего |
| 3. Аргументы | обязательных нет; `optional` + отсутствующий `blob` → тихий выход |
| 4. Данные | штатная цепочка `blob → reasm → payload` |
| 5. Гварды | штатные `#data>0`, `direction_check`, `payload_check` (по умолчанию `known`) |
| 6. replay | штатная развилка: работа на первой части, дроп остальных |
| 7. **Своя техника** | позиции не задаются вручную: функция сама разрешает диапазон `host,endhost-1` и строит нарезку вокруг имени хоста, подмешивая фейковые hostname — этому посвящена вся заметка |
| 8. Вердикт | `VERDICT_DROP` после успешной отправки, `VERDICT_PASS` при `nodrop` или сбое `rawsend` |

Две особенности стоит держать в голове. Первая: диапазон имени хоста разрешается в **строгом** режиме — если маркеры `host`/`endhost` не разрешились (payload не распознан как HTTP или TLS), функция не делает ничего. Вторая: как и у [[fakedsplit]], опции собираются в два набора — полный fooling и `repeats` уходят только на фейковые hostname, реальным частям достаётся лишь `tcp_ts_up` и `ip_id`. Про `ipfrag` комментарий в коде (перед отправкой реальных частей имени) обещает выборочность: если IP-фрагментация задана, фрагментировать только реальные части имени хоста, а фейки и всё вне имени оставлять видимыми для DPI. Но в текущей версии кода оба набора опций передают `ipfrag = {}`, поэтому фактически IP-фрагментации нет ни у одной части — подробно в [[#3. ipfrag не задействуется|нюансе 3]].

> [!note] Проще говоря
> Стадии скелета в таблице выше разобраны в заметке про жизненный цикл: `instance_cutoff` — навсегда отключиться от потока, который не TCP ([[Zapret2/жизненный цикл desync-функции#Стадия 1. Отсев чужого транспорта и instance cutoff|стадия 1]]); гварды — три проверки перед работой ([[Zapret2/жизненный цикл desync-функции#Стадия 5. Три гварда|стадия 5]]); replay — работа на первой части собранного запроса ([[Zapret2/жизненный цикл desync-функции#Стадия 6. Развилка перепроигрывания (replay)|стадия 6]]); вердикт — решение, что делать с оригиналом ([[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8]]). `ipfrag` — IP-фрагментация, деление пакета на уровне IP ([[Zapret2/desync/multisplit#F) Standard ipfrag|F) Standard ipfrag]]).

---

## Псевдокод алгоритма

Тело функции целиком, включая общие для всех техник стадии — они пронумерованы так же, как в [[жизненный цикл desync-функции]]:

```lua
function hostfakesplit(ctx, desync)
    -- 1. Проверка: только TCP
    if not desync.dis.tcp then
        if not desync.dis.icmp then instance_cutoff_shim() end
        return
    end

    -- 2. Cutoff противоположного направления
    direction_cutoff_opposite(ctx, desync)

    -- 3. Проверка optional blob
    if optional and blob specified and blob not exists then
        DLOG("blob not found. skipped")
        return
    end

    -- 4. Выбор данных
    data = blob_or_def(blob) or reasm_data or dis.payload

    -- 5. Проверки: данные не пусты, направление OK, payload OK
    if #data > 0 and direction_check() and payload_check() then
        if replay_first() then

            -- 6. Разрешение host и endhost (strict=true)
            pos = resolve_range(data, l7payload, "host,endhost-1", true)
            -- pos[1] = host, pos[2] = endhost-1 (последний байт hostname)

            if pos then
                -- 7. Подготовка opts
                opts_orig = { fooling = {tcp_ts_up only}, no repeats, no badsum }
                opts_fake = { fooling = all, repeats, badsum }

                -- 8. Отправка before_host (реальный)
                rawsend(data[1..host-1], seq=0, opts_orig)

                -- 9. Генерация фейкового hostname
                fakehost = genhost(endhost - host, arg.host)

                -- 10. Fake1 (если не nofake1)
                if not nofake1 then
                    rawsend(fakehost, seq=host-1, opts_fake)
                end

                -- 11. Реальный hostname (с midhost или без)
                if midhost and midhost внутри host+1..endhost-1 then
                    rawsend(data[host..midhost-1], seq=host-1, opts_orig)
                    rawsend(data[midhost..endhost-1], seq=midhost-1, opts_orig)
                else
                    rawsend(data[host..endhost-1], seq=host-1, opts_orig)
                end

                -- 12. Fake2 (если не nofake2)
                if not nofake2 then
                    rawsend(fakehost, seq=host-1, opts_fake)
                end

                -- 13. After_host (с disorder или без)
                if disorder_after and disorder_after > endhost then
                    -- ОБРАТНЫЙ порядок:
                    rawsend(data[disorder_after..end], seq=disorder_after-1, opts_orig)
                    rawsend(data[endhost..disorder_after-1], seq=endhost-1, opts_orig)
                else
                    rawsend(data[endhost..end], seq=endhost-1, opts_orig)
                end

                -- 14. Пометить как отправленное
                replay_drop_set()
                return nodrop and VERDICT_PASS or VERDICT_DROP
            else
                DLOG("host range cannot be resolved")
            end
        else
            DLOG("not acting on further replay pieces")
        end
        -- 15. Drop replayed packets если ранее успешно отправлено
        if replay_drop() then
            return nodrop and VERDICT_PASS or VERDICT_DROP
        end
    end
end
```

> [!note] Проще говоря
> Псевдокод читается по порядку. Если это не TCP, функция выходит. Иначе она выбирает данные, находит границы имени и отправляет: часть до имени, первый фейк, настоящее имя (одним куском или двумя, если задан `midhost`), второй фейк и часть после имени (в обратном порядке, если задан `disorder_after`). В конце она помечает запрос отправленным и выбрасывает оригинал (при `nodrop` — пропускает).

---

## Нюансы и подводные камни

### 1. Работает только с http_req и tls_client_hello

`hostfakesplit` требует payload с hostname. Для `unknown`, `quic_initial` и любых других типов маркеры `host`/`endhost` не разрешатся, и функция ничего не сделает. Это **не ошибка** — просто тихий пропуск.

> [!note] Проще говоря
> `unknown` — данные, тип которых не распознан; `quic_initial` — первый пакет QUIC; это не TCP, а на не-TCP функция отключается (см. [[#Место в общем скелете|Место в общем скелете]]). Как распознаются типы, описано в заметке про [[Zapret2/payload#Что такое тип payload (и чем он отличается от протокола потока)|тип payload]].

### 2. Fooling обязателен

Без fooling фейковые hostname будут приняты сервером. Сервер получит два противоречивых hostname на одном TCP sequence. Результат непредсказуем: от ошибки TLS handshake до 400 Bad Request. **Всегда** задавайте хотя бы одну fooling-опцию (`tcp_md5`, `ip_ttl=1`, `badsum`, `tcp_seq=-10000` и т.д.).

Каталог способов испортить фейк — в разделе «[[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталог способов]]».

### 3. ipfrag не задействуется

В отличие от [[multisplit]] и [[multidisorder]], `hostfakesplit` **не поддерживает** IP-фрагментацию. Поля `ipfrag = {}` пусты и для оригиналов, и для фейков. Если вам нужна IP-фрагментация поверх hostname-разреза — придётся комбинировать с другими инструментами.

Проще говоря: IP-фрагментация — деление пакета на части уже на уровне IP, а не TCP; что это такое и какие у неё параметры, описано в разделе [[Zapret2/desync/multisplit#F) Standard ipfrag|F) Standard ipfrag]].

### 4. repeats идут только на фейки

`repeats=5` означает, что каждый фейковый сегмент отправится 5 раз, а реальные — по 1 разу. Это полезно: повторение фейков увеличивает шанс, что DPI примет именно фейк, а не реальный hostname.

То же самое для `fakedsplit` — в нюансе [[Zapret2/desync/fakedsplit#6. repeats применяются ТОЛЬКО к фейкам|repeats применяются ТОЛЬКО к фейкам]].

### 5. midhost за пределами hostname молча игнорируется

Если `midhost` разрешается в позицию за пределами hostname (например, `midhost=method` для HTTP, который указывает на начало `GET`), разрез внутри hostname не произойдет. Функция логирует "midhost is not inside the host range" и отправляет hostname одним сегментом.

Проще говоря: маркер `method` указывает на начало HTTP-метода (`GET`), то есть далеко от имени, поэтому такой `midhost` разреза не даёт. Описание маркеров — в разделе «[[Zapret2/desync/multisplit#Относительные маркеры|относительные маркеры]]»; работа `midhost` — в разделе [[#midhost — разрез внутри hostname|midhost]].

### 6. disorder_after с пустым значением = "-1"

Запись `:disorder_after` (без `=значения`) эквивалентна `:disorder_after=-1`. Маркер `-1` разрешается в `#data - 1` (предпоследний байт payload в 0-based, последний байт в 1-based). Это делает disorder хвостовой части максимально выраженным: последний байт отправляется первым, а основная часть хвоста — вторым.

Проще говоря: `:disorder_after` без значения означает «последний байт данных отправить первым, а всё остальное после имени — вторым».

### 7. Оба фейка используют один и тот же fakehost

`genhost` вызывается один раз, и результат (`fakehost`) используется и для fake1, и для fake2. Оба фейковых сегмента содержат **идентичный** фейковый hostname и отправляются с одинаковым TCP sequence (`pos[1]-1`).

Проще говоря: оба фейка одинаковые, а не два разных случайных имени. Как строится фейк, описано в разделе [[#Генерация фейкового hostname (genhost)|Генерация фейкового hostname]].

### 8. TCP sequence фейков = TCP sequence реального hostname

Фейки отправляются с тем же `seq = pos[1] - 1`, что и реальный hostname. Это означает, что DPI видит **перекрывающиеся** данные: на одном и том же диапазоне sequence — фейковый hostname, затем реальный, затем снова фейковый. TCP-стек сервера отбросит фейки (благодаря fooling) и примет реальный.

Проще говоря: три сегмента претендуют на одно и то же место потока — фейк, настоящее имя, фейк. Сервер оставляет настоящее. Картинка — в разделе [[#ASCII-диаграмма: вид на уровне TCP sequence|ASCII-диаграмма]], про seq — в разделе [[#Коротко и по-простому|Коротко и по-простому]].

### 9. nodrop создает дублирование

С `nodrop` оригинальный пакет тоже будет отправлен. Сервер получит hostname дважды (из нарезанных сегментов и из оригинала). Используйте `nodrop` только для отладки.

Про решение «выбросить или пропустить» — [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8 жизненного цикла]]; то же для `multisplit` — [[Zapret2/desync/multisplit#4. nodrop создаёт дублирование|нюанс 4]].

### 10. Порядок инстансов важен

Если перед `hostfakesplit` стоит `pktmod` с fooling — fooling применится к диссекту, и `hostfakesplit` порежет модифицированный пакет. Если после `hostfakesplit` стоит другой инстанс — он увидит `VERDICT_DROP` и не получит оригинальный payload.

Уточнение из разбора вердиктов: `VERDICT_DROP` не обрывает цепочку. Движок проходит все инстансы профиля и складывает их вердикты (`VERDICT_DROP` перебивает `VERDICT_PASS` и `VERDICT_MODIFY`), так что инстансы после `hostfakesplit` вызываются как обычно, а дроп означает лишь то, что оригинальный пакет в итоге не уйдёт ([[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8 жизненного цикла]]; то же — в [[Zapret2/desync/multisplit#7. Порядок инстансов важен|нюансе о порядке инстансов в multisplit]] и в [[Zapret2/desync/fakedsplit#10. Порядок инстансов важен|fakedsplit]]).

Проще говоря: инстанс — одна запись `--lua-desync=…` в профиле, а диссект — пакет, заранее разобранный движком на поля ([[структура desync и диссекта]]). `pktmod` — функция, которая модифицирует диссект. Порядок инстансов задаёт порядок флагов в командной строке ([[последовательность аргументов]]); как складываются вердикты нескольких инстансов — [[Zapret2/жизненный цикл desync-функции#Стадия 8. Вердикт и его агрегация|стадия 8 жизненного цикла]].

---

## Отличия от других функций сегментации

Пять функций ниже режут данные на TCP-сегменты, но различаются числом разрезов, наличием фейков, порядком отправки и тем, как устроены `seqovl` (скрытый фейк внутри сегмента, [[Zapret2/desync/multisplit#seqovl — скрытый фейк внутри сегмента|разбор]]) и `ipfrag` (IP-фрагментация). Подробности о каждой — в разделах [[Zapret2/desync/multisplit#Отличия от других функций сегментации|multisplit]], [[Zapret2/desync/fakedsplit#Отличия от других функций сегментации|fakedsplit]] и [[Zapret2/desync/fakeddisorder#Отличия от fakedsplit, multisplit и multidisorder|fakeddisorder]].

| Аспект | `hostfakesplit` | `fakedsplit` | `fakeddisorder` | `multisplit` | `multidisorder` |
|:-------|:----------------|:-------------|:----------------|:-------------|:----------------|
| Точки разреза | Автоматические (host/endhost) + midhost, disorder_after | Одна произвольная | Одна произвольная | Список произвольных | Список произвольных |
| Привязка к hostname | **Да** (только http_req, tls_client_hello) | Нет | Нет | Нет | Нет |
| Фейковые сегменты | 2 (фейковый hostname) | До 4 (паттерн) | До 4 (паттерн) | Нет | Нет |
| Содержимое фейков | Осмысленный hostname (genhost) | Паттерн (0x00 и т.п.) | Паттерн (0x00 и т.п.) | — | — |
| disorder хвоста | Опционально (disorder_after) | Нет | Весь порядок обратный | Нет | Весь порядок обратный |
| seqovl | **Нет** | Да (число) | Да (маркер) | Да (число) | Да (маркер) |
| ipfrag | **Нет** | **Нет** | **Нет** | Да | Да |
| Fooling к | Только фейкам | Только фейкам | Только фейкам | Всем сегментам | Всем сегментам |
| repeats к | Только фейкам | Только фейкам | Только фейкам | Всем сегментам | Всем сегментам |

> [!note] Проще говоря
> `hostfakesplit` — единственная из пяти функций, которая сама знает, где в запросе имя сайта, и единственная, чьи фейки — осмысленное имя, а не заливка паттерном. Обратный порядок у неё необязательный и касается только данных после имени.

---

## Миграция с nfqws1

Этот раздел для тех, кто переносит настройки из первой версии zapret (программа `nfqws`) во вторую (`nfqws2`). В первой версии техника выбиралась ключом `--dpi-desync=…`, а её параметры — отдельными ключами `--dpi-desync-…`; во второй всё это записывается аргументами одного вызова `--lua-desync=hostfakesplit:…`.

### Соответствие параметров

| nfqws1 | nfqws2 |
|:-------|:-------|
| `--dpi-desync=hostfakesplit` | `--lua-desync=hostfakesplit` |
| `--dpi-desync-fooling=md5sig` | `:tcp_md5` |
| `--dpi-desync-fooling=badseq` | `:tcp_seq=-10000` |
| `--dpi-desync-fooling=ttl` + `--dpi-desync-ttl=N` | `:ip_ttl=N` |
| `--dpi-desync-fake-unknown=hex` | `:host=<template>` (не прямой аналог; genhost вместо hex) |
| нет аналога | `:midhost=midsld` (новая возможность nfqws2) |
| нет аналога | `:disorder_after` (новая возможность nfqws2) |
| нет аналога | `:nofake1`, `:nofake2` (новая возможность nfqws2) |

Проще говоря: режимы fooling из nfqws1 становятся аргументами функции (`md5sig` превращается в `:tcp_md5`), а `midhost`, `disorder_after`, `nofake1` и `nofake2` в первой версии просто не существовали.

### Пример полной миграции

```bash
# nfqws1:
nfqws --dpi-desync=hostfakesplit \
  --dpi-desync-fooling=md5sig

# nfqws2 (эквивалент):
nfqws2 \
  --payload=tls_client_hello,http_req \
    --lua-desync=hostfakesplit:tcp_md5
```

```bash
# nfqws1: hostfakesplit + fake
nfqws --dpi-desync=fake,hostfakesplit \
  --dpi-desync-fooling=md5sig \
  --dpi-desync-fake-tls-mod=rnd,rndsni,dupsid

# nfqws2 (два инстанса):
nfqws2 \
  --payload=tls_client_hello \
    --lua-desync=fake:blob=fake_default_tls:tcp_md5:tls_mod=rnd,rndsni,dupsid \
  --payload=http_req \
    --lua-desync=fake:blob=fake_default_http:tcp_md5 \
  --payload=tls_client_hello,http_req \
    --lua-desync=hostfakesplit:tcp_md5
```

В nfqws1 фейк и разрез задавались одним ключом `--dpi-desync=fake,hostfakesplit`, в nfqws2 каждая техника становится отдельным инстансом `--lua-desync`.

```bash
# nfqws1: hostfakesplit + syndata
nfqws --dpi-desync=syndata,hostfakesplit \
  --dpi-desync-fooling=md5sig --wssize 1:6

# nfqws2:
nfqws2 \
  --lua-desync=wssize:wsize=1:scale=6 \
  --lua-desync=syndata \
  --payload=tls_client_hello,http_req \
    --lua-desync=hostfakesplit:tcp_md5
```

Третий пример переносит связку с [[Zapret2/desync/syndata#Зачем нужен syndata|syndata]] и ключом `--wssize`; каждый ключ nfqws1 тоже становится своим инстансом (о `wssize` см. [[desync]]).

---

## Практические примеры

Ниже шестнадцать готовых сочетаний, от минимального до боевых. В большинстве сначала стоит фильтр `--payload=…` («на каких данных срабатывать»), потом `--lua-desync=hostfakesplit:…`; fooling на фейках обязателен ([[Zapret2/ts-and-fooling#Что такое fooling|Что такое fooling]]).

### 1. Минимальный (TLS + md5sig fooling)

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5
```

Разрезает TLS ClientHello по границам SNI, вставляет 2 фейка с md5sig fooling.

### 2. HTTP + TTL fooling

```bash
--payload=http_req --lua-desync=hostfakesplit:ip_ttl=4
```

Разрезает HTTP-запрос по границам `Host:`, фейки с TTL=4 (не доживут до сервера).

TTL — ограничение числа промежуточных узлов, которые может пройти пакет; фейк с малым TTL «умирает» по дороге и до сервера не доходит ([[Zapret2/ts-and-fooling#Как заставить fake умереть: каталог способов|каталог способов]]).

### 3. С шаблоном фейкового hostname

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:host=google.com
```

Фейковый SNI будет выглядеть как `r7k2q.google.com` (длина = длина реального hostname).

### 4. С midhost (разрез hostname пополам)

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:midhost=midsld
```

Реальный hostname разрезается посередине SLD. Вместо 3 реальных сегментов — 4. DPI получает ещё более фрагментированную картину.

### 5. С disorder хвоста (пустое значение = "-1")

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after
```

Хвостовая часть (после hostname) отправляется в обратном порядке: последний байт первым, основная часть — вторым.

### 6. С disorder_after по конкретной позиции

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:disorder_after=-20
```

Хвост разрезается за 20 байт до конца payload. Последние 20 байт идут первыми, остальная часть хвоста — вторым.

### 7. Полный набор: midhost + disorder_after + шаблон

```bash
--payload=tls_client_hello \
  --lua-desync=hostfakesplit:tcp_md5:host=vk.com:midhost=midsld:disorder_after
```

Максимальная фрагментация: hostname разрезан пополам, хвост перевернут, фейки с правдоподобным `*.vk.com`.

### 8. Только один фейк (nofake2)

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:nofake2
```

Отправляется только fake1 (перед реальным hostname). Полезно если DPI реагирует именно на первый hostname в потоке.

### 9. Только второй фейк (nofake1)

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:nofake1
```

Отправляется только fake2 (после реального hostname). Для DPI, которые берут последний hostname.

### 10. С badsum на фейках

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:badsum
```

Фейковые сегменты получают невалидный TCP checksum. Сервер отбросит их, DPI (который часто не проверяет checksum) примет.

### 11. Многократные фейки (repeats)

```bash
--payload=tls_client_hello --lua-desync=hostfakesplit:tcp_md5:repeats=5
```

Каждый фейковый сегмент отправляется 5 раз. Реальные — по 1 разу. Итого: 1 before_host + 5 fake1 + 1 hostname + 5 fake2 + 1 after_host = 13 пакетов.

### 12. Комбинация: fake → hostfakesplit (два инстанса)

```bash
--payload=tls_client_hello \
  --lua-desync=fake:blob=fake_default_tls:tcp_md5:repeats=3:tls_mod=rnd,rndsni,dupsid \
  --lua-desync=hostfakesplit:tcp_md5:midhost=midsld:host=google.com
```

Сначала 3 фейковых TLS ClientHello (целиком), затем реальный — нарезанный по hostname с дополнительным разрезом по midsld и фейковым hostname `*.google.com`.

Проще говоря: инстанс [[Zapret2/desync/fake#Зачем нужен fake|fake]] отправляет отдельный фейковый ClientHello целиком (заготовленный blob `fake_default_tls`, см. [[Zapret2/desync/fake#Стандартные blob-ы|Стандартные blob-ы]]), а `tls_mod=rnd,rndsni,dupsid` меняет в нём случайные поля, SNI и Session ID ([[Zapret2/desync/fake#Опции tls_mod|Опции tls_mod]]).

### 13. Боевой пример для YouTube (TLS)

```bash
--filter-tcp=443 --hostlist=youtube.txt \
  --lua-desync=fake:blob=fake_default_tls:repeats=5:tcp_md5 \
  --lua-desync=hostfakesplit:tcp_md5:midhost=midsld:host=google.com
```

5 фейковых TLS ClientHello + реальный с hostname-фейками и разрезом по midsld.

Здесь `--filter-tcp=443` и `--hostlist=youtube.txt` — фильтры профиля (порт и список доменов, [[filter]] и [[hostlist]]). Похожий боевой пример с `multisplit` — [[Zapret2/desync/fake#17. Боевая связка: fake + multisplit для YouTube|в статье про fake]].

### 14. HTTP + TLS в одном конфиге

```bash
--filter-tcp=80 --hostlist=blocked.txt \
  --lua-desync=hostfakesplit:ip_ttl=3 \
--filter-tcp=443 --hostlist=blocked.txt \
  --lua-desync=hostfakesplit:tcp_md5:midhost=midsld
```

HTTP с TTL-fooling, TLS с md5sig и разрезом hostname.

`--filter-tcp=80` и `--filter-tcp=443` — фильтры по порту TCP: 80 обычно занят HTTP, 443 — HTTPS ([[filter]]). Между двумя блоками в примере нет `--new`, которым в Zapret 2 разделяют профили ([[filter]]); как движок разберёт два `--filter-tcp` подряд без разделителя, статья не уточняет. Если нужны два отдельных профиля, между блоками ставят `--new`.

### 15. С blob (отправка произвольного payload)

```bash
--blob=my_ch:@custom_clienthello.bin \
--payload=tls_client_hello \
  --lua-desync=hostfakesplit:tcp_md5:blob=my_ch
```

Вместо реального ClientHello отправляется кастомный blob (при условии, что в нём есть валидный SNI).

`--blob=my_ch:@custom_clienthello.bin` объявляет blob из файла; подробности — в заметке [[blob]].

### 16. Защита от отсутствующего blob

```bash
--payload=tls_client_hello \
  --lua-desync=hostfakesplit:tcp_md5:blob=maybe_missing:optional
```

Если blob не существует — тихий пропуск, без ошибок.

---

## 📚 См. также

- [[desync]] — обзор всех функций `--lua-desync` и общий контракт: кто вызывает функцию, что ей передаёт и как складываются вердикты
- [[жизненный цикл desync-функции]] — восемь стадий, общих для всех техник дурения: отсев транспорта, выбор данных, replay, вердикты
- [[структура desync и диссекта]] — устройство таблицы `desync` и диссекта пакета
- [[fakedsplit]] — разрез по одной произвольной позиции с фейковыми сегментами, заполненными паттерном; ближайший родственник `hostfakesplit`
- [[fakeddisorder]] — фейки и обратный порядок отправки
- [[multisplit]] — обычная последовательная сегментация без фейков; там же разобраны маркеры позиций и `seqovl`
- [[multidisorder]] — тот же разрез в обратном порядке
- [[tcpseg]] — отправка диапазона данных между двумя маркерами
- [[oob]] — сегментация с urgent-байтом
- [[fake]] — отдельный фейковый пакет, после которого чаще всего ставят `hostfakesplit`
- [[blob]] — как объявлять и передавать данные для аргумента `blob`
- [[payload]] — типы payload, от которых зависит, найдутся ли маркеры `host` и `endhost`
- [[ts-and-fooling]] — способы испортить фейк, чтобы сервер его отбросил
- [[verify-strategy]] — как честно проверить, работает ли подобранная комбинация
- [[последовательность аргументов]] — как выстраивается цепочка инстансов в профиле
- [[profile]] — где `--lua-desync` живёт среди остальных настроек
- [[DPI/DPI|DPI и ТСПУ]] — как устроена инспекция трафика, против которой работает `hostfakesplit`

---

> **Источники:** `lua/zapret-antidpi.lua:695-800`, `lua/zapret-lib.lua:396-401` (rawsend_opts/rawsend_opts_base), `lua/zapret-lib.lua:1287-1302` (genhost), `docs/manual.md:4208-4246` из репозитория zapret2.

---

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