---
date: 2026-07-17
tags:
  - zapret
  - zapret2
  - nfqws2
  - conntrack
  - dissect
  - pipeline
  - architecture
link: https://github.com/bol-van/zapret2/blob/master/docs/manual.md
aliases:
  - Схема обработки трафика Zapret 2
  - Конвейер обработки пакета nfqws2
  - Путь пакета в nfqws2
  - Как nfqws2 обрабатывает пакет
---
# 🔄 Схема обработки трафика в Zapret 2

> [!info] О чём заметка
> Полный путь одного пакета внутри [[Zapret2/Zapret2|Zapret 2]]: от перехвата из ядра ОС до финального вердикта «отправить / изменить / выбросить». Разобрана каждая стадия конвейера `nfqws2` — диссекция, conntrack, распознавание протокола, реконструкция, выбор профиля, вызов Lua-инстансов и агрегация вердиктов. Изложено проще, чем в официальной документации, но без потери деталей. Про состав проекта (какие вообще есть компоненты) — соседняя заметка [[структура проекта]]; про сам механизм `--lua-desync` — [[desync]].

## TL;DR

- Единица обработки — **IP-пакет**. Ядро ОС отдаёт нужные пакеты в `nfqws2` (процесс пространства пользователя), и это дорого — поэтому лишнее отсекают как можно раньше.
- Каждый пакет проходит конвейер: **диссекция** (разбор по слоям) → **conntrack** (привязка к потоку) → **определение типа пейлоада и протокола** → при нужде **реконструкция из нескольких пакетов** → **выбор профиля по фильтрам** → **вызов Lua-инстансов** → **итоговый вердикт**.
- **Профиль** выбирается по фильтрам (L3/L4/L7, порты, ipset, хостлисты) один раз и кэшируется в записи потока; пересматривается максимум дважды — при распознавании L7-протокола и при обнаружении имени хоста.
- **Инстанс** — один вызов Lua-функции из профиля (через `--lua-desync`). Инстансы выполняются строго по порядку; каждый возвращает вердикт `PASS` / `MODIFY` / `DROP`, и они агрегируются.
- Ради экономии CPU есть механизмы **cutoff** (инстанс/направление/весь поток отключаются от Lua) и режим **оркестратора** — инстанса, который сам управляет вызовом остальных.

---

## Стадия 0. Зачем пакет вообще передаётся в nfqws2

Сети работают с **IP-пакетами**, поэтому единица обработки — именно пакет. Приёмом и отправкой пакетов занимается сетевая подсистема в ядре операционной системы. А `nfqws2` работает **не в ядре** (kernel mode), а как обычный процесс пространства пользователя (user mode). Значит, первый шаг — «вытащить» нужные пакеты из ядра и передать их в процесс `nfqws2`.

Все четыре средства перехвата (Linux — `iptables`/`nftables`, FreeBSD — `ipfw`, OpenBSD — `pf`, Windows — WinDivert; подробнее в [[структура проекта]]) умеют предварительно фильтровать пакеты ещё на стороне ядра. Максимальные возможности фильтрации — в Linux.

> [!important] Почему ранняя фильтрация критична
> Передача пакета из ядра в user mode и обратно сопряжена со **значительными накладными расходами** (это переключение контекста между ядром и процессом на каждый пакет). Чем больше ненужного трафика отсечь прямо на перехвате, тем меньше пакетов пройдёт этот дорогой путь — и тем ниже нагрузка на процессор. Поэтому правило: отсекай лишнее как можно раньше.

## Стадия 1. Диссекция — разбор пакета на части

Пакет пришёл в `nfqws2`. Первое, что тот делает, — разбирает его по уровням модели OSI: выделяет заголовки `ip`, `ipv6`, `tcp`, `udp` и поле данных (payload). Это называется **диссекцией**, а её результат — **диссект** (`dis`): представление пакета в виде структур, к полям которых можно обращаться по имени.

> [!note] Проще говоря
> Пришедший пакет — это сплошная лента байт. Диссекция «раскладывает» её по полочкам: вот IP-заголовок, вот TCP-заголовок, вот прикладные данные. После этого Lua-код и C-ядро могут читать и менять конкретные поля (например, TCP sequence number), а не считать байты вручную. Модель OSI — стандартное деление сети на уровни: IP — сетевой уровень, TCP/UDP — транспортный, данные приложения — выше.

