todo/Zapret2/exclusions.md
loop-uh e933ad411d
Some checks failed
Published content check / validate (push) Failing after 4s
Ввести термин «десинхронизация» в заметке об исключениях
Авторский термин bol-van (отсюда --lua-desync); «дурение» — его же
разговорное название тех же атак, в документации оба.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 23:26:20 +03:00

148 lines
29 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-08-16
tags:
- zapret
- zapret2
- gui
- preset
- profile
- exclusions
- troubleshooting
aliases:
- Исключения
- Исключения в Запрете
- Есть ли в Zapret 2 строчка Исключения
- Как добавить сайт в исключения Zapret
- Куда вписать IP и домены чтобы Запрет их не трогал
- Профиль pass
- pass ничего не делает
- hostlist-exclude и ipset-exclude
---
# 🚫 Исключения в Zapret 2: почему такой кнопки нет и как они устроены на самом деле
![[exclusions-header-rkn-zapret-chan.png]]
> [!info] О чём заметка
> Отвечает на частый вопрос: «есть ли в [[Zapret2/Zapret2|Zapret 2]] строчка "Исключения", куда можно накидать IP-адреса и домены, чтобы программа их не трогала?» Короткий ответ — **такой строчки нет, и это сделано намеренно**. То, что пользователи называют исключениями, в Zapret 2 реализуется двумя разными механизмами: [[profile|профилем]] со стратегией `pass` и исключающими списками `--hostlist-exclude`/`--ipset-exclude`. Заметка разбирает оба, их разницу и подводные камни. Основа — разбор от разработчика Zapret 2 GUI в чате поддержки (август 2026); описанная механика движка сверена с исходным кодом `nfqws2` (файлы `nfq2/desync.c`, `nfq2/hostlist.c`, `nfq2/ipset.c`, Lua-библиотеки; август 2026).
## TL;DR
- **Глобального поля «Исключения» на весь [[preset|пресет]] не существует** — ни в GUI, ни в формате пресета. Есть только условия отбора трафика (включающие и исключающие) и профили со стратегиями.
- **Исключение делается двумя способами**: отдельный профиль со стратегией `pass` («ничего не делать») ИЛИ исключающий список `--hostlist-exclude`/`--ipset-exclude` внутри профиля.
- Логика у них **разная**, но результат в типичном пресете **одинаковый**: пакеты ресурса идут как есть, напрямую, без [[desync|дурения]].
- Исключение — это **не особая сущность, а обычный профиль** (или условие в нём). Поэтому на него действуют общие правила: [[profile#Порядок профилей важен|порядок «выше = главнее»]], first-match-wins, конфликты списков.
- Практический рецепт: создать [[add-profile|новый профиль]], выбрать стратегию `pass (ничего не делает)`, накидать в его список нужные домены или IP и поставить профиль **выше** остальных.
## Откуда берётся вопрос про «строчку Исключения»
Привычка приходит из других программ: в VPN-клиентах и файрволах обычно есть одно глобальное поле «исключения», действующее на всё приложение сразу. Пользователь открывает настройки пресета Zapret 2, ищет такое же поле, не находит — и спрашивает, куда вписывать адреса.
По объяснению разработчика Zapret 2 GUI (чат поддержки, август 2026), понятия «исключение на весь пресет» в движке просто не существует. [[preset|Пресет]] — это набор [[profile|профилей]], каждый из которых сам решает, какой трафик он ловит ([[filter|фильтры]] и списки) и что с ним делает ([[desync|стратегия]]). «Нельзя нигде в пресете написать исключения — есть только включающее или исключающее условие»: `--hostlist`/`--ipset` против `--hostlist-exclude`/`--ipset-exclude`. GUI сознательно спроектирован без отдельной кнопки «Исключения» — чтобы новичок с самого начала мыслил профилями, а не воображаемым глобальным списком: исключения лежат среди обычных профилей, где их можно двигать, менять им стратегию и списки.
Проще говоря: в Zapret 2 «исключить сайт» — значит не «вписать его в особое поле», а «сделать так, чтобы его пакеты прошли через программу без изменений». А этого можно добиться двумя способами, у которых под капотом разная механика.
> [!note] Единственное «глобальное» исключение живёт уровнем ниже профилей
> Отсечь трафик от программы целиком можно только на слое захвата — глобальным фильтром [[wf|WinDivert]] (флаги `--wf-*` в шапке пресета): такой трафик вообще не попадает в движок. Но это фильтр по портам и IP-адресам — исключить **домен** на этом уровне невозможно, поэтому для сайтов остаются два механизма, разобранные ниже.
## Что вообще называют исключением
Исключение — это ресурс (домен или IP-диапазон), чей трафик должен пройти **мимо дурения**: без фейковых пакетов, нарезки и прочих манипуляций. Точный авторский термин для этих манипуляций — **десинхронизация** (desync): так bol-van, автор движка, называет атаки на DPI, при которых поток пакетов формируется так, что DPI собирает из него искажённую картину и «рассинхронизируется» с тем, что реально видит сервер; отсюда и имя флага стратегии `--lua-desync`. «Дурение» — его же, более разговорное название тех же приёмов (в официальной документации есть разделы «Дурение http» и «Дурение udp»), в заметках оба слова используются как синонимы; полный разбор стратегий — [[desync]]. Зачем нужны исключения: Zapret обходит DPI, намеренно «портя» исходящие пакеты, но незаблокированному сайту такие манипуляции только вредят — настоящий сервер может принять фейки за мусор и сбросить соединение (подробно — [[symptom-not-cause|«не работает» — это симптом]]). Типичный пример — российские сайты: в стоковых пресетах для них заведён профиль «Исключения (RU сайты)», который стоит в начале списка.
## Как движок выбирает профиль: три факта, без которых исключения не понять
Оба механизма исключений опираются на одну и ту же процедуру выбора профиля. Полный справочник фильтров — в [[filter|отдельной заметке]]; здесь — тот минимум, на который дальше ссылается весь текст.
**Факт 1: сверху вниз, побеждает первый.** Для каждого нового соединения движок перебирает [[profile|профили]] пресета по порядку, сверху вниз, и берёт **первый**, у которого совпали все фильтры: порты, протокол, списки доменов и IP. На этом поиск заканчивается — два подходящих профиля никогда не работают «вместе», всегда побеждает тот, что выше (first-match-wins). Отсюда главное правило исключений: их профиль должен стоять выше конкурентов.
**Факт 2: выбор кэшируется и пересматривается максимум дважды.** Найденный профиль запоминается в записи соединения (conntrack — таблица активных соединений, которую движок ведёт у себя), поэтому заново профиль **не** ищется на каждый пакет. Пересмотр случается не больше двух раз за соединение: когда движок распознаёт протокол потока (L7) и когда узнаёт имя хоста из SNI. И важная деталь: пересмотр — это не «продолжить поиск вниз с текущего места», а **полный поиск заново с самого верха списка**. Профиль с неизменными условиями (например, отбирающий по IP) при пересмотре выигрывает повторно — это ключ к подводному камню со смешением IP и доменов, разобранному ниже. Полная схема — в [[схема обработки трафика|схеме обработки трафика]].
**Факт 3: в конце каждого пресета стоит скрытый профиль `no_action`.** Его нет ни в файле пресета, ни в настройках GUI — движок при запуске сам дописывает его последним в список, под служебным номером 0. Фильтров у него нет, поэтому он совпадает с любым трафиком; стратегий у него тоже нет, поэтому он ничего не делает. Именно этот профиль подбирает всё, что не совпало ни с одним видимым профилем, — поэтому «не попасть никуда» в Zapret 2 означает то же, что попасть в исключение: трафик уходит нетронутым. По сути `no_action` — это встроенное «исключение по умолчанию» для всего, что пресет не покрывает.
Увидеть `no_action` вживую можно в логах: [[log-analyzer|анализатор логов]] показывает его в колонке «Профиль» как `0 (no_action)` — наравне с обычными профилями вроде «Исключения (RU сайты)». Это те соединения, которые не совпали ни с одним профилем пресета и прошли без обработки:
![[log-no-action-profile.png]]
Проще говоря: движок — это одна очередь профилей с гарантированной заглушкой в конце. Исключения не образуют отдельной подсистемы — они просто занимают места в этой очереди (профиль `pass`) или меняют условия совпадения её элементов (exclude-списки).
## Механизм 1: профиль со стратегией `pass`
Первый способ — завести отдельный профиль, который **ловит** трафик нужных ресурсов (через [[hostlist]] или [[ipset]]), но в качестве стратегии имеет `pass` — «ничего не делать». Такой профиль честно выигрывает конкуренцию за соединение по общим правилам (факт 1) и забирает его себе — до профилей ниже этот трафик уже не доходит.
Важный нюанс: такие пакеты **всё равно проходят через Zapret**. Их захватывает [[wf|WinDivert]], движок прогоняет соединение по профилям и выбирает `pass`-профиль — просто на выходе пакеты не изменены, байт в байт. При этом `pass` — не выдумка GUI, а штатная функция Lua-библиотеки движка: она буквально не делает ничего, только пишет строку в debug-лог. Исключение не «прячет» трафик от программы, а прогоняет его через неё вхолостую.
> [!note] `pass` по доменам не действует на самые первые пакеты
> Профиль, отбирающий трафик по [[hostlist|хостлисту]], не участвует в выборе, пока движок не увидел имя хоста (SNI в TLS ClientHello). Поэтому SYN и все пакеты до ClientHello достаются другому профилю — и если у того есть стратегия «нулевой фазы» вроде [[syndata]], она успеет отработать до того, как соединение перескочит на `pass`-профиль. У исключения по [[ipset|ипсету]] такого лага нет: IP известен с первого пакета.
В GUI стратегия так и называется — `pass (ничего не делает)`, выбирается в «Готовых стратегиях» профиля как любая другая:
![[pass-strategy-gui.png]]
## Механизм 2: исключающие списки `--hostlist-exclude` / `--ipset-exclude`
Второй способ — исключающее условие **внутри** профиля: «этот профиль срабатывает для всех, **кроме** доменов/IP из такого-то списка». Флаги `--hostlist-exclude` (домены) и `--ipset-exclude` (IP-диапазоны) — это те же фильтры отбора, что и обычные `--hostlist`/`--ipset`, только с обратным знаком (полный справочник фильтров — [[filter]]). Проверяются exclude-списки первыми и имеют приоритет: если ресурс попал и во включающий, и в исключающий список одного профиля, побеждает исключение.
Чаще всего exclude-списки встречаются в широких профилях-перехватчиках вида «Все сайты»: у такого профиля вообще нет включающего списка, есть только исключающие — поэтому он действует на весь трафик своих портов за вычетом исключений (признак разобран в [[find-game-strategy|подборе стратегии для игр]]). В стоковых пресетах так исключают российские ресурсы (`ipset-ru.txt`), DNS-серверы (`ipset-dns.txt`) и ручной список пользователя. Нюанс: непустой `--hostlist-exclude` сам делает профиль «доменным» — пока имя хоста неизвестно, такой профиль в выборе не участвует; `--ipset-exclude` работает с первого пакета, потому что IP известен сразу.
Механика отличается от `pass` принципиально: исключённый трафик этот профиль **не забирает** — для движка профиль просто «не совпал», и поиск продолжается по профилям ниже. Если не совпал вообще ни один видимый профиль, соединение подбирает скрытый профиль `no_action` (факт 3 выше) — и трафик уходит нетронутым.
> [!note] Исключение из правила — профиль с автохостлистом
> В профиле с `--hostlist-auto` (автоматическое пополнение списка заблокированных доменов) логика другая: при известном имени хоста такой профиль выигрывает выбор **безусловно**, и `--hostlist-exclude` в нём не отдаёт трафик профилям ниже, а лишь запрещает применять стратегию — то есть по наблюдаемому эффекту ведёт себя как `pass`. На `--ipset-exclude` это не распространяется: он проверяется раньше и честно «роняет» профиль. Нюанс актуален для ручных конфигураций с автохостлистом.
## Разная логика — одинаковый результат. Но не всегда
Разработчик GUI формулирует это так: два вида исключений с разной логикой, но одинаковым результатом — пакеты сайта идут как есть, напрямую, без дурения. Сравнение:
| | Профиль со стратегией `pass` | `--hostlist-exclude` / `--ipset-exclude` |
|---|---|---|
| Что это | Отдельный [[profile\|профиль]] | Условие внутри чужого профиля |
| Что происходит с трафиком | Профиль **забирает** соединение и ничего с ним не делает | Профиль **пропускает** трафик — тот идёт к профилям ниже |
| Поиск профиля | Останавливается (first-match-wins) | Продолжается вниз по списку |
| Где встречается | «Исключения (RU сайты)» в начале пресета | Широкие профили «Все сайты» |
Отсюда видно, когда результат перестаёт быть одинаковым. `pass`-профиль — жёсткая точка для трафика, который он реально поймал: пока соединение за ним, оно гарантированно едет без обхода (про лаг доменных `pass`-профилей на первых пакетах — callout выше). Exclude-список — мягкая точка: он лишь выводит трафик из-под **одного** профиля, и если ниже по списку есть другой профиль, чьи фильтры совпадут, трафик достанется ему — со всем его дурением. Поэтому относиться к exclude-списку как к «глобальному исключению» нельзя: он локален для своего профиля.
## Как сделать исключение в GUI: чек-лист
Совет из чата поддержки (август 2026): достаточно создать в любом пресете новый профиль со стратегией `pass` и накидать туда нужные домены и IP. По шагам:
- [ ] Создайте новый профиль — пошагово в [[add-profile|«Как добавить свой профиль»]] (там же — про бэкап пресета).
- [ ] В списке готовых стратегий профиля выберите `pass (ничего не делает)`.
- [ ] Наполните список профиля: домены — в [[hostlist|хостлист]], IP-диапазоны — в [[ipset|ипсет]]. Не смешивайте их бездумно (см. подводные камни ниже).
- [ ] Кнопкой «Порядок в пресете» поднимите профиль **выше** тех профилей, которые могли бы перехватить этот же трафик (обычно — в самый верх).
- [ ] Проверьте, что исключённый ресурс открывается, а остальной обход не отвалился ([[verify-strategy|как проверить стратегию]]).
Экран «Порядок в пресете» прямо напоминает правило: профиль выше в списке имеет больший приоритет. Здесь же видно, что «Исключения (RU сайты)» — это обычный профиль со стратегией `pass (ничего не делает)`, стоящий у вершины списка:
![[profile-order-gui.png]]
> [!note] «Исключение» никак не защищено — это просто профиль, у которого сейчас выбран `pass`
> Обратная сторона такой архитектуры: GUI не запрещает открыть профиль-исключение и выбрать ему любую другую стратегию — тогда «исключение» молча превратится в обычный дурящий профиль. Работает и в обратную сторону: любой профиль становится исключением заменой его стратегии на `pass`. Для движка все профили равны, статус «исключения» существует только в голове пользователя. Удачна ли такая свобода — вопрос открытый; помнить о ней стоит хотя бы потому, что случайная смена стратегии у профиля «Исключения (RU сайты)» включит [[desync|дурение]] сразу для всех российских сайтов из его списка.
## Подводные камни
Все они — следствия одного факта: исключение — это обычный профиль, а не глобальная настройка.
**Порядок решает.** Исключения не действуют «на весь пресет сверху» — они конкурируют за трафик наравне с остальными профилями, по правилу [[profile#Порядок профилей важен|«выше = главнее»]]. `pass`-профиль ниже «портящего» профиля бесполезен: трафик заберут раньше него.
**Сайт в двух профилях.** Если добавить домен в исключение, но он уже сидит в списке другого профиля (или наоборот — добавили в рабочий профиль, а из исключений не убрали), сработает тот профиль, что выше, — и результат может быть противоположен ожидаемому. Держите каждый ресурс в одном профиле.
**Смешение IP и доменов.** Профиль с [[ipset|ипсетом]] матчится по IP удалённой стороны — тот известен с первого пакета, тогда как профиль с [[hostlist|хостлистом]] до появления имени хоста в выборе вообще не участвует. Когда движок наконец узнаёт имя (из SNI), он ищет профиль заново — но, как описано в факте 2, снова сверху вниз, и верхний IP-профиль, чьи условия не изменились, опять совпадает первым. Поэтому нижний профиль с **доменом** того же ресурса не сработает никогда — именно об этом предупреждение разработчика GUI: «профили будут матчиться по айпи, а твой домен ниже не заработает». Практическое правило: доменные профили ставьте **выше** IP-профилей — тогда до раскрытия имени соединение обслуживает нижний IP-профиль, а после раскрытия штатно перескакивает на верхний доменный.
**`pass` не «защищает» сайт — он отключает обход.** Если `pass`-профиль с широким фильтром перехватит трафик **заблокированного** сайта, тот поедет к DPI с открытым SNI и умрёт. Классический случай «включил исключения — отвалился YouTube» разобран в [[profile-independence|«Профили не влияют друг на друга»]]: стратегия YouTube при этом не ломается, она просто не запускается, потому что соединение забрал верхний `pass`-профиль.
## 📚 См. также
- [[profile|Что такое профиль]] — анатомия профиля, раздел про профиль-исключение `pass` и порядок профилей
- [[profile-independence|Профили не влияют друг на друга]] — почему «включил исключение — сломался другой сайт» это не глюк
- [[filter|Фильтры профиля]] — как движок выбирает профиль; `--hostlist-exclude`/`--ipset-exclude` среди прочих фильтров
- [[hostlist]] · [[ipset]] — списки доменов и IP, которыми наполняются исключения
- [[add-profile|Как добавить свой профиль]] — практика создания профиля в GUI
- [[find-game-strategy|Как найти стратегию для игры]] — широкие профили «Все сайты» с exclude-списками в деле
- [[symptom-not-cause|«Не работает» — это симптом]] — когда сайту нужен `pass`, а когда обход
- [[preset|Пресет]] — контейнер, в котором живут все эти профили
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/Zapret2/exclusions.md) · [скачать весь репозиторий одним zip-архивом](https://git.zapret.moe/zapretdiscordyoutube/todo/archive/main.zip).