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

29 KiB
Raw Permalink Blame History

date tags aliases
2026-08-16
zapret
zapret2
gui
preset
profile
exclusions
troubleshooting
Исключения
Исключения в Запрете
Есть ли в Zapret 2 строчка Исключения
Как добавить сайт в исключения Zapret
Куда вписать IP и домены чтобы Запрет их не трогал
Профиль pass
pass ничего не делает
hostlist-exclude и ipset-exclude

🚫 Исключения в Zapret 2: почему такой кнопки нет и как они устроены на самом деле

!

[!info] О чём заметка Отвечает на частый вопрос: «есть ли в Zapret2/Zapret2 строчка "Исключения", куда можно накидать 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 (флаги --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 сайты)». Это те соединения, которые не совпали ни с одним профилем пресета и прошли без обработки:

!

Проще говоря: движок — это одна очередь профилей с гарантированной заглушкой в конце. Исключения не образуют отдельной подсистемы — они просто занимают места в этой очереди (профиль pass) или меняют условия совпадения её элементов (exclude-списки).

Механизм 1: профиль со стратегией pass

Первый способ — завести отдельный профиль, который ловит трафик нужных ресурсов (через hostlist или ipset), но в качестве стратегии имеет pass — «ничего не делать». Такой профиль честно выигрывает конкуренцию за соединение по общим правилам (факт 1) и забирает его себе — до профилей ниже этот трафик уже не доходит.

Важный нюанс: такие пакеты всё равно проходят через Zapret. Их захватывает wf, движок прогоняет соединение по профилям и выбирает pass-профиль — просто на выходе пакеты не изменены, байт в байт. При этом pass — не выдумка GUI, а штатная функция Lua-библиотеки движка: она буквально не делает ничего, только пишет строку в debug-лог. Исключение не «прячет» трафик от программы, а прогоняет его через неё вхолостую.

[!note] pass по доменам не действует на самые первые пакеты Профиль, отбирающий трафик по hostlist, не участвует в выборе, пока движок не увидел имя хоста (SNI в TLS ClientHello). Поэтому SYN и все пакеты до ClientHello достаются другому профилю — и если у того есть стратегия «нулевой фазы» вроде syndata, она успеет отработать до того, как соединение перескочит на pass-профиль. У исключения по ipset такого лага нет: IP известен с первого пакета.

В GUI стратегия так и называется — pass (ничего не делает), выбирается в «Готовых стратегиях» профиля как любая другая:

!

Механизм 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 (ничего не делает), стоящий у вершины списка:

!

[!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: исходник этой заметки · скачать весь репозиторий одним zip-архивом.