Сборка берёт последний стабильный bol-van/zapret2 и останавливается, если он новее проверенного. Вышел v1.0.6; проверены изменения v1.0.5–v1.0.6 (docs/changes.txt, manual.md, nfq2/protocol.h): - новые опции --filter-mark, --filter-ssid-neg (--ssid-filter-neg, --nlm-filter-neg — Windows): валидатор пресетов работает по белому списку и по-прежнему отвергает их как UNKNOWN_OPTION; - нотация --in-range/--out-range расширена (<mode>[<int>][(-|<)<mode>[<int>]]), прежняя запись — её частный случай; валидатор передаёт значение как есть; - --wf-tcp-empty — только WinDivert, на Android запрещено (WINDOWS_OPTION_FORBIDDEN); - список L7-протоколов (t_l7proto) не изменился; - остальное — blockcheck2, оптимизации поиска профиля, разбор URL и QUIC в Lua. pinned-release.json: тег v1.0.6, коммит 221d7dff, SHA-256 архива и sha256sum.txt совпадают с digest GitHub Release. fetch-release.sh проходит. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 KiB
Единый контракт «Пресет → Профиль → Стратегия»
Статус: проект целевой архитектуры
Версия контракта: 1
Область действия: root-модуль Zapret2 для Android, nfqws2, Android-приложение и обновления модуля
1. Назначение
Этот документ задаёт единую модель конфигурации Zapret2 и заменяет параллельные понятия «категории», «глобальная стратегия», «TXT-пресет» и «custom cmdline».
Целевая модель:
Пресет
├── общие параметры запуска
├── профиль 1
│ ├── имя
│ ├── состояние: включён/выключен
│ ├── фильтры трафика
│ └── стратегия обработки
├── профиль 2
└── ...
В нормативных разделах слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ и МОЖЕТ описывают обязательное или рекомендуемое поведение реализации.
2. Термины
2.1. Пресет
Пресет — полный переносимый набор параметров запуска Zapret2.
Пресет содержит:
- метаданные файла;
- общие параметры
nfqws2, применяемые ко всему запуску; - загрузку Lua-файлов и blob-данных;
- упорядоченный список профилей.
В каждый момент времени активен ровно один пресет. Именно он является
единственным источником фильтров и стратегий для нового процесса nfqws2.
2.2. Профиль
Профиль — одно правило внутри пресета: какой трафик выбрать и как его обработать.
Профиль содержит четыре логические части:
- имя —
--name=...; - состояние — наличие или отсутствие
--skip; - фильтры и границы обработки;
- одну стратегию, представленную упорядоченной цепочкой
--lua-desync.
Профили выполняются в порядке, записанном в пресете, и разделяются отдельной
строкой --new.
2.3. Стратегия
Стратегия — конкретный способ обработки уже выбранного профилем трафика.
В формате nfqws2 стратегия — одна или несколько строк --lua-desync=... внутри
одного профиля. Несколько таких строк образуют одну упорядоченную цепочку, а не
несколько независимых профилей.
Стратегия --lua-desync=pass является допустимой явной стратегией «ничего не
изменять».
Готовая стратегия из каталога — только шаблон для редактора. При выборе её
упорядоченные строки --lua-desync копируются в профиль активного пресета.
Идентификатор каталога не записывается в пресет и никогда не передаётся
nfqws2. Поэтому каталог не является вторым источником runtime-конфигурации.
2.4. Общие параметры
Общие параметры располагаются до первого --name=.... Они не принадлежат
первому профилю и применяются ко всему процессу.
К ним относятся, например:
--lua-init=...;--blob=...;--ctrack-disable=...;- другие явно разрешённые глобальные параметры Android-сборки
nfqws2.
Параметры жизненного цикла и изоляции модуля — номер NFQUEUE, mark, UID/GID, pidfile — образуют системную оболочку запуска. Они принадлежат модулю, а не пользовательской стратегии, и безопасно добавляются вокруг активного пресета.
Все пользовательские и файловые blob-объявления, необходимые стратегиям
пресета, находятся в этом глобальном блоке самого TXT. Внешнего общего каталога
blob-объявлений нет. Встроенные имена blob-ов самого nfqws2 не требуют строки
--blob и не считаются отдельным источником конфигурации.
Все 98 встроенных пресетов содержат одинаковый полный канонический blob-блок.
Импортёр строит его как непротиворечивое объединение объявлений исходного набора,
сортирует по имени blob и физически записывает в каждый TXT. Два разных значения
одного имени являются ошибкой импорта. Это правило гарантирует доступность
готовых стратегий без runtime-обращения к blobs.txt или иному реестру.
3. Единственный источник истины
3.1. Активный запуск
Целевая версия runtime.ini хранит только ссылку на активный пресет и системные
параметры модуля:
[core]
active_preset=Default v1 (game filter).txt
autostart=1
wifi_only=0
qnum=200
desync_mark=0x40000000
nfqws_uid=0:0
log_mode=none
active_preset — единственный ключ выбора источника профилей. Переходные алиасы
и одновременная поддержка нескольких ключей не допускаются.
Грамматика и типы runtime-v1 принадлежат scripts/runtime-config.sh и
используемому им lifecycle-парсеру. Kotlin редактирует текст, но не дублирует
семантическую валидацию: каждый кандидат проверяется модулем до атомарной
публикации. Обычное чтение никогда не восстанавливает и не перезаписывает файл.
Отсутствующее, небезопасное, повреждённое и несовместимое состояние возвращается
как разные типизированные результаты. Явный repair выполняется под общей
блокировкой мутаций, создаёт канонический [core] из встроенных значений и
сохраняет не-core секции; Android и boot lifecycle используют один shell-путь
восстановления.
Все модульные ошибки передаются в APK одним ограниченным envelope:
schema=1
status=ERROR
domain=CONFIG
stage=RUNTIME_PARSE
code=INVALID_QNUM
detail=qnum=70000, expected 1..65535
domain, stage и code являются непрозрачными строками: APK проверяет только
форму, версию и лимиты, поэтому новые значения отображаются без пересборки.
detail однострочный и ограничен 512 байтами. Envelope является UI-сводкой и не
заменяет полный lifecycle/logcat/файловый журнал.
3.2. Пресет по умолчанию
Обязательный встроенный пресет первого запуска:
Default v1 (game filter).txt
Используется существующий файл из каталога zapret2/presets, приведённый
импортёром к Android-контракту версии 1. Его имена профилей, порядок, фильтры,
селекторы, --skip и стратегии сохраняются; Windows-only параметры и
--ipcache* удаляются согласно разделам 7–8.
Требования:
- fresh install записывает это точное имя в
active_preset; - файл входит в обязательную часть package contract;
- release build проверяет структуру, зависимости и Android-совместимость файла;
- device preflight выполняет его
nfqws2 --dry-runдо первого применения; - ошибка проверки блокирует сборку или запуск и не выбирает другой пресет;
- встроенный файл является обычным рабочим пресетом: профильный и полный
текстовый редакторы изменяют именно выбранный TXT на месте, без автоматического
создания копии и без смены
active_preset; - установка следующей версии модуля перезаписывает этот файл новой комплектной версией без merge и без сохранения локальных изменений.
3.3. Удаляемые параллельные источники
Runtime контракта версии 1 НЕ ДОЛЖЕН читать:
preset_mode;strategy_preset;categories.ini;strategies-tcp.ini;strategies-udp.ini;strategies-stun.ini;blobs.txt;- отдельный режим
custom_cmdline_file.
Для этих источников не создаются fallback, конвертеры или ветки совместимости.
Файлы strategies-tcp.ini, strategies-udp.ini, strategies-stun.ini и
blobs.txt, а также их одноразовые конвертеры удаляются из release-пакета в той
же версии, в которой исчезают последние production-ссылки на них. Оставлять их
как скрытый fallback или параллельный каталог запрещено.
3.4. Запрет неявного fallback
Если активный пресет отсутствует, небезопасен, несовместим или не проходит
nfqws2 --dry-run, запуск завершается ошибкой. Runtime НЕ ДОЛЖЕН молча переходить
на категории, другой пресет или встроенную глобальную стратегию.
4. Канонический формат файла
4.1. Кодировка и границы
- UTF-8 без BOM;
- переводы строк LF;
- не более 1 МиБ;
- не более 256 профилей;
- обычный файл без symlink и hardlink;
- имя файла — безопасное непосредственное имя внутри каталога пресетов.
Рекомендуемый заголовок:
# Preset: Default v1
# Preset-Format: 1
# Source: builtin
Комментарии и пустые строки разрешены и сохраняются редактором.
4.2. Общая грамматика
<metadata and comments>
<global option>*
--name=<human-readable label>
[--skip]
<profile filter>+
<profile selector>*
<lua strategy>+
--new
--name=<human-readable label>
[--skip]
<profile filter>+
<profile selector>*
<lua strategy>+
Ограничения:
- первый профиль начинается с
--name=...без предшествующего--new; - между соседними профилями находится ровно один
--new; --newне может быть первой или последней значимой строкой;- пустые профили запрещены;
- все профильные параметры находятся после имени соответствующего профиля;
- после начала первого профиля глобальные параметры запрещены.
Канонический формат использует отдельные --name=... и --new. Сокращение
--new=name, хотя и поддерживается upstream, в сохраняемых файлах версии 1 не
используется: у профиля должен быть один очевидный источник имени.
5. Контракт профиля
5.1. Имя: --name
Каждый профиль ДОЛЖЕН содержать ровно одну строку:
--name=<имя профиля>
Требования к имени:
- после удаления внешних пробелов имя не пустое;
- не более 128 символов и 512 байт UTF-8;
- отсутствуют NUL, CR, LF и управляющие символы;
- пробелы, кириллица и обычная пунктуация разрешены.
Имя является отображаемой меткой, а не идентификатором профиля. Одинаковые
--name внутри одного пресета разрешены и сохраняются без автоматического
переименования: такие имена уже встречаются в каноническом исходном наборе.
Редактор адресует профиль его позицией в разобранном snapshot и ожидаемым
SHA-256 всего файла, а не поиском по имени.
--name является рабочей опцией nfqws2, а не комментарием только для UI.
Сборщик ДОЛЖЕН передать всё значение как один аргумент argv. Запрещено удалять
имя, делить его по пробелам или заменять без необходимости подчёркиваниями.
5.2. Состояние: --skip
Включённый профиль не содержит --skip.
Выключенный профиль содержит ровно одну отдельную строку:
--skip
Правила:
--skipотносится только к текущему профилю;- выключение не удаляет фильтры или стратегию;
- повторное включение удаляет только
--skip; - предпочтительное каноническое место — сразу после
--name; --skipпередаётсяnfqws2, который штатно игнорирует такой профиль;- выключенный профиль не участвует в расчёте Android-перехвата трафика.
5.3. Фильтры
Фильтры отвечают только на вопрос «какой трафик относится к профилю».
К ним относятся:
--filter-l3=...;--filter-tcp=...;--filter-udp=...;--filter-l7=...;--hostlist=...;--hostlist-domains=...;--hostlist-exclude=...;--ipset=...;--ipset-exclude=....
--filter-l7 принимает полный набор имён протоколов, опубликованный проверенной
версией upstream v1.0.6: all, unknown, known, http, tls, dtls,
quic, wireguard, dht, discord, stun, xmpp, dns, mtproto, bt,
utp_bt. Несколько имён разделяются запятыми. L7-фильтр уточняет транспортный
фильтр и сам по себе не делает профиль смешанным: например,
--filter-tcp=443 вместе с --filter-l7=tls остаётся TCP-профилем.
Профиль ДОЛЖЕН иметь хотя бы один явный положительный фильтр. Намеренный профиль
для всего TCP или UDP задаётся явно (--filter-tcp=* или --filter-udp=*), а не
получается из отсутствия фильтров.
Android-правила NFQUEUE ДОЛЖНЫ охватывать консервативное объединение трафика всех
включённых профилей. Фильтр в nfqws2 не может расширить более узкий kernel
capture. Реализация НЕ ДОЛЖНА поддерживать отдельный ручной список портов,
способный незаметно разойтись с активным пресетом.
Если протокол невозможно однозначно получить из фильтра профиля, адаптер Android ДОЛЖЕН потребовать явную область перехвата или признать пресет несовместимым.
5.3.1. Политика kernel capture
Канонический TXT является источником не только port union и argv nfqws2, но и
внешних ограничителей connbytes, требуемых upstream Linux-интеграцией. До
первого глобального аргумента каждый канонический пресет ДОЛЖЕН ровно один раз
объявить:
# NFQWS2_TCP_PKT_OUT=20
# NFQWS2_TCP_PKT_IN=10
# NFQWS2_UDP_PKT_OUT=20
# NFQWS2_UDP_PKT_IN=10
Это Android-метаданные, отражающие четыре одноимённых upstream-настройки. Каждое
значение — каноническое положительное десятичное число не длиннее девяти цифр.
Компилятор включает их в аутентифицированный argv artifact, owner metadata,
firewall spec, fingerprint и WAL. runtime.ini и Control не имеют второй копии
этих значений.
Политика задаётся явно, потому что --out-range/--in-range допускают счётчики
n, d, b, s, p, а также a и x; общего корректного преобразования
такого диапазона в kernel packet count не существует. Значения выбираются
согласно требованиям всех включённых профилей пресета. Недостаточный bound
считается дефектом пресета, а избыточный увеличивает CPU-нагрузку.
5.4. Селекторы и границы обработки
Следующие параметры уточняют, когда применять стратегию к уже выбранному трафику:
--out-range=...;--payload=...;- другие поддержанные
nfqws2селекторы профиля.
Они принадлежат профилю, а не стратегии и не глобальной конфигурации.
--in-range является переносимым параметром nfqws2 и на Android сохраняется
так же, как --out-range; WinDivert-only являются только параметры --wf-*.
5.5. Стратегия
Профиль ДОЛЖЕН содержать хотя бы одну строку:
--lua-desync=<обработчик и параметры>
Все --lua-desync текущего профиля образуют одну стратегию и сохраняют порядок.
Внешняя ссылка на имя стратегии из strategies-*.ini запрещена: исполняемая
стратегия полностью записана в самом профиле.
--comment и --comment=<текст> являются штатными no-op параметрами nfqws2.
Валидатор принимает их в общем или профильном блоке, компилятор сохраняет каждый
такой параметр одним argv-элементом, а профильный редактор не считает его
неизвестной частью стратегии и не удаляет при замене --lua-desync.
5.6. Каталоги готовых стратегий
Вместо старых strategies-*.ini целевая поставка содержит четыре неизменяемых
каталога шаблонов редактора:
zapret2/strategy-catalogs/
├── tcp.txt
├── udp.txt
├── voice.txt
└── http80.txt
Исходной базой служат четыре переданных одноимённых файла. Области каталогов:
tcp.txt— TCP/TLS;udp.txt— UDP/QUIC;voice.txt— STUN, Discord и другой голосовой UDP-трафик;http80.txt— незашифрованный HTTP по TCP/80.
UI автоматически выбирает каталог только по разобранным фильтрам профиля:
voice.txt— профиль содержит--filter-l7с токеномstunилиdiscord, либо это UDP-профиль с Discord hostlist/ipset;http80.txt— профиль является только TCP и его разрешённое множество портов равно80;udp.txt— остальные однозначно UDP-профили;tcp.txt— остальные однозначно TCP-профили.
Имя из --name не участвует в классификации. Смешанный или неоднозначный
профиль не получает каталог по догадке и возвращает машинную причину
STRATEGY_CATALOG_SCOPE_UNRESOLVED. Компилятор не меняет профиль по имени
каталога. Одинаковые ID в разных каталогах допустимы: полная идентичность
шаблона — пара <catalog, id>. Внутри одного файла ID обязан быть уникальным.
Формат одной записи:
[stable_id]
name = Человекочитаемое имя
author = Автор
label = recommended
description = Описание
blobs = tls_google,quic1
--lua-desync=handler:arg=value
--lua-desync=next_handler:arg=value
Правила формата:
[id],nameи хотя бы одна непустая строка--lua-desyncобязательны;author,label,descriptionиblobs— метаданные интерфейса;labelне влияет на запуск;- несколько
--lua-desyncобразуют одну упорядоченную цепочку; args=..., фильтры, селекторы,--new,--name,--skip, shell-синтаксис и--ipcache*в каталоге запрещены;blobsявляется подсказкой интерфейсу, а не доверенным списком: валидатор вычисляет фактические blob-зависимости из самой цепочки;- неизвестный metadata-ключ, дублирующийся ключ или дублирующийся ID делает каталог невалидным; строка стратегии с неизвестным обработчиком или неразрешимым blob делает невалидной конкретную запись.
Каталоги проверяются при сборке и при чтении приложением. В picker попадают
только записи, для которых доступны все Lua-обработчики и blob-зависимости в
глобальном блоке активного пресета. Разрешённые встроенные blob-имена nfqws2
проверяются отдельно и не требуют объявления. Отсутствующая файловая зависимость
не берётся из blobs.txt и не добавляется молча: шаблон показывается недоступным
с точной причиной или не показывается в обычном режиме. Выбрать такой шаблон
нельзя.
Переданная база нормализуется до включения в release:
- из
http80.txtудаляется второй, побайтно одинаковый блок[multisplit_midsld]; - отдельные проверяемые
fakemultisplit.luaиfakemultidisorder.luaвходят в Android-пакет и явно загружаются глобальным блоком каждого встроенного пресета, поэтому восемь соответствующих TCP-записей проверяются наравне с остальными; - записи со ссылками на preset-local blob-имена, которых нет в активном пресете, остаются недоступными;
- никакой дубликат, отсутствующая зависимость или неизвестный обработчик не исправляется подбором похожей стратегии.
Выбор готовой стратегии заменяет только строки --lua-desync целевого профиля.
Имя, --skip, match-фильтры, --payload, --in-range, --out-range, комментарии
и общие параметры пресета не меняются. Если зависимости новой цепочки не
разрешены до записи, операция завершается ошибкой без изменения TXT.
После raw-редактирования UI сравнивает фактическую упорядоченную цепочку
--lua-desync с каталогом. Точное совпадение может показываться именем готовой
стратегии; любое другое содержимое показывается как Custom. Отдельно хранить
выбранный ID запрещено.
6. Пример канонического пресета
# Preset: Default v1
# Preset-Format: 1
# NFQWS2_TCP_PKT_OUT=20
# NFQWS2_TCP_PKT_IN=10
# NFQWS2_UDP_PKT_OUT=20
# NFQWS2_UDP_PKT_IN=10
--lua-init=@lua/zapret-lib.lua
--lua-init=@lua/zapret-antidpi.lua
--lua-init=@lua/zapret-auto.lua
--blob=tls_google:@bin/tls_clienthello_www_google_com.bin
--blob=fake_default_udp:0x00000000000000000000000000000000
--name=youtube.com (интерфейс)
--filter-tcp=80,443
--hostlist=lists/youtube.txt
--out-range=-d8
--payload=tls_client_hello
--lua-desync=multisplit:pos=1,midsld:seqovl=1
--new
--name=youtube.com (QUIC)
--filter-udp=443
--ipset=lists/ipset-youtube.txt
--out-range=-n8
--payload=all
--lua-desync=fake:repeats=6:blob=fake_default_udp
--new
--name=Discord UDP (обычно не нужно)
--skip
--filter-udp=443-65535
--ipset=lists/ipset-discord.txt
--out-range=-n8
--payload=all
--lua-desync=fake:blob=fake_default_udp:repeats=4
--new
--name=Мои сайты
--filter-tcp=80,443
--hostlist=lists/other.txt
--out-range=-d10
--payload=tls_client_hello
--lua-desync=hostfakesplit:host=example.org:repeats=4
7. Android-совместимость
7.1. Сохраняемые опции
--name, --skip, --comment, --in-range и полный поддерживаемый upstream
набор --filter-l7 являются переносимыми рабочими опциями и НЕ ДОЛЖНЫ удаляться
импортёром, валидатором или сборщиком команды.
Windows-only параметры (--wf-*, WinDivert-зависимости и неподдержанные Lua-
обработчики) не передаются Android-бинарнику. Их удаляет или преобразует только
явный импортёр до сохранения Android-пресета.
Live-валидатор не должен молча пропускать неизвестную значимую опцию: такой файл помещается в карантин с конкретной причиной.
7.2. Политика IP cache
Все опции с префиксом --ipcache запрещены на Android:
--ipcache-hostname
--ipcache-hostname=...
--ipcache-lifetime=...
--ipcache...
Требования:
- импортёр удаляет их из импортируемых Windows-пресетов;
- валидатор сохранённого или пользовательского пресета отклоняет их;
- custom/import migration отклоняет или удаляет их до активации;
- финальная граница сборки повторно проверяет, что
--ipcacheне попал в argv; - запрет не реализуется молчаливым игнорированием уже активного файла.
Следствие этого решения: нулевая фаза TCP (syndata) не получает доменное
ограничение от одного --hostlist, потому что имя хоста ещё неизвестно на SYN.
Пресет и его порядок обработчиков сохраняются дословно, но UI и документация не
должны обещать host-scoped syndata без отдельного --ipset. Автоматическая
адаптация не удаляет такие обработчики и не добавляет скрытый ipcache.
7.3. Зависимости
Допустимы только объявленные обычные файлы внутри разрешённых каталогов модуля:
@lua/<file>;@bin/<file>;lists/<file>.
Перед запуском путь разрешается в абсолютный путь установленного модуля.
Запрещены .., абсолютные пользовательские пути, symlink, hardlink, пустые,
нечитаемые, слишком большие и не объявленные package manifest зависимости.
8. Импорт Windows-пресетов
Импорт является отдельной детерминированной операцией, а не частью live-запуска.
Экран пресетов также предоставляет прямой импорт пользовательского TXT через
Android document provider. Эта граница проверяет прямое безопасное имя .txt,
лимит 1 МБ, строгий UTF-8 и отсутствие NUL. Для уже канонического пресета
меняются только BOM, CRLF и конечный перевод строки. Legacy/native TXT без
Android capture policy получает явные значения 20/10/20/10, а безымянные
профили — стабильные отображаемые имена из имени файла; вариант upstream
--new=<name> переводится в эквивалентные --new и --name=<name>. Остальные
значимые строки, включая --comment, --filter-l7=tls, ranges и
--lua-desync, не переписываются. Файл создаётся только под новым именем, чтобы импорт не мог молча
затереть существующий пресет, после чего проходит ту же root-транзакцию,
структурный валидатор и nfqws2 --dry-run, что save/apply. Только подтверждённый
пресет появляется в обновлённом каталоге приложения.
Если пользователь помещает legacy/native TXT непосредственно в каталог
presets, runtime-компилятор не изменяет файл: отсутствующий целиком capture
policy означает тот же документированный default 20/10/20/10, имена профилей
остаются необязательными, а --new=<name> передаётся upstream как есть.
Частично заданный capture policy отклоняется как неоднозначный. Это даёт
совместимость ручного размещения без скрытой мутации пользовательского файла.
Импорт Windows-пресета с адаптацией — отдельная операция от этого прямого импорта: только она вправе удалить явно Windows-only параметры и обязана отчитаться о преобразовании.
Импортёр ДОЛЖЕН:
- прочитать исходный пресет без изменения порядка профилей;
- сохранить
--name,--skip, фильтры, селекторы и--lua-desync; - удалить
--ipcache*; - удалить Windows-only глобальные параметры;
- отклонить профиль, который невозможно безопасно адаптировать для Android;
- проверить все зависимости;
- привести структуру к каноническому формату версии 1;
- добавить явную Android capture policy в TXT;
- прогнать тот же валидатор, который используется перед запуском;
- сформировать отчёт о каждом удалённом или отклонённом элементе.
Импортёр НЕ ДОЛЖЕН превращать именованные профили в безымянные блоки и НЕ ДОЛЖЕН скрывать несовместимость простым удалением неизвестных строк.
8.1. Зафиксированная исходная база
Перед реализацией runtime-контракта встроенный каталог готовится из точных источников:
- имена и структура winws2-пресетов — ревизия
zapretdiscordyoutube/zapretgui@eb98b501acd22623d1570be28574c0110f10df11; - Android-списки — переданный архив
lists.zipс SHA-256e76409252f438d8847891c62b504f706884803f5aebb2235b93c89eedbf810da; - upstream-контракт
nfqws2, использованный при подготовке этой исходной базы, — releasev1.0.3, commitb78b52c4cd7f843da3ff0848a3430afbd401bdf2. Это историческая база импорта, а не pin бинарника: каждая CI-сборка запрашивает latest stable upstream Release, сверяет его с явно проверенной версией parser-контракта, записывает выбранные tag/commit/digest и повторно квалифицирует полный каталог.
Из архива списков импортируются только прямые .txt-зависимости пресетов.
Записи Zone.Identifier, каталоги и иные побочные файлы не входят в пакет.
Кодировка приводится к UTF-8 без BOM, переводы строк — к LF; каждая hostlist- и
ipset-строка проходит проверку своего типа. Историческое имя
russia-youtube-rtmps.txt, использованное как ipset, канонизируется в
ipset-russia-youtube-rtmps.txt, потому что содержимое файлов совпадает.
Отсутствующий blob не подменяется другим пакетом. Подготовка release-каталога
останавливается до получения и проверки всех требуемых файлов: заведомо
quarantined-пресеты не считаются допустимым окончательным результатом реализации.
Переданный bin.zip добавляет @bin/quic_initial_dbankcloud_ru.bin и закрывает
последнюю файловую зависимость исходного набора. Все 98 встроенных пресетов,
включая обязательный Default v1 (game filter).txt, классифицируются compatible;
перед релизом они ДОЛЖНЫ дополнительно пройти общий валидатор и реальный
nfqws2 --dry-run.
9. Валидация и компиляция
9.1. Этапы валидации
Один валидатор используется при сканировании каталога, импорте, сохранении, применении и запуске:
- проверка безопасного файла и размера;
- лексический разбор строк;
- структурная проверка global/profile/
--new; - проверка не более одного непустого
--nameи не более одного--skipв профиле; канонический/app-imported формат требует имя, runtime также принимает безымянный legacy/native профиль; - проверка фильтров и стратегии каждого профиля;
- проверка Android-совместимости опций;
- разрешение и проверка зависимостей;
- построение kernel capture для включённых профилей;
- построение точного argv;
nfqws2 --dry-run.
Неизвестная, потерянная или неоднозначно распарсенная значимая строка приводит к ошибке. Частично собранный пресет не запускается.
9.2. Построение argv
Сборщик создаёт типизированную последовательность аргументов, а не одну строку, которая затем повторно делится shell по пробелам.
Upstream-загрузка параметров через единственный аргумент @config_file не
поддерживается Android-сборкой nfqws2. Поэтому Android runtime НЕ ДОЛЖЕН
запускать nfqws2 @preset и не должен менять или форкать бинарник только ради
этого механизма.
Вместо этого используются два разных файла с разной ответственностью:
- исходный TXT-пресет — единственный редактируемый источник истины;
- приватный служебный argv-артефакт — результат компиляции для одного запуска.
Служебный артефакт создаётся заново при каждом apply, принадлежит root, доступен только владельцу и находится в управляемом runtime-каталоге. Пользователь и UI его не редактируют. Он содержит точные границы нормализованных аргументов, разрешённые абсолютные пути и module-owned системные параметры.
Launcher читает артефакт без eval, command substitution и повторного shell-
парсинга. Каждая запись добавляется как один argv-элемент. После построения argv
launcher повторно проверяет запрет --ipcache, размер argv и целостность
артефакта.
Логическая форма запуска:
nfqws2
<module-owned system arguments>
<preset global arguments>
<profile 1 arguments>
--new
<profile 2 arguments>
...
Границы каждого аргумента должны сохраняться дословно. В частности,
--name=Голосовые звонки/чаты является одним argv-элементом.
В диагностический файл публикуется воспроизводимое escaped-представление argv,
а ownership metadata сверяет фактический /proc/<pid>/cmdline.
Один и тот же скомпилированный набор профильных аргументов используется сначала
для dry-run, затем для реального запуска. Различаться могут только явно
определённые служебные аргументы --dry-run, --daemon и --pidfile.
9.3. Свежесть исходника и артефакта
Компиляция привязана к точному snapshot исходного пресета. Snapshot включает как минимум безопасный путь, metadata файла и SHA-256 содержимого.
Порядок операции:
- прочитать и захэшировать активный TXT-пресет;
- разобрать и проверить его;
- построить firewall plan и argv-артефакт;
- повторно проверить metadata и SHA-256 исходника;
- атомарно опубликовать артефакт;
- выполнить dry-run;
- ещё раз подтвердить связь
active_preset → source hash → artifact hash; - применить firewall и запустить процесс;
- сверить фактический argv и состояние firewall.
Если исходник или указатель активного пресета изменился на любом этапе, операция
завершается PRESET_SOURCE_CHANGED. Устаревший артефакт никогда не запускается.
10. Операции приложения
10.1. Экран пресетов
Экран пресетов отвечает за цельные наборы запуска:
- просмотр каталога;
- выбор активного пресета;
- импорт;
- полный текстовый редактор для эксперта;
- просмотр точного итогового
nfqws2argv для текущего текста редактора.
Редактор имеет две связанные вкладки: исходный TXT и итоговая команда. Preview
строится из текущего черновика, в том числе ещё не сохранённого, тем же безопасным
компилятором и проходит nfqws2 --dry-run. Он показывает executable, границы
каждого аргумента, точные TCP/UDP-множества перехвата, четыре packet limit и
служебные --daemon / --pidfile, когда их поддерживает установленный бинарник.
Preview не меняет
active_preset, постоянный TXT или состояние сервиса; временный root-owned файл
удаляется до публикации результата в UI.
10.2. Экран профилей
Экран профилей показывает профили активного пресета в порядке исполнения:
- имя;
- состояние;
- краткое описание фильтра;
- краткое описание стратегии;
- включение/выключение через
--skip; - редактирование;
- изменение порядка перемещением цельного блока профиля.
Отдельный экран выбора «стратегии для категории» в новой модели не нужен.
10.3. Изменения
Любая операция над активным пресетом выполняется под общей mutation lock:
- получить авторитетный snapshot файла и активного указателя;
- построить новое содержимое в памяти;
- проверить новым единым валидатором;
- атомарно записать файл;
- выполнить dry-run;
- если сервис работал — перезапустить его; если он был остановлен — сохранить остановленное состояние;
- для работавшего сервиса проверить новый процесс и firewall;
- при ошибке восстановить snapshot и предыдущий рабочий запуск.
Успех публикуется только после post-operation verification.
10.4. Синхронизация TXT и экрана профилей
Экран профилей не имеет собственного постоянного хранилища фильтров, стратегий
или состояния. Его модель всегда является проекцией текущего TXT-файла,
указанного в active_preset.
Правила чтения:
- при открытии экрана и после смены активного пресета файл читается заново;
- после любого события успешного сохранения/apply экран заново читает файл;
- возврат из полного текстового редактора всегда инвалидирует профильный cache;
- cache разрешён только как оптимизация с ключом
path + SHA-256, но не как источник данных; - изменение имени, фильтра, стратегии,
--skip, порядка или числа профилей в полном редакторе должно отобразиться без перезапуска приложения.
Архитектурная граница чтения и инвалидации:
- repository публикует монотонную process-local ревизию только после подтверждённой записи, применения или смены активного пресета;
- ревизия не содержит копию конфигурации: она только инвалидирует проекции, которые обязаны перечитать единственный источник истины;
- вход на экран профилей всегда инвалидирует восстановленное navigation-state и
читает
runtime.iniплюс ровно один выбранный TXT; - это чтение использует отдельный узкий интерфейс active-snapshot и не имеет
доступа к перечислению каталога, глубокой проверке пресетов,
nfqws2 --dry-runили immutable package audit; - если ревизия изменилась во время чтения, полученная модель не публикуется: выполняется новое bounded-чтение уже для последней ревизии;
- пока экран не виден, событие только инвалидирует модель и не запускает привилегированный I/O; при следующем входе читается актуальный snapshot;
- polling и периодическое перечитывание запрещены.
Правила записи из экрана профилей:
- экран запоминает SHA-256 прочитанного исходника;
- изменение применяется к типизированной модели профиля;
- сериализуется весь пресет с сохранением не затронутых строк и комментариев;
- repository сравнивает ожидаемый SHA-256 с текущим файлом;
- при несовпадении возвращает
PRESET_SOURCE_CHANGEDи ничего не перезаписывает; - при совпадении выполняет полную валидацию и атомарную запись;
- persisted-файл читается повторно, и UI публикует только эту модель;
- если пресет активен, запускается транзакционный apply.
Операции имеют точную область изменения:
- выключение добавляет только
--skipпосле--name; - включение удаляет только
--skipтекущего профиля; - переименование меняет только
--name; - смена hostlist/ipset меняет только соответствующие match-сегменты;
- готовая стратегия заменяет только упорядоченные строки
--lua-desync, но не имя, состояние, фильтры, селекторы или общие параметры; - перестановка переносит профиль целиком вместе с комментариями и сохраняет
корректные разделители
--new.
После сохранения raw-редактор и экран профилей обязаны показывать одно и то же содержимое. Отдельная таблица выбранных стратегий, флагов enabled или фильтров запрещена.
11. Граница совместимости
Контракт версии 1 вводится как новая реализация только для Zapret2. Существование старой модели, старой установки или Zapret1 не входит в область реализации; миграционный код не создаётся.
Требования:
- legacy
runtime.ini,categories.ini,strategies-*.ini, старый custom cmdline, старые TXT, hostlist и Lua не ищутся, не читаются, не копируются, не архивируются и не преобразуются; - при первом запуске версии с новым контрактом создаётся новый
runtime.iniи выбирается валидный встроенный пресет по умолчанию; - в production-коде отсутствуют legacy-парсеры, конвертеры, dual-read и fallback.
Это намеренно несовместимое изменение конфигурации. Простота и однозначность нового runtime имеют приоритет над сохранением старых настроек.
12. Обновления и пользовательские данные
- встроенные пресеты поставляются модулем и имеют известную версию;
- встроенный пресет редактируется на месте в пределах установленной версии;
- следующее обновление безусловно заменяет все встроенные TXT новыми комплектными версиями; локальные изменения встроенных файлов не объединяются и не восстанавливаются;
- явно импортированные пользовательские пресеты с отдельными именами и активный указатель сохраняются при совместимом обновлении, если указатель остаётся валидным;
- обновление сначала валидирует активный пресет против нового
nfqws2и его Lua; - несовместимость блокирует применение или сохраняет предыдущий рабочий релиз, но не включает другой пресет молча.
13. Коды ошибок
Минимальный стабильный набор машинных причин:
PRESET_MISSING;PRESET_UNSAFE;PRESET_TOO_LARGE;MALFORMED_PRESET_STRUCTURE;EMPTY_PROFILE;PROFILE_NAME_MISSING;PROFILE_SKIP_DUPLICATE;PROFILE_FILTER_MISSING;PROFILE_STRATEGY_MISSING;UNSUPPORTED_ANDROID_OPTION;FORBIDDEN_IPCACHE_OPTION;DEPENDENCY_MISSING;DEPENDENCY_UNSAFE;CAPTURE_SCOPE_UNRESOLVED;PRESET_SOURCE_CHANGED;COMPILED_ARTIFACT_STALE;ARGV_BUILD_FAILED;NFQWS_DRY_RUN_FAILED;STRATEGY_CATALOG_INVALID;STRATEGY_CATALOG_SCOPE_UNRESOLVED;STRATEGY_DEPENDENCY_UNAVAILABLE.
UI показывает модульный envelope как есть и не заменяет машинную причину.
14. Критерии готовности реализации
Контракт считается реализованным, когда одновременно выполняются условия:
- fresh install выбирает
Default v1 (game filter).txtединственным указателем активного пресета; categories.ini,strategies-*.ini,blobs.txt,strategy_presetи cmdline mode не влияют на argv и не читаются production-кодом;- каждый профиль имеет ровно один непустой
--name, при этом одинаковые отображаемые имена допустимы; - toggle профиля изменяет только наличие
--skipи перезапускает сервис транзакционно; --nameс пробелами и кириллицей доходит доnfqws2одним argv-элементом;--skipдоходит доnfqws2и выключенный профиль не расширяет NFQUEUE capture;--ipcache*удаляется импортёром и отклоняется всеми live-входами;- фильтры и
--lua-desyncберутся только из активного пресета; - firewall является проверенным superset включённых профилей;
- неизвестные опции не пропускаются молча;
- любой save/apply проходит один валидатор и
nfqws2 --dry-run; - ошибка записи, dry-run, restart или verification восстанавливает предыдущий рабочий пресет и состояние сервиса;
- GUI показывает точный итоговый argv для сохранённого текста и несохранённого черновика без изменения активной конфигурации.
15. Обязательная матрица проверок
Реализация не считается готовой без автоматических host-тестов и проверок на реальном Android-устройстве.
Минимальные сценарии:
- первый запуск выбирает
Default v1 (game filter).txt, а последующий выбор пресета согласованно меняетactive_preset, экран профилей и итоговый argv; - raw-редактор меняет
--name, и новое имя появляется на экране профилей; - raw-редактор добавляет, удаляет и переставляет профили, UI повторяет структуру;
- raw-редактор добавляет/удаляет
--skip, toggle показывает правильное состояние; - toggle в UI меняет только
--skip, а raw-редактор видит тот же результат; - выбор готовой стратегии меняет только строки
--lua-desyncпрофиля, а после raw-редактирования точное совпадение снова определяется из TXT без скрытого ID; - изменение hostlist/ipset меняет match и пересчитывает firewall plan;
- имя с пробелами и кириллицей остаётся одним элементом в
/proc/<pid>/cmdline; - изменение файла между read и write возвращает
PRESET_SOURCE_CHANGED; - изменение файла между compile и launch блокирует устаревший артефакт;
- неизвестная опция, unsafe dependency и
--ipcache*блокируют apply; - выключенный профиль остаётся в argv с
--skip, но не расширяет firewall; - dry-run и реальный процесс используют один скомпилированный профильный argv;
- ошибка restart/verification восстанавливает предыдущий файл, argv и firewall;
- многократный parse → serialize без изменений даёт побайтно тот же TXT;
- закрытие и повторное открытие приложения восстанавливает UI только из
active_presetи его TXT, без скрытого состояния. - четыре новых каталога проходят проверку уникальности ID, грамматики, обработчиков и blob-зависимостей; дубликат и недоступная зависимость дают детерминированную ошибку.
strategies-tcp.ini,strategies-udp.ini,strategies-stun.ini,blobs.txtи их production-ссылки отсутствуют в release-пакете.- два профиля с одинаковым
--nameредактируются по позиции независимо и не переименовываются автоматически. - удаление всех
--blobиз тестового пресета делает зависящую от них стратегию невалидной и не активирует fallback на общий каталог. - все 98 встроенных пресетов содержат один и тот же полный канонический blob-блок, а конфликт двух определений одного имени блокирует импорт.
- picker автоматически открывает
voiceдля STUN/Discord,http80для чистого TCP/80,udpдля остального UDP иtcpдля остального TCP. - обновление заменяет локально изменённый встроенный TXT новой комплектной версией без merge.
- preview несохранённого TXT проходит единый компилятор и dry-run, сохраняет аргумент с пробелами одним элементом, показывает точный TCP/UDP union и всегда удаляет временный candidate.
- прямой импорт TXT не перезаписывает существующий файл, сохраняет
--comment,--filter-l7=tlsи--in-rangeи публикует файл только после общей save/preflight-транзакции. - legacy/native TXT без wrapper capture policy и
--nameполучает только явные Android-метаданные и отображаемые имена; blob-free стратегия без ссылок на blob остаётся допустимой, как в upstream. - legacy/native TXT, помещённый в каталог вручную, компилируется без изменения
argv с тем же default capture policy и поддерживает upstream
--new=<name>.
Device-проверка дополнительно подтверждает PID ownership, фактический argv,
iptables/ip6tables, работу --name, --skip и сетевой трафик хотя бы для одного
TCP-, UDP- и L7-профиля.
16. Нормативные внешние основания
- Upstream
nfqws2: latest stable запрашивается на каждой сборке и записывается в package provenance. Если latest новее проверенногоupstream/reviewed-release.txt, сборка останавливается до ревизии грамматики: https://github.com/bol-van/zapret2/releases/latest - Upstream
nfqws2:--newначинает новый профиль,--nameзадаёт имя, а--skipигнорирует профиль; CI читает manual выбранного release: https://github.com/bol-van/zapret2/blob/v1.0.6/docs/manual.md - Upstream
nfqws2:@config_fileявно не поддерживается Android-сборкой; Android использует собственный compiler + launcher: https://github.com/bol-van/zapret2/blob/v1.0.6/docs/manual.md#общие-принципы-задания-параметров - Формат именованных профилей, используемый исходными winws2-пресетами: https://git2.zapret.moe/zapretdiscordyoutube/zapretgui/tree/eb98b501acd22623d1570be28574c0110f10df11/src/presets/builtin/winws2