todo/Zapret2/desync/hostfakesplit.md
loop-uh baf0091fa3
Some checks failed
Published content check / validate (push) Failing after 17s
git.zapret.moe → git2.zapret.moe во всём хранилище, защита от возврата старых имён
git.zapret.moe в России заблокирован по имени, как и wiki.zapret.moe. Ссылки
на Forgejo во всех заметках, футерах, wizard.html и главной заменены на
git2.zapret.moe (включая закодированные адреса значков shields.io).

Какое имя рабочее, теперь решают реестры хоста /etc/zapret-*-domains.conf через
zapret-site-names: pre-commit хук vault отклоняет добавленные строки с
заблокированным именем, сборка Quartz сама переводит ссылки и baseUrl на
рабочие имена. Правило в AGENTS.md переписано под этот механизм.

scripts/wiki-stats.py: переходы с любых имён вики и git (wikiN, gitN) больше
не считаются внешними сайтами. content-check: проверка зеркал сторонних
проектов узнаёт любое имя git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:51:32 -04:00

1099 lines
98 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-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).