15 KiB
Чистая переборка на базе 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. ВЫКИНУТЬ / не переносить
- WSS-фрейминг и Reality (TLS-мимикрия) — оба выкинуть. Путь zapret-like, а не
маскировка под TLS-сайт; Reality (
reality.rs) к тому же сложно сделать корректно с нуля. Удалить из базы 0.2.98 целиком. - Multi-server / urltest-группа — не нужен. Один сервер на конфиг.
- Остатки 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по шлюзу (а не legacyCreateIpForwardEntry, который падал с 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+ Kotlinclose()) →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.
Что легко упустить (решить до старта)
- Версия переборки — 0.4.0 (решено). Сожжённые 0.3.x не переиспользуем.
- Серверный конфиг — тоже плоский и согласован с клиентским. Сервер обязан поддерживать всё оставленное: derived-secrets (и отвергать старый формат), корректную обработку junk (не probe-флуд), UoT, DNS-transport, management API.
- Версия/магический байт протокола ДО крипто-слоя. Сейчас нельзя отличить старый handshake от нового — отсюда баг «старый клиент → новый сервер подключился». Добавить версию в wire → будущие изменения управляемы, сервер чётко режет несовместимое. Это системный фикс проблемы derived-secrets.
- Клиент и сервер обновляются ВМЕСТЕ — derived-secrets ломает совместимость, смешивать старое и новое нельзя. Координировать выкладку.
- Reality — выкинуть (решено; в базе 0.2.98 есть
reality.rs→ удалить целиком). - Verify-loop = критерий «готово». Каждая фича проверяется реальным тестом, не
«на словах»: connect →
curlпоказывает IP сервера; старый клиент к новому серверу не подключается; смена сети на мобилке восстанавливает туннель.
Порядок переборки (предложение, 1 сессия)
- §B (фиксы стабильности) — на чистый 0.2.98.
- §C (derived-secrets) — и СРАЗУ проверить: старый клиент к новому серверу НЕ подключается (в прошлый раз был баг — подключался).
- Junk + TCP-фрагментация — перенести (оставляем), но junk по UDP не должен
читаться сервером как
Unauthorized probe(rate-limit/гейт на сервере). - §F/§G по минимуму (routing, метрики, состояние, share) + брендинг (тёмная тема, орёл на фоне).
- §D (DNS transport) — если нужно.
- ВЫКИНУТЬ: WSS, multi-server, tun2socks (§A). §E (тюнинг junk) — позже.
- Конфиг: плоский по сути, секционно-оформленный, один сервер (РЕШЕНО — без inbounds/outbounds/routing-движка).