## Стадия 2. conntrack — привязка пакета к потоку

Дальше включается встроенная в `nfqws2` подсистема **conntrack** (connection tracking, отслеживание соединений) — система учёта **потоков** поверх отдельных пакетов. По данным уровней L3/L4 пакета (адреса, порты, протокол) ищется уже существующая запись о потоке; если её нет — создаётся новая. Старые записи, по которым давно нет активности, удаляются.

Что conntrack хранит и делает для каждого потока:

- отслеживает **логическое направление** каждого пакета (входящий / исходящий);
- ведёт **подсчёт** прошедших пакетов и байт в обе стороны;
- следит за **sequence numbers** для TCP;
- при необходимости используется для **сборки сообщений**, которые передаются несколькими пакетами (см. стадию 4).

> [!note] Проще говоря
> Отдельный пакет сам по себе мало что говорит. conntrack связывает пакеты в «разговор» (поток) между двумя сторонами и помнит про него контекст: сколько всего прошло, в какую сторону, на каком месте потока мы сейчас. Этот контекст нужен почти всем дальнейшим решениям.

## Стадия 3. Тип пейлоада и протокол потока

По сигнатурам определяется **тип пейлоада** — что за содержимое в этом конкретном пакете (или группе пакетов): TLS ClientHello, HTTP-запрос, QUIC Initial и т. д. (см. [[payload]]). На основании типа пейлоада определяется **тип протокола всего потока**, и он закрепляется за потоком до конца его существования.

Тонкость: в рамках одного потока могут идти **разные типы пейлоадов**. Например, поток протокола XMPP обычно несёт и специфические XMPP-сообщения, и сообщения, связанные с TLS. Тип протокола потока при этом остаётся XMPP, но отдельные пакеты получают разные типы пейлоада — как известные, так и неизвестные. Нераспознанный пейлоад помечается типом **`unknown`**.

> [!note] Проще говоря
> «Протокол потока» — это ярлык на весь разговор целиком (он ставится один раз и не меняется). «Тип пейлоада» — ярлык на конкретный пакет внутри этого разговора (у разных пакетов он может отличаться). Многие приёмы обхода реагируют именно на тип пейлоада конкретного пакета.

## Стадия 4. Реконструкция сообщения из нескольких пакетов (reasm / decrypt)

Иногда одно логическое сообщение не помещается в один пакет (классический пример — большой TLS ClientHello с post-quantum ключами). Если для конкретного пейлоада и протокола потока выясняется, что нужна **реконструкция из нескольких пакетов**, `nfqws2`:

1. начинает **накапливать** эти пакеты, привязывая их к записи в conntrack;
2. **запрещает их немедленную отправку** (держит, пока не соберёт всё);
3. после приёма всех частей выполняет **реконструкцию**, а при необходимости — **дешифровку** составного пейлоада.

Дальнейшие решения принимаются уже на базе полностью собранного пейлоада — он называется **reasm** (reassembled, реассемблированный), а результат сборки с расшифровкой — **decrypt**.

> [!note] Проще говоря
> Если сообщение «разрезано» сетью на несколько пакетов, `nfqws2` придерживает их, склеивает обратно в целый пейлоад (reasm) и только тогда даёт стратегиям работать с полной картиной. Иначе, например, домен в SNI мог бы оказаться разорван между пакетами, и его нельзя было бы корректно найти или разрезать.

## Стадия 5. Классификация по профилям

Когда информация о пейлоаде получена, наступает очередь **системы классификации по профилям**. [[profile|Профиль]] — это набор из **фильтров** (кому он адресован) и **команд-действий** (что делать). Профили фильтруются по:

- **L3** — версия IP-протокола, номер IP-протокола, `ipset`-ы (списки IP-адресов);
- **L4** — порты TCP или UDP, `type`/`code` для ICMP;
- **L6/L7** — тип протокола потока, списки доменов (хостлисты).

**Правило выбора:** профили сканируются строго по порядку, от первого к последнему. При первом же совпадении фильтра этот профиль выбирается, и сканирование прекращается. Если не подошёл ни один — берётся **профиль по умолчанию**, в котором нет никаких действий (то есть с трафиком ничего не делается).

