# Чистая переборка на базе v0.2.98 База: `v0.2.98` (commit `31d0020`) — последняя версия, которая **стабильно работает**. Ветка: `clean-rebuild`. Всё, что появилось после (0.3.1 … 0.3.21), переносим **выборочно и с чистой головой**, а не копируем рефактор целиком. Принцип: 0.3.1 принёс «модульный multi-server рефактор» + лавину фич — и вместе с ними нестабильность. Берём только проверенное и нужное. --- ## Решения (зафиксировано пользователем) - **Junk-пакеты + TCP-фрагментация — ОСТАВЛЯЕМ** (нравятся). НО починить вредную часть: junk по UDP не должен выглядеть для сервера как `Unauthorized probe` (rate-limit/гейт на сервере), иначе флуд лога и риск самобана клиента. Фича остаётся — чиним поведение, а не выпиливаем. Тонкая настройка — §E. - **Версия переборки — 0.4.0** (решено; 0.3.x сожжены в pre-release). - **WSS и Reality (TLS-мимикрия) — ВЫКИНУТЬ.** Путь проекта — **zapret-like**: обфускация/DPI-evasion на уровне пакетов (junk, фрагментация, обфускация), а НЕ мимикрия под TLS. Reality с нуля тяжела и не вписывается. - **Multi-server — НЕ НУЖЕН.** Режем до одного сервера → уходит urltest-группа и половина сложности 0.3.1. - **Конфиг — ПЛОСКИЙ по сути, но оформлен красиво/секционно как сейчас** (решено). Сохраняем читаемую секционную структуру (server / transport / tun / dns / exclude и т.п.), но **выпиливаем модульную машинерию**: массивы `inbounds[]`/`outbounds[]`, `routing.rules[]` с тегами, `default_outbound`, urltest, мульти-сервер. Один сервер на конфиг. Исключения = плоский список внутри секции `exclude`. - **Профили — ОСТАВЛЯЕМ, single-select, в UI-слое** (решено). Профиль = сохранённый конфиг одного сервера; активен ровно один (radio). Список/выбор/share живут во фронте (prefs GUI / Flutter); **ядро о профилях не знает** — на «Подключить» из выбранного профиля генерится плоский конфиг на один сервер. Никаких чекбоксов/ мульти-актив/urltest. - **Derived-secrets — ОСТАВЛЯЕМ, но ОБЯЗАТЕЛЬНО проверить, что он РЕАЛЬНО работает:** старый клиент НЕ должен подключаться к новому серверу. В прошлой реализации это НЕ соблюдалось (старый клиент → новый сервер подключался) — значит сервер всё ещё принимал старый формат handshake / obfuscation-key. Это **баг**, закрыть в первую очередь: сервер обязан отвергать всё, что не прошло derived-secrets. - **Лицензия — AGPLv3.** - **Брендинг — ОСТАВЛЯЕМ**: тёмная тема + орёл на фоне (watermark/логотип). - **Стелс-философия (north-star): zapret-like** — «нет узнаваемого заголовка + манипуляции пакетами» (обфускация, junk, фрагментация, DNS/UoT-транспорты), а НЕ «притворись известным протоколом» (Reality/WSS — выкинуты). --- ## 0. Корневая причина нестабильности 0.3.x **Модульный multi-server рефактор (0.3.1)** — `580faf6`, `8ed66f9`, `67f9c06`. Сменил формат конфига (inbounds/outbounds/routing/urltest), session-модель, hot-reload. Источник большинства багов (мёртвые маршруты, фейк-коннект, рассинхрон конфига). **НЕ копировать целиком.** Если multi-server реально нужен — добавлять минимально и поверх рабочей одно-серверной модели 0.2.98. --- ## A. ВЫКИНУТЬ / не переносить 1. **WSS-фрейминг и Reality (TLS-мимикрия)** — оба выкинуть. Путь zapret-like, а не маскировка под TLS-сайт; Reality (`reality.rs`) к тому же сложно сделать корректно с нуля. Удалить из базы 0.2.98 целиком. 2. **Multi-server / urltest-группа** — не нужен. Один сервер на конфиг. 3. Остатки **tun2socks** на Android (`libtun2socks.so`, `tun2socks-arm64`, `tun_child`, `t2sBinPath`) — давно мёртвый код, только раздувает APK. Не тащить. > ⚠️ Junk-пакеты и TCP-фрагментация **ОСТАЮТСЯ** (см. Решения и §E) — это уже не > «мусор». Но junk по UDP нужно сделать так, чтобы сервер его не считал > `Unauthorized probe` (rate-limit/гейт), иначе лог-флуд и риск самобана. --- ## B. ОБЯЗАТЕЛЬНО перенести (фиксы стабильности) - **fd limits / EMFILE** — `922cf0b`. - **Lifecycle хелпера**: принудительный `std::process::exit` после остановки, чтобы не оставался зомби-процесс, держащий адаптер `ostp_tun` и дефолтный маршрут — `b6e78c1`. - **Bypass-маршрут сервера через `route.exe` по шлюзу** (а не legacy `CreateIpForwardEntry`, который падал с err 160 из-за рассинхрона индексов интерфейсов) — `b6e78c1`. - **IPC хелпера** (ChaCha20Poly1305 + hex) + **единый формат логов** — `ee38b15`. - **Closing-state fix** + `sent_history` на `BTreeMap` (O(log n) NACK) — `47d44fa`. - **Handshake timeout fixes** — `d65af35`, `6eb7b36` (ждать ответ до отправки данных). - **Buffer / UDP handler** — `b5e830a`. - **Логи**: UoT и unauthorized-probe → debug; rate-limit probe-лога — `1151726`, `fc339b3`. --- ## C. Протокол / крипто — решить и перенести - **Derived secrets handshake** — `f8f27d3`. PSK и obfuscation-key выводятся из access-key через HKDF; handshake-payload = `[timestamp][session_id][access_key]`; параметры паддинга деривируются; timestamp anti-replay (±300с). ⚠️ **Ломает совместимость с 0.2.98 wire** (старый клиент не подключится). Безопаснее старого (raw-PSK + нулевой obfuscation-key). **РЕШЕНИЕ:** переносим ли (тогда нужен ребилд всех клиентов) — ДА, скорее всего, но осознанно. - **l4_protocol** для server outbound — `2997bfd`, `ad3a8cb`, `aae9d22`. --- ## D. Транспорты — перенести аккуратно (большие куски) - **DNS transport (dnstt)** как fallback — `3f1adbc`, `3ced4a1`, `d031b15`, `10c1772`, `b31da29`. Полезно против блокировок, но объёмно и со своей фрагментацией/reassembly. Переносить отдельным изолированным модулем. - UoT (UDP-over-TCP) — уже есть в 0.2.98, проверить что не сломан. --- ## E. Тонкая настройка junk/фрагментации (как в AmneziaWG) Junk и фрагментацию **оставляем** (Решения), а это — их параметризация. Главное условие: **координация клиент↔сервер**, иначе junk превращается в probe-флуд. - `Jc` — кол-во junk-пакетов, `Jmin`/`Jmax` — размеры; **сервер знает и молча отбрасывает**. - `S1`/`S2` — размеры init/response подгоняются. - Магические заголовки/сигнатуры пакетов (`H1..H4`). Реализовать как явные настраиваемые поля (не хардкод). Сервер ОБЯЗАН их понимать. Сам факт junk/frag — в базе; это «желание» — сделать их настраиваемыми. Можно потом. --- ## F. GUI (desktop) — перенести нужное, без хаоса - Профили на странице **настроек** (пусто + «Create a new profile» + «+» когда нет профиля; «+» → меню «из ссылки / вручную»). Главный экран не усложнять. - **Share** профиля: QR (генерить локально, ключ наружу не отдавать — крейт `qrcode`) + копируемая `ostp://` ссылка. - **Метрики**: байты считать в TUN-инбаунде; rtt брать из round-trip handshake (а не отдельным TCP-probe). - **Health/состояние**: «connected» по реальной достижимости сервера на ПРАВИЛЬНОМ порту (не хардкод :443), а не по факту «процесс запустился». - **routing**: всегда задавать `default_outbound: "proxy"`; ключи правил — `domain_suffix` / `ip_cidr` / `process_name` (не `domains/ips/processes`). - Смена сервера = полный **stop+start**, а не hot-reload (иначе остаётся старый сервер). - Никаких непрогарженных `addEventListener` на удалённые элементы (краш init). --- ## G. Мобилка (Flutter + JNI) — перенести нужное - **routing**: тот же `default_outbound` + правильные ключи правил (без них трафик шёл мимо туннеля — реальный IP). - **Байты на Android**: считать в обеих задачах fd-пути (read=upload, write=download). - **rtt**: из handshake (health-probe сокет на Android не protected → до сервера не доходит). - **Смена сети (WiFi↔LTE)**: реальный reconnect (сейчас `notifyNetworkChanged` — no-op). - **fd ownership**: НЕ двойное закрытие (Rust `OwnedFd` + Kotlin `close()`) → `detachFd()`. - **Share** профиля: QR (`qr_flutter`) + ссылка. - Выкинуть tun2socks (см. §A.3). --- ## H. Инфра / лицензия / брендинг - Лицензия: **AGPLv3** ✅ (зафиксировано). В 0.2.98 был BSL 1.1 → заменить (`9ce9e6d`). - **Брендинг — ОСТАВЛЯЕМ** ✅: тёмная тема + орёл на фоне (watermark/логотип) в GUI. Перенести из текущего `ostp-gui` (assets/logo.svg, тёмная палитра) в чистую переборку. - Панель/license-check: open-source без license-check — `5782107`, `99ff76d` (если нужно). - Версионирование/CI build-script — `774d926` и пр. --- ## Инвентаризация базы 0.2.98 (что уже есть / что портировать) - **Есть в 0.2.98**: WSS (→ удалить), Reality/`reality.rs` (→ удалить), инфра derived-secrets (`derive_all_secrets`, `obfuscation_key`) — но клиент юзал dummy-ключи до `f8f27d3`. - **Нет в 0.2.98 — портировать из пост-0.2.98 кода**: junk-пакеты, TCP-фрагментация, DNS-transport (dnstt), фикс derived-secrets `f8f27d3`, все фиксы §B, GUI/мобилка §F/§G. ## Что легко упустить (решить до старта) 1. **Версия переборки — 0.4.0** (решено). Сожжённые 0.3.x не переиспользуем. 2. **Серверный конфиг — тоже плоский** и согласован с клиентским. Сервер обязан поддерживать всё оставленное: derived-secrets (и **отвергать** старый формат), корректную обработку junk (не probe-флуд), UoT, DNS-transport, management API. 3. **Версия/магический байт протокола ДО крипто-слоя.** Сейчас нельзя отличить старый handshake от нового — отсюда баг «старый клиент → новый сервер подключился». Добавить версию в wire → будущие изменения управляемы, сервер чётко режет несовместимое. Это системный фикс проблемы derived-secrets. 4. **Клиент и сервер обновляются ВМЕСТЕ** — derived-secrets ломает совместимость, смешивать старое и новое нельзя. Координировать выкладку. 5. **Reality — выкинуть** (решено; в базе 0.2.98 есть `reality.rs` → удалить целиком). 6. **Verify-loop = критерий «готово».** Каждая фича проверяется реальным тестом, не «на словах»: connect → `curl` показывает IP **сервера**; старый клиент к новому серверу **не** подключается; смена сети на мобилке восстанавливает туннель. ## Порядок переборки (предложение, 1 сессия) 1. §B (фиксы стабильности) — на чистый 0.2.98. 2. §C (derived-secrets) — и СРАЗУ проверить: старый клиент к новому серверу НЕ подключается (в прошлый раз был баг — подключался). 3. **Junk + TCP-фрагментация** — перенести (оставляем), но junk по UDP не должен читаться сервером как `Unauthorized probe` (rate-limit/гейт на сервере). 4. §F/§G по минимуму (routing, метрики, состояние, share) **+ брендинг** (тёмная тема, орёл на фоне). 5. §D (DNS transport) — если нужно. 6. ВЫКИНУТЬ: **WSS, multi-server, tun2socks** (§A). §E (тюнинг junk) — позже. 7. Конфиг: плоский по сути, секционно-оформленный, один сервер (РЕШЕНО — без inbounds/outbounds/routing-движка).