ostp/REBUILD_PLAN.md

15 KiB
Raw Blame History

Чистая переборка на базе 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 / EMFILE922cf0b.
  • 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 fixesd65af35, 6eb7b36 (ждать ответ до отправки данных).
  • Buffer / UDP handlerb5e830a.
  • Логи: UoT и unauthorized-probe → debug; rate-limit probe-лога — 1151726, fc339b3.

C. Протокол / крипто — решить и перенести

  • Derived secrets handshakef8f27d3. 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-движка).