> [!important] Кэширование и переключение профиля
> Выбранный профиль **кэшируется в записи conntrack**, поэтому для каждого следующего пакета потока поиск заново не выполняется — это экономит CPU. Повторный поиск запускается только при изменении исходных данных, а таких изменений всего два: **обнаружение L7-протокола** и **обнаружение имени хоста**. В этих случаях профиль может быть пересмотрен и переключён. За всё время жизни потока таких переключений может быть **не более двух** — ровно потому, что изменяемых параметра всего два.

## Стадия 6. Инстансы — вызовы Lua-функций внутри профиля

Профиль выбран. За его действия отвечают **Lua-функции**, и их в профиле может быть произвольное количество. Каждый отдельный вызов Lua-функции из профиля называется **инстансом** (экземпляром вызова).

Зачем нужно понятие инстанса: одна и та же функция может вызываться несколько раз с разными параметрами. Поэтому инстанс идентифицируется парой «номер профиля + порядковый номер вызова внутри профиля». Инстансы задаются флагами [[desync|`--lua-desync`]], и каждый получает свой набор произвольных параметров.

> [!important] Порядок вызова имеет значение
> Инстансы выполняются **строго в том порядке**, в каком заданы флаги `--lua-desync`. Этот порядок принципиален для логики стратегии: например, сначала отправить [[fake|фейковый пакет]], потом нарезать реальный — и наоборот это уже другая стратегия. Подробнее про очерёдность — [[последовательность аргументов]].

### Внутрипрофильные фильтры

Кроме фильтров выбора профиля есть ещё **внутрипрофильные фильтры** — они сужают, какие пакеты дойдут до конкретных инстансов. Их три типа:

- **`--payload`** — список типов пейлоада, которые инстанс принимает (см. [[payload]]);
- **`--in-range`** и **`--out-range`** — два диапазонных фильтра, задающих диапазон позиций внутри потока, интересный инстансу (см. [[out-range]]).

Определённый внутрипрофильный фильтр действует на **все последующие** инстансы — до тех пор, пока его не переопределят. Главный смысл этих фильтров — **сократить число относительно медленных вызовов Lua**, приняв максимум решений на быстрой стороне C-кода.

## Стадия 7. Что происходит внутри Lua-инстанса

Пакет дошёл до Lua-инстанса. Функция имеет два параметра — `ctx` и `desync`:

- **`ctx`** — контекст для связи с некоторыми функциями на стороне C-кода (через него, например, реально отправляются пакеты);
- **`desync`** — таблица со множеством параметров обрабатываемого пакета. Прежде всего это диссект (подтаблица **`dis`**) и информация из записи conntrack (подтаблица **`track`**). Есть и ряд других параметров — увидеть их все можно, выполнив `var_debug(desync)` или просто подключив готовый инстанс `pktdebug`.

Что ещё доступно инстансу:

- **Информация о replay.** Если идёт перепроигрывание задержанных пакетов (replay — повторная прогонка накопленных при реконструкции частей), инстанс получает номер части, общее количество частей исходного сообщения, позицию текущей части и `reasm`/`decrypt` при наличии.
- **Хранение состояния между пакетами.** Для данных, не относящихся к текущему пакету, доступна таблица `desync.track.lua_state` — в ней можно хранить любую информацию, привязанную к записи conntrack: при каждом новом пакете потока Lua получает **ту же самую** таблицу.
- **Передача данных между инстансами.** Саму таблицу `desync` можно использовать для временных данных в рамках обработки текущего пакета. Следующие инстансы получают ту же `desync` и потому могут принять данные от предыдущих.

Механику `desync`, `ctx` и полей на конкретном примере разбирает заметка [[multisplit]] (раздел про вход и выход функции).

## Стадия 8. Вердикты и их агрегация

Инстанс может создавать копии текущего диссекта, вносить в них изменения, генерировать собственные диссекты и отправлять их через вызовы C-кода. Итог работы каждого инстанса — **вердикт**:

- **`VERDICT_PASS`** — ничего не делать с текущим диссектом;
- **`VERDICT_MODIFY`** — в конце всей цепочки отправить модифицированное содержимое диссекта;
- **`VERDICT_DROP`** — выбросить (дропнуть) текущий диссект.

Вердикты всех инстансов профиля **агрегируются** по правилу приоритета: `MODIFY` замещает `PASS`, а `DROP` замещает и `PASS`, и `MODIFY`. То есть достаточно одному инстансу вернуть `DROP` — и оригинальный пакет будет выброшен, что бы ни вернули остальные.

> [!note] Проще говоря
> Почему `DROP` сильнее всех: приёмы вроде [[multisplit|нарезки]] уже сами отправили нужные данные отдельными пакетами, поэтому оригинал надо выбросить, иначе сервер получит данные дважды. `DROP` перебивает `MODIFY` и `PASS`, чтобы такое дублирование было невозможно.

## Стадия 9. Cutoff и оркестратор — экономия и динамика

Чтобы не гонять через Lua лишние пакеты, у инстанса есть несколько способов «отключиться»:

- **instance cutoff** — инстанс сам себя отключает от дальнейших пакетов потока по направлению in/out;
- **lua cutoff** — отключает направление in/out текущего потока от **всей** Lua-обработки;
- **отмена дальнейшей цепочки** — инстанс может запросить прекращение всех оставшихся вызовов Lua по текущему диссекту.

Инстанс, который берёт на себя такую отмену, становится **координатором дальнейших действий** — его называют **оркестратором** (см. [[оркестратор]]). C-код передаёт ему план дальнейшего выполнения — все фильтры профиля и параметры вызова всех оставшихся инстансов, — и оркестратор сам решает, когда и при каких условиях их вызывать, не вызывать или менять их параметры. Так реализуются **динамические сценарии без переписывания основного кода стратегии** — например, распознать блокировку ресурса и сменить стратегию, если предыдущая не сработала.

> [!important] Признак «lua cutoff» как оптимизация
> Если все инстансы профиля вошли в состояние cutoff по текущему потоку, либо текущая позиция потока ушла за верхнюю границу range-фильтров, значит по этому потоку Lua-вызовов больше не будет. C-код помечает такой поток специальным признаком **«lua cutoff»**, который проверяется максимально быстро, без единого вызова Lua. Так экономятся ресурсы процессора: «отработавшие» потоки дальше идут по быстрому пути.

## Стадия 10. Итоговый вердикт и следующий пакет

После выполнения всей цепочки инстансов профиля C-код получает **итоговый агрегированный вердикт** — что делать с текущим диссектом: отправить как есть (`PASS`), отправить модифицированный вариант (`MODIFY`) или выбросить (`DROP`).

На этом обработка пакета завершается: `nfqws2` переходит к ожиданию следующего пакета, и весь цикл повторяется заново.

> [!summary] Весь путь пакета одной строкой
> Ядро ОС → перехват и ранняя фильтрация → диссекция → conntrack → тип пейлоада и протокол потока → (при нужде) реконструкция reasm/decrypt → выбор профиля по фильтрам → цепочка Lua-инстансов с их вердиктами → агрегированный вердикт → отправить / изменить / дропнуть → ждать следующий пакет.

## 📚 См. также

- [[структура проекта|Структура проекта Zapret 2]] — из каких компонентов состоит инструмент (парная заметка: там «что есть», здесь «как работает»)
- [[Zapret2/Zapret2|Что такое Zapret 2]] — обзор и определение автора
- [[desync|Механизм --lua-desync]] — как задаются и вызываются инстансы
- [[структура desync и диссекта|Прототип и структура desync]] — полный разбор полей `desync`, диссекта, `track`, reasm/replay, вердиктов
- [[profile|Профили]] и [[preset|Пресеты]] — как описываются стратегии
- [[filter|Фильтры]] · [[payload|Тип payload]] · [[out-range|Диапазонные фильтры in/out-range]] — детали классификации
- [[оркестратор|Оркестрация]] — динамический выбор стратегии
- [[multisplit]] — конкретный пример инстанса: что он получает в `desync` и что возвращает
- [[последовательность аргументов]] — почему порядок инстансов важен
- 🔗 [Официальная документация (docs/manual.md)](https://github.com/bol-van/zapret2/blob/master/docs/manual.md) — первоисточник

---

> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/%D1%81%D1%85%D0%B5%D0%BC%D0%B0%20%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B8%20%D1%82%D1%80%D0%B0%D1%84%D0%B8%D0%BA%D0%B0.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).
