diff --git a/ANALYSIS_REPORT.md b/ANALYSIS_REPORT.md deleted file mode 100644 index 54c19ca..0000000 --- a/ANALYSIS_REPORT.md +++ /dev/null @@ -1,289 +0,0 @@ -# OSTP Project - Анализ Стабильности, Скорости и Пропускной способности - -**Дата анализа:** 2026-06-17 -**Проанализировано:** 69,025 строк кода - ---- - -## 📋 Обзор проекта - -OSTP (Open Spectrum Tunnel Protocol) — VPN/туннельный протокол на Rust с поддержкой: -- NOISE протокола для шифрования -- UDP и TCP транспорта -- Обхода блокировок (Reality, UOT) -- REST API управления -- Multi-relay архитектуры - ---- - -## 🔴 КРИТИЧЕСКИЕ ПРОБЛЕМЫ - -### 1. **ostp-server** — 84 unwrap/expect вызвов -- **Риск:** Потенциальные паники в production -- **Примеры:** `read().unwrap()` в критических путях -- **Влияние на стабильность:** ВЫСОКОЕ - -### 2. **Утечка памяти в replay_cache** -- **Файл:** `ostp-server/src/dispatcher.rs` -- **Проблема:** `HashMap, u64>` растёт без ограничений -- **Влияние:** Неограниченный рост памяти при атаке - -### 3. **Multiple to_vec() в горячем пути** -- **Файл:** `ostp-server/src/relay.rs:74, 160, 165, 174` -- **Проблема:** Выделение памяти для каждого пакета -- **Влияние на пропускную способность:** СРЕДНЕ - -### 4. **Excessive cloning в relay.rs** -```rust -// relay.rs:47-55 -let mut connect_target = target.clone(); // ❌ Лишнее -let target_clone = connect_target.clone(); // ❌ Лишнее -let connect_tx_clone = connect_tx.clone(); -let stream_tx_clone = stream_tx.clone(); -let router_clone = router.clone(); -``` - ---- - -## 📊 ОЦЕНКИ ПАПОК (по 10-балльной шкале) - -### 📦 **ostp-server** — 5.2/10 -**Стабильность: 4/10 | Скорость: 5/10 | Пропускная способность: 6/10** - -#### Сильные стороны: -- ✅ Буферы сокетов: 32MB (хорошо) -- ✅ Асинхронная архитектура (tokio) -- ✅ Поддержка UDP и TCP -- ✅ Rate limiting и token bucket - -#### Проблемы: -- ❌ 84 unwrap/expect (паники) -- ❌ Неограниченный replay_cache -- ❌ Лишние clone() операции в relay.rs -- ❌ to_vec() в горячем пути -- ❌ RwLock contention в dispatcher -- ❌ Нет backpressure handling - -**Рекомендации:** -1. Заменить все `.unwrap()` на `?` или `.unwrap_or_else()` -2. Добавить максимум 10K записей в replay_cache с LRU eviction -3. Убрать ненужные clones (lines 47, 52-55) -4. Использовать `Bytes` вместо `Vec` для пакетов -5. Добавить канал backpressure для relay streams - ---- - -### 🔐 **ostp-core** — 7.8/10 -**Стабильность: 8/10 | Скорость: 8/10 | Пропускная способность: 7/10** - -#### Сильные стороны: -- ✅ Криптография (Noise, ChaCha20Poly1305) -- ✅ Чистая архитектура -- ✅ Хорошо структурирована -- ✅ Congestion control module - -#### Проблемы: -- ⚠️ Padding strategy может замедлить throughput -- ⚠️ Resumption logic complexity - -**Рекомендации:** -1. Оптимизировать padding для low-latency режима -2. Бенчмарк криптографических операций - ---- - -### 💻 **ostp-client** — 7.5/10 -**Стабильность: 7/10 | Скорость: 8/10 | Пропускная способность: 7/10** - -#### Сильные стороны: -- ✅ Только 21 unwrap (хорошо!) -- ✅ Хороший panic hook для логирования -- ✅ Поддержка Windows/Linux - -#### Проблемы: -- ⚠️ bridge.rs.bak и runner.rs.bak — неудалённые файлы -- ⚠️ TODO: detect physical interface for bypassing - -**Рекомендации:** -1. Удалить .bak файлы -2. Реализовать physical interface detection -3. Добавить pool буферов для TUN I/O - ---- - -### 🌐 **ostp-gui** — 5.0/10 -**Стабильность: 5/10 | Скорость: 5/10 | Пропускная способность: N/A** - -#### Проблемы: -- ❌ Tauri app (src-tauri исключена из workspace) -- ⚠️ Зависит от стабильности backend -- ⚠️ UI может отставать при высоких нагрузках - ---- - -### 📡 **ostp-jni** — 6.5/10 -**Стабильность: 6/10 | Скорость: 7/10 | Пропускная способность: 6/10** - -#### Проблемы: -- ⚠️ JNI interface complexity -- ⚠️ Garbage collection паузы в Java - ---- - -### 🔗 **netstack-smoltcp** — 7.0/10 -**Стабильность: 7/10 | Скорость: 7/10 | Пропускная способность: 7/10** - -#### Сильные стороны: -- ✅ Mature smoltcp backend (0.12) -- ✅ IPv4 + IPv6 support -- ✅ TCP + UDP sockets - -#### Проблемы: -- ⚠️ Embedded stack complexity -- ⚠️ Per-packet overhead - ---- - -### 📋 **ostp-license** — 6.0/10 -**Стабильность: 6/10 | Скорость: 8/10 | Пропускная способность: N/A** - -#### Проблемы: -- ❌ TODO: HMAC verify (низкий приоритет) -- ⚠️ Зависит от внешних API - ---- - -### 🧠 **ostp-brain** — 5.5/10 -**Стабильность: 5/10 | Скорость: 6/10 | Пропускная способность: N/A** - -#### Проблемы: -- ❌ Исключена из workspace -- ⚠️ Неизвестное состояние поддержки - ---- - -### 🔍 **ostp-prober** — 6.0/10 -**Стабильность: 6/10 | Скорость: 6/10 | Пропускная способность: 7/10** - -#### Проблемы: -- ⚠️ Исключена из workspace -- ⚠️ Диагностический инструмент (не critical) - ---- - -### 📱 **ostp-flutter** — 5.0/10 -**Стабильность: 5/10 | Скорость: 5/10 | Пропускная способность: N/A** - -#### Проблемы: -- ⚠️ Зависит от ostp-core stability -- ⚠️ Mobile platform constraints - ---- - -### ⚙️ **ostp-sandbox** — 4.0/10 -**Стабильность: 4/10 | Скорость: 4/10 | Пропускная способность: N/A** - -#### Проблемы: -- ❌ Исключена из workspace -- ⚠️ Неясное предназначение - ---- - -### 🎮 **ostp-control** — 5.5/10 -**Стабильность: 5/10 | Скорость: 6/10 | Пропускная способность: N/A** - -#### Проблемы: -- ❌ Исключена из workspace -- ⚠️ Состояние неизвестно - ---- - -### 📡 **ostp-tun-helper** — 6.5/10 -**Стабильность: 6/10 | Скорость: 7/10 | Пропускная способность: 6/10** - -#### Сильные стороны: -- ✅ Platform-specific TUN handling -- ✅ Разделение привилегий - ---- - -### 🌍 **docs** — 7.0/10 -**Документация хорошая, но есть пробелы** - ---- - -## 🎯 ПРИОРИТЕТЫ ИСПРАВЛЕНИЙ - -### 🔴 КРИТИЧНЫЕ (Неделя 1) -``` -1. ✅ Заменить .unwrap() на Result propagation в ostp-server -2. ✅ Добавить bounds checking для replay_cache -3. ✅ Убрать ненужные clone() из relay.rs -4. ✅ Использовать Bytes вместо Vec в горячих путях -``` - -### 🟠 ВЫСОКИЕ (Неделя 2-3) -``` -5. Добавить backpressure механизм -6. RwLock → Arc в dispatcher для лучшей fairness -7. Удалить .bak файлы из ostp-client -8. Реализовать connection pooling в API -``` - -### 🟡 СРЕДНИЕ (Месяц 1) -``` -9. Бенчмарк производительности криптографии -10. Оптимизировать padding strategy -11. Реализовать HMAC verify в ostp-license -12. Добавить monitoring для memory leaks -``` - ---- - -## 📈 РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ - -### Ожидаемые улучшения после исправлений: - -| Модуль | До | После | Улучшение | -|--------|-----|-------|-----------| -| ostp-server | 5.2 | **7.5** | +43% | -| ostp-core | 7.8 | **8.2** | +5% | -| ostp-client | 7.5 | **8.3** | +11% | -| **СРЕДНЕЕ** | **6.8** | **8.0** | +18% | - ---- - -## 💡 КЛЮЧЕВЫЕ МЕТРИКИ - -| Метрика | Значение | Статус | -|---------|----------|--------| -| Кол-во строк | 69,025 | ℹ️ | -| Unwrap вызовов | 105 (84+21) | 🔴 | -| TODO/FIXME | 4 | 🟡 | -| Backup файлов | 2 | 🔴 | -| Буффер размер | 32MB | ✅ | -| Async runtime | tokio | ✅ | - ---- - -## 🔐 БЕЗОПАСНОСТЬ - -- ✅ Шифрование: ChaCha20Poly1305 + Noise -- ✅ Key derivation: HKDF + x25519 -- ✅ Ed25519 для подписей -- ⚠️ License verification может быть улучшена - ---- - -## 📌 ИТОГИ - -**Общая оценка проекта: 6.8/10** - -**Вердикт:** Проект функционален, но **требует стабилизации ostp-server** для production. - -**Рекомендуемый road map:** -1. **2 недели:** Критические исправления (unwrap, replay_cache, clone) -2. **1 месяц:** Оптимизация производительности и backpressure -3. **2 месяца:** Load testing и battle-hardening - -После этих улучшений проект будет готов к production с оценкой **8.0+/10**. diff --git a/CLIENTS_DETAILED_ANALYSIS.md b/CLIENTS_DETAILED_ANALYSIS.md deleted file mode 100644 index e1e8933..0000000 --- a/CLIENTS_DETAILED_ANALYSIS.md +++ /dev/null @@ -1,622 +0,0 @@ -# OSTP Клиенты — Детальный анализ (ostp-client, ostp-gui, ostp-flutter) - -**Дата анализа:** 2026-06-17 - ---- - -## 📊 СРАВНИТЕЛЬНАЯ ТАБЛИЦА - -| Параметр | ostp-client (Rust CLI) | ostp-gui (Tauri) | ostp-flutter (Mobile) | -|----------|:---:|:---:|:---:| -| **Язык** | Rust | Rust + TypeScript | Dart | -| **Строк кода** | 3,433 | 912 | ~1,500 | -| **Платформы** | Windows, Linux, macOS | Windows, macOS, Linux | iOS, Android | -| **Unwrap вызовов** | 21 | 20 | 0 (Dart не имеет unwrap) | -| **TUN поддержка** | ✅ Windows/Linux | ✅ Windows (via helper) | ✅ iOS/Android | -| **SOCKS5 прокси** | ✅ | ✅ | ❌ | -| **UI** | TUI (terminal) | GUI (Tauri) | Mobile (Flutter) | -| **Архитектура** | В процессе | в процессе + отдельный helper | Native bridge | -| **Стабильность** | 7.5/10 | 6.5/10 | 6.0/10 | - ---- - -## 🖥️ 1. OSTP-CLIENT (CLI + TUI) - -### 📏 Размер и структура -``` -ostp-client/src: - 3,433 строк (основной код) - - app.rs (119 строк) — UI состояние - - bridge.rs (26 строк) — Метрики - - runner.rs (74 строк) — Основной loop - - config.rs (314 строк) — Конфиг парсинг - - logging.rs (118 строк) — Логирование - - sysproxy.rs (278 строк) — Windows proxy - - tunnel/router.rs (155 строк) — Маршрутизация - - tunnel/process_lookup.rs (195 строк) — Windows/Linux process lookup - - tunnel/inbounds/tun.rs (300 строк) — TUN interface - - tunnel/inbounds/local_proxy.rs (224 строк) — SOCKS5 прокси - - transport/xhttp.rs (394 строк) — HTTP transport -``` - -### ✅ Сильные стороны - -1. **Хороший контроль ошибок** - - Только 21 unwrap/expect (самый низкий показатель) - - Использует `?` оператор для пропагации ошибок - -2. **Полнофункциональность** - - Поддержка TUN (Windows/Linux) - - SOCKS5 прокси - - Маршрутизация по доменам/IP/процессам - - Исключения (bypass) - -3. **Хороший logging** - - setup_panic_hook() для crash logs - - Полная поддержка трассировки - - Работает с файлами и stderr - -4. **Cross-platform** - - Windows API (process lookup, sysproxy) - - Unix/Linux поддержка - - macOS совместимость - -5. **Оптимизации** - - Buffer pooling в TUN I/O - - Async/await с tokio - - Rate limiting - -### ❌ Критические проблемы - -1. **Backup файлы** - ``` - ❌ ostp-client/src/bridge.rs.bak (115,500 строк!) - ❌ ostp-client/src/runner.rs.bak (15,289 строк!) - ``` - - Не удалены неиспользуемые файлы - - Занимают дисковое пространство - - Могут вызвать путаницу при работе - -2. **Performance Issues в hot paths** - - **router.rs:50-67**: `to_lowercase()` для каждого SNI matcher - ```rust - let d = d.to_lowercase(); // ❌ На каждый чек - ``` - - **router.rs:67**: String allocation в process match - ```rust - proc.contains(&p.to_lowercase()) // ❌ Выделение памяти - ``` - -3. **UDP Handler incomplete** - ```rust - // ostp-client/src/tunnel/outbounds/ostp.rs:93 - Err(anyhow!("OSTP UDP handler not yet fully migrated")) - ``` - - UDP поддержка неполная - - Это критично для производительности! - -4. **Platform-specific issues** - - **TODO: detect physical interface index for bypassing** (runner.rs) - - Windows: Неправильное определение интерфейса для bypass - -5. **TUN buffer configuration** - ```rust - // ostp-client/src/tunnel/inbounds/tun.rs:56-58 - .stack_buffer_size(1024) // ❌ Маленький буффер! - .tcp_buffer_size(1024) - .udp_buffer_size(1024) - ``` - - 1024 bytes буффер ОЧЕНЬ маленький для throughput - - Должно быть 32KB-64KB минимум - -6. **Memory leak в process lookup** - - Windows API вызывает `vec![0u8; 1024]` без переиспользования - - При высокой активности может быть проблемой - -7. **Connection state tracking** - - Нет rate limiting на reconnects - - Может привести к DoS при частых сбоях - -### 📈 Оценка: 7.5/10 - -| Метрика | Оценка | Примечание | -|---------|:---:|----------| -| Стабильность | 7/10 | Хороший error handling, но UDP incomplete | -| Скорость | 8/10 | Async/await хорошо, но буферы маленькие | -| Пропускная способность | 7/10 | Много allocations в hot paths | -| Кодовое качество | 7/10 | Чистый код, но backup файлы и TODO | - -### 🔧 Рекомендации - -**КРИТИЧНЫЕ (Неделя 1):** -1. ❌ Удалить bridge.rs.bak и runner.rs.bak -2. ⬆️ Увеличить буферы TUN: - ```rust - .stack_buffer_size(32768) // 32KB - .tcp_buffer_size(32768) - .udp_buffer_size(32768) - ``` -3. ✅ Реализовать UDP handler полностью -4. 🎯 Добавить rate limiting на reconnects - -**ВЫСОКИЕ (Неделя 2-3):** -5. 🔤 Кэшировать `to_lowercase()` в router -6. 📍 Реализовать physical interface detection -7. 🔄 Переиспользовать буферы в process lookup -8. 📊 Добавить metrics для buffer utilization - ---- - -## 🎨 2. OSTP-GUI (Tauri + TypeScript) - -### 📏 Размер и структура -``` -ostp-gui/src-tauri/src: - 912 строк (Rust backend) - - lib.rs (843 строк) — Основная логика - - main.rs (69 строк) — Entry point - -ostp-gui/src: - TypeScript + React/Svelte - - Файлы не включены в анализ -``` - -### ✅ Сильные стороны - -1. **Хороший UI/UX** - - Tauri для native feel - - Поддержка tray icon - - Single instance lock - - Autostart на Windows - -2. **Безопасность** - - Tokenization для UAC elevation - - Temp file для auth token (не в argv!) - - Platform-specific elevation (UAC, pkexec, osascript) - -3. **Multi-mode поддержка** - - In-process режим (прокси) - - Helper режим (TUN с привилегиями) - - Hot-reload конфига - -4. **Хороший error handling** - - Обработка паник - - Dialog для отображения ошибок - - Логирование в файл - -5. **Кроссплатформенность** - - Windows (UAC, registry) - - macOS (osascript, osascript) - - Linux (pkexec) - -### ❌ Критические проблемы - -1. **20 unwrap/expect в коде** - - Выше, чем хотелось бы - - Примеры: - ```rust - // lib.rs:536 - listener.local_addr().unwrap() - - // lib.rs:559 - serde_json::to_string(&mapped).unwrap_or_default() - - // lib.rs:365 - serde_json::to_string(&core_cfg).unwrap() - ``` - -2. **Процесс управления TUN слишком сложный** - - Запуск отдельного helper с UAC - - IPC через JSON lines - - Потенциальные race conditions - - Temp файлы не гарантированно удаляются - -3. **Отсутствие timeout для helper connection** - ```rust - // lib.rs:544-551 - timeout 60 секунд для подключения к helper - // ❌ Слишком долго! Пользователь ждёт. - ``` - -4. **Process list loading может зависнуть** - ```rust - // lib.rs:162-219 - Синхронный вызов tasklist/ps каждый раз - // ❌ Может блокировать UI в процессе сканирования - ``` - -5. **Memory leaks в HelperPipeState** - - Нет cleanup для temp файлов auth token - - Нет гарантированного kill helper процесса при выходе - -6. **Token validation отсутствует** - ```rust - // lib.rs:557-559 - Отправляет конфиг в plain text через pipe - // ❌ Нет шифрования между GUI и helper! - ``` - -7. **Config migration хрупкая** - ```rust - // lib.rs:282-284 - Полагается на комментарий в JSON - // "// OSTP Configuration v0.3.1" - // ❌ Может сломаться при форматировании - ``` - -8. **Нет версионирования для IPC** - - Если helper и GUI из разных версий — crash - - Нет fallback механизма - -### 🔄 Процесс запуска TUN (ОЧЕНЬ сложный!) - -```mermaid -GUI: Нажимаем "Connect" - ↓ - → Читаем config.json - → Проверяем wintun.dll - → Находим ostp-tun-helper.exe - → Генерируем random token - → Пишем token в temp file - ↓ - → Вызываем ShellExecuteW с UAC - ↓ -Helper: Запускается с привилегиями - → Слушает на TCP 127.0.0.1:port - → Ждёт подключения GUI - ↓ -GUI: Подключается к helper (retry 200мс × N) - → Отправляет JSON: {cmd: "start", config, token} - ↓ -Helper: Парсит JSON - → Запускает tunnel - → Отправляет status JSON каждый tick - ↓ -GUI: Получает JSON lines - → Обновляет UI state - → Показывает метрики -``` - -**Проблемы:** -- 🔴 Если helper не запустится — зависает на 60 сек timeout -- 🔴 Если temp file удалится — helper не сможет прочитать token -- 🔴 IPC не зашифрована -- 🔴 Нет graceful shutdown helper - -### 📈 Оценка: 6.5/10 - -| Метрика | Оценка | Примечание | -|---------|:---:|----------| -| Стабильность | 6/10 | Helper IPC может сломаться | -| Скорость | 6/10 | 60сек timeout, процесс list синхронно | -| Пропускная способность | 7/10 | OK, но зависит от helper | -| Удобство | 8/10 | Хороший UI | -| Кодовое качество | 5/10 | Много unwraps, IPC не безопасна | - -### 🔧 Рекомендации - -**КРИТИЧНЫЕ (Неделя 1):** -1. 🔐 Зашифровать IPC между GUI и helper (AES-256) -2. ⏱️ Снизить timeout с 60 до 15 сек -3. 🗑️ Гарантировать cleanup temp файлов -4. 🔄 Добавить версионирование для IPC messages - -**ВЫСОКИЕ (Неделя 2-3):** -5. ❌ Заменить все unwrap на Result -6. 🔀 Async process list loading (не блокировать UI) -7. 🎯 Добавить graceful shutdown helper -8. 📊 Добавить heartbeat между GUI и helper - -**СРЕДНИЕ (Месяц 1):** -9. 🔔 Notification system для helper ошибок -10. 📝 Version migration guide для config - ---- - -## 📱 3. OSTP-FLUTTER (Mobile) - -### 📏 Размер и структура -``` -ostp-flutter/lib: - ~1,500 строк (Dart) - - main.dart (42 строк) — Entry point - - ui/home_screen.dart (~300 строк) — Основной UI - - ui/settings_screen.dart - - ui/logs_screen.dart - - ui/qr_scanner_screen.dart - - models/connection_state_enum.dart -``` - -### ✅ Сильные стороны - -1. **Нативный мобильный опыт** - - Flutter для iOS/Android - - Native bridge (MethodChannel) - - Platform-specific implementations - -2. **Хороший UI/UX** - - Material 3 design - - Animations (pulse, spin) - - Dark theme - - QR scanner для конфига - -3. **Отсутствие паник** - - Dart не имеет unwrap() - - Тип safety гарантирует?/null checks - - try-catch для error handling - -4. **Сохранение состояния** - - SharedPreferences для settings - - Auto-reconnect механизм - - Uptime tracking - -5. **Удобная конфигурация** - - Введение вручную - - QR code сканирование - - Сохранение в SharedPreferences - -### ❌ Критические проблемы - -1. **Отсутствие SOCKS5 прокси** - - Только TUN поддержка - - Нельзя использовать как прокси для браузера - - Нет split tunneling по приложениям (нативно) - -2. **Native bridge не зашифрован** - ```dart - // home_screen.dart:24 - static const platform = MethodChannel('com.ospab.ostp/vpn'); - // ❌ Нет шифрования между Dart и native! - ``` - -3. **Polling механизм неэффективен** - ```dart - _pollTimer = Timer.periodic(Duration(seconds: 1), (_) { - platform.invokeMethod('getStatus'); - }); - // ❌ Каждую секунду IPC вызов! - ``` - - 60 вызовов в минуту - - Потребление батареи и CPU - - Сеть может быть дорогой на мобильных - -4. **Отсутствие проверки версии** - - Нет версионирования между Dart и native - - Если native code разные версии → crash - -5. **Config parsing уязвимость** - ```dart - // home_screen.dart:79-130 - Парсит JSON без валидации - // Большой JSON может привести к OutOfMemory - ``` - -6. **Hardcoded localhost** - - Привязка к 127.0.0.1 в конфиге - - Невозможно подключиться к удалённому серверу - - Нет мультисерверной поддержки - -7. **DNS переопределение на Android** - ```dart - final effectiveDnsServer = (dnsServer == null || dnsServer.isEmpty) - ? '1.1.1.1' : dnsServer; - // ❌ Жёсткий fallback, нет системного DNS - ``` - -8. **Логирование отсутствует** - - debugPrint() только для ошибок - - Нет файлового логирования - - Сложно диагностировать проблемы на production - -9. **Memory leak в animations** - ```dart - _pulseController = AnimationController(vsync: this); - _spinController = AnimationController(vsync: this); - // ❌ Контроллеры не dispose в некоторых путях - ``` - -10. **Отсутствие rate limiting** - - Пользователь может спамить "Connect" - - Может привести к множественным соединениям - -### 📊 Traffic calculations issues - -```dart -// home_screen.dart:130-150 -final configMap = { - "download_speed": int.parse(_download.replaceAll(RegExp(r'[^\d]'), '') ?? "0"), - "upload_speed": int.parse(_upload.replaceAll(RegExp(r'[^\d]'), '') ?? "0"), - // ❌ Неправильный парсинг! "10.5 MB" → "105"! -}; -``` - -### 📈 Оценка: 6.0/10 - -| Метрика | Оценка | Примечание | -|---------|:---:|----------| -| Стабильность | 6/10 | Нет crash detection, memory leaks | -| Скорость | 6/10 | Excessive polling, animations heavy | -| Батарея | 5/10 | Continuous polling, animations | -| Пропускная способность | 5/10 | Только TUN, нет контроля | -| Кодовое качество | 6/10 | Нет logging, парсинг хрупкий | - -### 🔧 Рекомендации - -**КРИТИЧНЫЕ (Неделя 1):** -1. 🔐 Зашифровать native bridge (TLS / AEAD) -2. 📢 Заменить polling на event-based updates (callbacks) -3. 🛡️ Добавить crash handler (Sentry/Firebase) -4. 🔢 Исправить traffic parsing - -**ВЫСОКИЕ (Неделя 2-3):** -5. 📝 Добавить файловое логирование -6. 🎯 Добавить rate limiting на кнопки -7. 🗑️ Dispose animations в cleanup -8. 📌 Добавить версионирование для native bridge - -**СРЕДНИЕ (Месяц 1):** -9. 🌐 Поддержка удалённых серверов -10. 🔄 Система DNS fallback (система → custom → 1.1.1.1) - ---- - -## 🎯 СРАВНЕНИЕ КЛИЕНТОВ - -### По Стабильности -``` -ostp-client ████████░░ 7.5/10 ← Лучше -ostp-gui ██████░░░░ 6.5/10 -ostp-flutter ██████░░░░ 6.0/10 ← Хуже -``` - -### По Скорости -``` -ostp-client ████████░░ 8.0/10 ← Лучше (буферы маленькие, но быстрый) -ostp-gui ██████░░░░ 6.0/10 (тяжёлый UI overhead) -ostp-flutter ██████░░░░ 6.0/10 ← Хуже (polling + UI lag) -``` - -### По Пропускной способности -``` -ostp-client ███████░░░ 7.0/10 ← Лучше -ostp-gui ███████░░░ 7.0/10 -ostp-flutter █████░░░░░ 5.0/10 ← Хуже (только TUN) -``` - -### По Удобству использования -``` -ostp-client █████░░░░░ 5.0/10 ← CLI/TUI -ostp-gui ████████░░ 8.0/10 ← Лучше (красивый GUI) -ostp-flutter ███████░░░ 7.0/10 -``` - ---- - -## 📋 UNIFIED ISSUES (ОБЩИЕ ДЛЯ ВСЕХ) - -### 1. **Отсутствие IPC шифрования** -- ostp-gui: JSON без шифрования между GUI и helper -- ostp-flutter: Native bridge без шифрования -- **РИСК:** MITM атаки, утечка конфига - -### 2. **Config migration хрупкая** -- Все клиенты используют JSON с комментариями -- Парсинг может сломаться при форматировании -- Нет версионирования - -### 3. **Нет graceful shutdown** -- Может привести к потере конфига -- Незаконченные операции I/O - -### 4. **Logging недостаточный** -- ostp-client: OK -- ostp-gui: File logging, но неполный -- ostp-flutter: Только debugPrint - -### 5. **Отсутствие crash reporting** -- Нет сбора информации о падениях -- Сложно диагностировать production issues - ---- - -## 🏆 ИТОГОВЫЕ ОЦЕНКИ - -| Клиент | Стабильность | Скорость | Пропускная способность | **Общая** | Рекомендация | -|--------|:---:|:---:|:---:|:---:|---------| -| **ostp-client** | 7/10 | 8/10 | 7/10 | **7.3/10** | ✅ Production-ready (с исправлениями) | -| **ostp-gui** | 6/10 | 6/10 | 7/10 | **6.3/10** | ⚠️ Beta (нужны исправления) | -| **ostp-flutter** | 6/10 | 6/10 | 5/10 | **5.7/10** | 🔴 Alpha (много работы) | - ---- - -## 🚀 ФАЗА УЛУЧШЕНИЙ - -### **НЕДЕЛЯ 1** (Критичные) -``` -ostp-client: - - ❌ Удалить .bak файлы - - ⬆️ Увеличить TUN буферы 32KB - - ✅ Реализовать UDP handler - -ostp-gui: - - 🔐 Зашифровать IPC (AES-256) - - ⏱️ Timeout 60→15 сек - - 🗑️ Cleanup temp files - -ostp-flutter: - - 🔐 Зашифровать native bridge - - 📢 Polling → Event-based - - 🔢 Исправить traffic parsing -``` - -### **НЕДЕЛЯ 2-3** (Высокие) -``` -ostp-client: - - 🔤 Кэшировать to_lowercase() - - 📍 Physical interface detection - -ostp-gui: - - ❌ Все unwrap → Result - - 🔀 Async process list - -ostp-flutter: - - 📝 File logging - - 🎯 Rate limiting buttons -``` - -### **МЕСЯЦ 1** (Средние) -``` -Все: - - 🔔 Crash reporting (Sentry) - - 📊 Telemetry & metrics - - 🧪 Integration tests - - 📖 Documentation -``` - ---- - -## 💡 АРХИТЕКТУРНЫЕ РЕКОМЕНДАЦИИ - -### Для ostp-client -``` -Текущая: CLI → bridge → tunnel → TUN/SOCKS5 -Нужна: CLI → async bridge → thread pool → buffered I/O -``` - -### Для ostp-gui -``` -Текущая: GUI → JSON IPC → helper → tunnel -Проблема: Нет безопасности, нет версионирования -Нужна: GUI → Encrypted RPC (protobuf/msgpack) → versioned helper -``` - -### Для ostp-flutter -``` -Текущая: Dart → polling → native → tunnel -Проблема: Неэффективно, нет logging -Нужна: Dart ← events → native (callback-based) - + File logging + Sentry -``` - ---- - -## 📌 ФИНАЛЬНЫЙ ВЕРДИКТ - -### ostp-client: **7.3/10** ✅ -**Лучший выбор для production после небольших исправлений** -- Проблемы: Маленькие буферы, UDP incomplete, backup файлы -- Срок исправления: 1 неделя -- Потом готов к production - -### ostp-gui: **6.3/10** ⚠️ -**Хороший UI, но нужна безопасность** -- Проблемы: IPC не зашифрована, timeout 60сек, unwraps -- Срок исправления: 2-3 недели -- Опасна для использования в public networks - -### ostp-flutter: **5.7/10** 🔴 -**Ещё в разработке** -- Проблемы: Polling excessive, no logging, parsing bugs -- Срок исправления: 1 месяц -- Пока только для личного использования - diff --git a/CODE_REVIEW_2026_06_17.md b/CODE_REVIEW_2026_06_17.md deleted file mode 100644 index ad3705e..0000000 --- a/CODE_REVIEW_2026_06_17.md +++ /dev/null @@ -1,539 +0,0 @@ -# 🔍 Полный Code Review - OSTP проект -**Дата:** 17 июня 2026 -**Статус:** Критические и серьёзные проблемы выявлены -**Проверено:** 99 Rust файлов, 204 исходных файла - ---- - -## 📊 Сводка по критичности - -| Уровень | Количество | Время исправления | -|---------|-----------|------------------| -| 🔴 **CRITICAL** | 4 | 4-6 часов | -| 🟠 **HIGH** | 11 | 8-12 часов | -| 🟡 **MEDIUM** | 6 | 12-20 часов | -| 🟢 **LOW** | 5 | 5-10 часов | - ---- - -## 🔴 КРИТИЧЕСКИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ НЕМЕДЛЕННО) - -### 1. ⚠️ Открытый Management API без аутентификации -**Файл:** `ostp-server/src/api.rs:313-315` -**Риск:** Несанкционированный доступ к управлению сервером - -```rust -// ❌ ПЛОХО - если нет credentials, API открыт для всех -if state.username.is_empty() && state.password_hash.is_empty() && state.api_token.is_none() { - return true; -} -``` - -**Последствия:** Любой, кто может достичь API порт, может: -- Включать/выключать туннели -- Менять конфигурацию -- Просматривать статистику трафика -- Управлять пользователями - -**Решение:** -```rust -// ✅ ХОРОШО - требовать хотя бы один способ аутентификации -if state.username.is_empty() && state.password_hash.is_empty() && state.api_token.is_none() { - warn!("API authentication disabled - server will not accept connections"); - return false; // Запретить доступ -} -``` - ---- - -### 2. 💾 Небезопасные операции с памятью (Windows Process Lookup) -**Файл:** `ostp-client/src/tunnel/process_lookup.rs:12-120` -**Риск:** Buffer overread, крах приложения, потенциальный exploitable bug - -```rust -// ❌ ПЛОХО - нет проверки границ перед разыменованием -let row_ptr = table as *const MIB_TCPROW; -for i in 0..num_entries { - let row = *row_ptr.add(i as usize); // Может выйти за границы -} -``` - -**Проблемы:** -- Не проверяется `dwNumEntries` перед доступом к массиву -- Pointer arithmetic без bounds checking -- Windows API может вернуть некорректные данные - -**Решение:** -```rust -// ✅ ХОРОШО - с проверкой границ -let table = table as *const MIB_TCPROW; -for i in 0..num_entries.min(table_len) { // Ограничить максимум - if let Some(row) = table.as_ref() { - // безопасная операция - } -} -``` - ---- - -### 3. 🔐 Небезопасный ввод-вывод TUN (Unix/Linux) -**Файл:** `ostp-client/src/tunnel/inbounds/tun.rs:83, 95, 121` -**Риск:** Buffer overflow, крах, потеря данных - -```rust -// ❌ ПЛОХО - размер буфера 65535, нет проверки return value -let res = unsafe { - libc::read(inner.as_raw_fd(), frame.as_mut_ptr() as *mut libc::c_void, frame.len()) -}; -// frame может быть 65535 байт, а прочитано 100 - потом пишем 65535! -``` - -**Проблемы:** -- `libc::read()` может вернуть меньше байт, чем запрошено -- Нет обработки отрицательных значений (ошибки) -- Используется весь размер буфера вместо реально прочитанных данных - -**Решение:** -```rust -// ✅ ХОРОШО - с проверкой и обработкой ошибок -let res = match unsafe { - libc::read(inner.as_raw_fd(), frame.as_mut_ptr() as *mut libc::c_void, frame.len()) -} { - n if n > 0 => n as usize, - 0 => return Ok(None), // EOF - _ => return Err(io::Error::last_os_error()), -}; -// Использовать res вместо frame.len() -``` - ---- - -### 4. 🔑 Слабое хеширование паролей (Plain SHA256) -**Файл:** `ostp-server/src/api.rs:358-362` -**Риск:** Rainbow table attack, компромисс credentials - -```rust -// ❌ ПЛОХО - SHA256 без salt = уязвимо -let password = payload.password.unwrap_or_default(); -let hash = sha2::Sha256::digest(password.as_bytes()); -``` - -**Проблемы:** -- SHA256 - это хеш, не функция для паролей -- Нет salt → все одинаковые пароли = один и тот же хеш -- Rainbow tables: можно купить готовые таблицы -- Быстро вычисляется (это плохо для паролей) - -**Решение:** -```rust -// ✅ ХОРОШО - использовать Argon2 -use argon2::{Argon2, PasswordHasher}; -use argon2::password_hash::SaltString; - -let salt = SaltString::generate(rand::thread_rng()); -let argon2 = Argon2::default(); -let password_hash = argon2 - .hash_password(password.as_bytes(), &salt) - .map_err(|e| anyhow::anyhow!("hash error: {}", e))? - .to_string(); -``` - -Добавить в `Cargo.toml`: -```toml -argon2 = "0.5" -``` - ---- - -## 🟠 ВЫСОКИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ НА ЭТОЙ НЕДЕЛЕ) - -### 5. 💥 305 вызовов `.unwrap()` - угроза паники -**Файл:** Множество файлов, top 3: -- `ostp-core/src/protocol.rs`: 23 unwraps -- `ostp/src/main.rs`: 18 unwraps -- `ostp-server/src/outbound.rs`: 10 unwraps - -**Критический пример:** -```rust -// ❌ ПЛОХО - паника если URL невалиден -let parsed = url::Url::parse(&link_str).unwrap(); -let host = parsed.host_str().unwrap(); -let port = parsed.port().unwrap_or(50000); -``` - -**Проблема:** Если пользователь передаст неправильный URL, сервер упадёт. - -**Решение:** -```rust -// ✅ ХОРОШО - обработка ошибок -let parsed = url::Url::parse(&link_str) - .map_err(|e| anyhow::anyhow!("invalid URL: {}", e))?; -let host = parsed.host_str() - .ok_or_else(|| anyhow::anyhow!("URL missing hostname"))?; -let port = parsed.port().unwrap_or(50000); -``` - -**Общая стратегия:** -1. CLI (main.rs) - можно использовать unwrap для быстрого выхода -2. Библиотеки и серверы - НЕ ИСПОЛЬЗОВАТЬ UNWRAP -3. Заменить на `?`, `map_err()`, `context()` - ---- - -### 6. 🔓 Небезопасные Windows API вызовы -**Файл:** `ostp-gui/src-tauri/src/lib.rs:679, 730` -**Риск:** Крах GUI, отсутствие обработки ошибок - -```rust -// ❌ ПЛОХО - нет проверки return value -let ret = unsafe { - ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(), - params_wstr.as_ptr(), dir_wstr.as_ptr(), 0) -}; -// ret <= 32 означает ошибку, но он не проверяется! -``` - -**Решение:** -```rust -// ✅ ХОРОШО - с обработкой ошибок -let ret = unsafe { - ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(), - params_wstr.as_ptr(), dir_wstr.as_ptr(), 1) // SW_SHOW -}; -if (ret as usize) <= 32 { - return Err(anyhow::anyhow!("ShellExecuteW failed: {}", ret)); -} -``` - ---- - -### 7. ⚠️ Command injection в macOS скриптах -**Файл:** `ostp-gui/src-tauri/src/lib.rs:691-692` -**Риск:** Выполнение произвольных команд через shell - -```rust -// ❌ ПЛОХО - cmd может содержать кавычки -let script = format!("do shell script \"{}\" with administrator privileges", cmd); -``` - -Если `cmd` = `"; rm -rf /`, то исполнится удаление файлов! - -**Решение:** -```rust -// ✅ ХОРОШО - экранировать специальные символы -fn escape_applescript_string(s: &str) -> String { - s.replace('\\', "\\\\") - .replace('"', "\\\"") -} - -let escaped_cmd = escape_applescript_string(cmd); -let script = format!("do shell script \"{}\" with administrator privileges", escaped_cmd); -``` - ---- - -### 8. 🔢 Integer overflow в размерах буферов -**Файл:** `ostp-client/src/tunnel/inbounds/tun.rs:88` -**Риск:** Buffer overflow, крах, потеря данных - -```rust -// ❌ ПЛОХО - нет проверки возвращаемого значения -let mut frame = vec![0u8; 65535]; -let res = unsafe { libc::read(...) }; // может быть отрицательным! -// ... -frame.len() - written // если written = -1, то integer overflow! -``` - -**Решение:** -```rust -// ✅ ХОРОШО - с обработкой -let res = unsafe { libc::read(...) }; -match res { - n if n > 0 => { - let bytes_read = n as usize; - if bytes_read > frame.len() { - return Err("read returned more bytes than buffer"); - } - frame.truncate(bytes_read); - } - 0 => return Ok(None), // EOF - _ => return Err(io::Error::last_os_error()), -} -``` - ---- - -### 9. 📝 11 вызовов `.expect()` - скрытые паники -**Файл:** Несколько файлов: -- `netstack-smoltcp/src/tcp.rs:399, 402` -- `ostp-core/src/crypto/obfuscation.rs:23, 38, 127` -- `ostp-core/src/crypto/reality.rs:29, 45` - -`expect()` - это более информативный `.unwrap()`, но всё равно паникует. - -**Решение:** Заменить на `?` или `context()`: -```rust -// ❌ ПЛОХО -let value = container.get(key).expect("key not found"); - -// ✅ ХОРОШО -let value = container.get(key) - .context("expected key to be present")?; -``` - ---- - -### 10. 🔐 Race conditions в RwLock -**Файл:** `ostp-server/src/api.rs:321, 364, 388` -**Риск:** Потеря данных при панике в критической секции - -```rust -// ❌ ПЛОХО - если поток с блокировкой упадёт, lock отравлен -*state.session_token.write().unwrap_or_else(|e| e.into_inner()) = Some(token.clone()); -``` - -**Проблема:** `unwrap_or_else` маскирует настоящую проблему (потыря данных). - -**Решение:** -```rust -// ✅ ХОРОШО - использовать drop для явного освобождения -{ - let mut token_write = state.session_token.write() - .map_err(|e| anyhow::anyhow!("token lock poisoned: {}", e))?; - *token_write = Some(token.clone()); - // Автоматический drop при выходе из блока -} -``` - ---- - -### 11. 📚 Чрезмерное использование `.clone()` (239 экземпляров) -**Файл:** `ostp-server/src/api.rs: 34 clones`, `ostp-server/src/lib.rs: 33 clones` -**Риск:** Высокое использование памяти, замедление - -**Пример:** -```rust -// ❌ ПЛОХО - клонируем весь String для каждого запроса -let username = state.username.clone(); -let response = format!("Hello, {}", username); -``` - -**Решение:** -```rust -// ✅ ХОРОШО - использовать ссылку -let response = format!("Hello, {}", &state.username); - -// Или для более сложных случаев - использовать Arc -let username = Arc::new(state.username.clone()); -``` - ---- - -## 🟡 СРЕДНИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ ЧЕРЕЗ 2 НЕДЕЛИ) - -### 12. 🚫 Отсутствие валидации входных данных -**Файл:** `ostp/src/main.rs`, `ostp-server/src/dns.rs` -**Риск:** Некорректная обработка неправильных данных - -**Проблема:** URL парсится через `.split(':')` без проверок: -```rust -// ❌ ПЛОХО -let parts: Vec<&str> = server.split(':').collect(); -let ip = parts[0]; // Может панникнуть если длина < 1! -let port = parts[1]; -``` - -**Решение:** Использовать `splitn()` и проверку длины: -```rust -// ✅ ХОРОШО -let mut parts = server.splitn(2, ':'); -let ip = parts.next().ok_or("missing IP")?; -let port = parts.next().ok_or("missing port")?; -``` - ---- - -### 13. 📏 Очень большие функции (>500 строк) -**Файл:** -- `ostp/src/main.rs`: 1813 строк (одна функция!) -- `ostp-core/src/protocol.rs`: 1006 строк -- `ostp-server/src/api.rs`: 1003 строк - -**Проблема:** Невозможно тестировать, аудировать, понимать - -**Решение:** Разбить на меньшие функции (~100-150 строк): -```rust -// ❌ ПЛОХО - 1813 строк в одной функции -fn main() { - // весь код... -} - -// ✅ ХОРОШО - разбить на логические части -fn main() -> Result<()> { - let config = load_config()?; - run_app(config).await -} - -fn load_config() -> Result { ... } -fn run_app(config: Config) -> Result<()> { ... } -``` - ---- - -### 14. 📝 4 TODO/FIXME комментария -**Файл:** -- `ostp-license/src/main.rs:321` - "TODO: implement HMAC verify" -- `ostp-client/src/runner.rs:22` - "TODO: Detect physical interface" -- `netstack-smoltcp/src/tcp.rs:142` - "FIXME: Follow system's settings" -- `ostp-client/src/tunnel/balancer.rs:43` - "TODO: Implement ping worker" - -**Решение:** Создать Issues в GitHub для каждого TODO и отследить - ---- - -### 15. 🔧 Потенциальные deadlock-и в async коде -**Файл:** `ostp-server/src/api.rs`, `ostp-client/src/tunnel/router.rs` -**Риск:** Зависание приложения (редко, но возможно) - -**Проблема:** Nested locks без явного порядка могут привести к deadlock - -**Решение:** -1. Всегда брать блокировки в одном порядке -2. Минимизировать время удержания блокировки -3. Использовать `parking_lot::RwLock` вместо `std::sync::RwLock` - ---- - -## 🟢 НИЗКИЕ ПРОБЛЕМЫ (КОСМЕТИЧЕСКИЕ, ИСПРАВИТЬ КОГДА БУДЕТ ВРЕМЯ) - -### 16. 🔍 Нежелательный код -**Файл:** `netstack-smoltcp/src/stack.rs:181`, `ostp/src/main.rs:1072` -**Проблема:** Код, который никогда не выполняется - -**Решение:** Удалить или добавить комментарий, почему это нужно - ---- - -### 17. 🔐 Слабая криптография (низкий приоритет) -**Файл:** `ostp-core/src/crypto/reality.rs` -**Проблема:** Noise pattern `NNpsk0` без forward secrecy - -**Решение:** Использовать `XX` pattern для forward secrecy (если требуется) - ---- - -### 18. 📦 Версии зависимостей -**Статус:** ✅ Хорошо (в основном актуальные версии) -- tokio 1.37 - актуальная -- chacha20poly1305 0.10 - актуальная -- chrono 0.4.44 - проверить обновления (есть сообщения о уязвимостях) - ---- - -## 📋 План исправления (Приоритет) - -### Неделя 1 (Критическое) -- [ ] Обязательная аутентификация API -- [ ] Переписать пароли на Argon2 -- [ ] Добавить bounds checking в process_lookup -- [ ] Исправить TUN I/O операции - -**Сроки:** 1-2 дня на разработку, 1 день на тестирование - -### Неделя 2 (Высокое) -- [ ] Заменить 50% unwrap() вызовов на `?` -- [ ] Исправить Windows API вызовы -- [ ] Экранировать AppleScript команды -- [ ] Исправить integer overflow в буферах - -**Сроки:** 2-3 дня - -### Неделя 3-4 (Среднее) -- [ ] Валидация входных данных -- [ ] Рефакторинг больших функций -- [ ] Создать Issues для TODO/FIXME -- [ ] Оптимизировать clone() вызовы - -**Сроки:** 3-5 дней - -### Неделя 5+ (Низкое) -- [ ] Удалить мёртвый код -- [ ] Обновить зависимости -- [ ] Добавить комментарии SAFETY для unsafe блоков - ---- - -## 🎯 Рекомендации по разработке - -### Правила для новых кодов -1. **Никогда** не используйте `.unwrap()` в production коде - используйте `?` -2. **Никогда** не используйте `format!()` с пользовательским вводом в shell - экранируйте -3. **Всегда** добавляйте `// SAFETY:` комментарии для unsafe блоков -4. **Всегда** используйте `Result` вместо `Option` для ошибок -5. **Максимум 150 строк** в одной функции -6. **Минимум** одна переменная per unsafe блок - -### Инструменты для автоматизации -```bash -# Проверить все unwrap() вызовы -cargo clippy -- -W clippy::unwrap_used - -# Проверить неиспользуемые переменные -cargo clippy -- -W unused_variables - -# Найти все TODO/FIXME -grep -r "TODO\|FIXME" --include="*.rs" . - -# Проверить на потенциальные уязвимости -cargo audit -``` - -### Настроить CI/CD -```yaml -# .github/workflows/security.yml -- name: Security check - run: cargo clippy -- -D clippy::unwrap_used - -- name: Audit dependencies - run: cargo audit - -- name: Format check - run: cargo fmt -- --check -``` - ---- - -## 📈 Метрики кодовой базы - -| Метрика | Значение | Оценка | -|---------|---------|--------| -| Размер codebase | 99 файлов | ⚠️ Большой | -| Avg функция | ~150 строк | ⚠️ Выше нормы | -| Unsafe блоки | 12+ | ⚠️ Требует аудита | -| unwrap() вызовы | 305 | 🔴 Критически много | -| expect() вызовы | 11 | ⚠️ Нужно удалить | -| clone() вызовы | 239 | ⚠️ Оптимизировать | -| Test coverage | ~60% | ⚠️ Нужно увеличить | - ---- - -## ✅ Заключение - -**Проект в целом:** 🟠 Требует срочных исправлений - -**Критические проблемы:** 4 (исправить немедленно) -**Серьёзные проблемы:** 11 (исправить на этой неделе) -**Среднее:** 6 (исправить через 2 недели) -**Низкое:** 5 (когда будет время) - -**Общий риск:** **СРЕДНИЙ-ВЫСОКИЙ** из-за security issues в API и memory safety - -После исправления критических и высоких проблем, проект будет в **ХОРОШЕМ** состоянии. - ---- - -## 📞 Контакты для вопросов - -Этот отчёт был сгенерирован автоматически AI Code Review. -Для вопросов по специфическим issue - смотри файлы по пути, указанному в каждой проблеме. - diff --git a/ostp-gui/src-tauri/Cargo.toml b/ostp-gui/src-tauri/Cargo.toml index 99485a1..b4a59c0 100644 --- a/ostp-gui/src-tauri/Cargo.toml +++ b/ostp-gui/src-tauri/Cargo.toml @@ -29,4 +29,7 @@ ostp-client = { path = "../../ostp-client" } portable-atomic = "1" json_comments = "0.2" rand = "0.8" +chacha20poly1305 = "0.10" +sha2 = "0.10" +hex = "0.4.3" diff --git a/ostp-gui/src-tauri/src/ipc_crypto.rs b/ostp-gui/src-tauri/src/ipc_crypto.rs new file mode 100644 index 0000000..c192522 --- /dev/null +++ b/ostp-gui/src-tauri/src/ipc_crypto.rs @@ -0,0 +1,41 @@ +use anyhow::{anyhow, Result}; +use chacha20poly1305::{ChaCha20Poly1305, Nonce}; +use chacha20poly1305::aead::{Aead, KeyInit}; +use sha2::{Sha256, Digest}; + +pub struct IpcCrypto { + cipher: ChaCha20Poly1305, + nonce: [u8; 12], +} + +impl IpcCrypto { + pub fn new(key: &[u8; 32]) -> Self { + let cipher = ChaCha20Poly1305::new_from_slice(key) + .expect("valid key size"); + let nonce = [0u8; 12]; + Self { cipher, nonce } + } + + pub fn encrypt(&self, plaintext: &[u8]) -> Result> { + let nonce = Nonce::from_slice(&self.nonce); + let ciphertext = self.cipher.encrypt(nonce, plaintext) + .map_err(|e| anyhow!("Encryption failed: {}", e))?; + Ok(ciphertext) + } + + pub fn decrypt(&self, ciphertext: &[u8]) -> Result> { + let nonce = Nonce::from_slice(&self.nonce); + let plaintext = self.cipher.decrypt(nonce, ciphertext) + .map_err(|e| anyhow!("Decryption failed: {}", e))?; + Ok(plaintext) + } +} + +pub fn derive_key(token: &str) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(token.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result); + key +} diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 73b4ff5..91afa05 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -7,6 +7,8 @@ use ostp_client::bridge::BridgeMetrics; use portable_atomic::Ordering; use tauri::Emitter; +mod ipc_crypto; + // ── Config types ───────────────────────────────────────────────────────────── #[derive(Debug, Deserialize, Serialize, Clone)] @@ -532,26 +534,32 @@ async fn start_tun_via_helper( app: tauri::AppHandle, ) -> Result { let port = { - let listener = std::net::TcpListener::bind("127.0.0.1:0").map_err(|e| format!("Bind error: {}", e))?; - listener.local_addr().unwrap().port() + let listener = std::net::TcpListener::bind("127.0.0.1:0") + .map_err(|e| format!("Bind error: {}", e))?; + listener.local_addr() + .map_err(|e| format!("Get local_addr failed: {}", e))?.port() }; let auth_token = rand::random::().to_string(); - let helper_exe = find_helper_exe().ok_or_else(|| "ostp-tun-helper.exe not found.".to_string())?; - launch_as_admin(&helper_exe, &auth_token, port).map_err(|e| format!("Failed to launch helper: {}", e))?; + let helper_exe = find_helper_exe() + .ok_or_else(|| "ostp-tun-helper.exe not found.".to_string())?; + launch_as_admin(&helper_exe, &auth_token, port) + .map_err(|e| format!("Failed to launch helper: {}", e))?; tokio::time::sleep(std::time::Duration::from_millis(1500)).await; - let socket = tokio::time::timeout(std::time::Duration::from_secs(60), async { + let socket = tokio::time::timeout(std::time::Duration::from_secs(15), async { loop { match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)).await { Ok(s) => return Ok::<_, std::io::Error>(s), Err(_) => tokio::time::sleep(std::time::Duration::from_millis(200)).await, } } - }).await.map_err(|_| "Timeout connecting to helper.".to_string())? + }).await.map_err(|_| "Timeout connecting to helper (15s)".to_string())? .map_err(|e| e.to_string())?; - // Send the config + let key = ipc_crypto::derive_key(&auth_token); + let crypto = ipc_crypto::IpcCrypto::new(&key); + let mapped = parsed_config.clone(); let start_cmd = serde_json::json!({ "cmd": "start", @@ -559,15 +567,26 @@ async fn start_tun_via_helper( "token": auth_token }).to_string(); + let encrypted_cmd = crypto.encrypt(start_cmd.as_bytes()) + .map_err(|e| format!("Encryption failed: {}", e))?; + let encoded_cmd = hex::encode(&encrypted_cmd); + let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::channel::(16); - let pipe_state = Arc::new(Mutex::new(HelperPipeState { connection_state: 1, bytes_sent: 0, bytes_recv: 0, rtt_ms: 0, error_msg: None })); + let pipe_state = Arc::new(Mutex::new(HelperPipeState { + connection_state: 1, + bytes_sent: 0, + bytes_recv: 0, + rtt_ms: 0, + error_msg: None + })); let state_for_task = pipe_state.clone(); + let crypto_for_task = ipc_crypto::IpcCrypto::new(&key); tokio::spawn(async move { use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, split}; let (reader_half, mut writer_half) = split(socket); let mut reader = BufReader::new(reader_half); - let _ = writer_half.write_all(format!("{}\n", start_cmd).as_bytes()).await; + let _ = writer_half.write_all(format!("{}\n", encoded_cmd).as_bytes()).await; let mut line = String::new(); loop { @@ -576,23 +595,41 @@ async fn start_tun_via_helper( if result.unwrap_or(0) == 0 { break; } let trimmed = line.trim().to_string(); line.clear(); - if let Ok(msg) = serde_json::from_str::(&trimmed) { - let mut s = state_for_task.lock().await; - match msg { - HelperMsg::Status { value } => s.connection_state = value, - HelperMsg::Metrics { bytes_sent, bytes_recv, rtt_ms } => { s.bytes_sent = bytes_sent; s.bytes_recv = bytes_recv; s.rtt_ms = rtt_ms; } - HelperMsg::Error { message } => { - s.connection_state = 0; - s.error_msg = Some(message.clone()); - eprintln!("Helper error: {}", message); - let _ = app.emit("tunnel-error", message); + + if let Ok(encrypted_bytes) = hex::decode(&trimmed) { + if let Ok(decrypted) = crypto_for_task.decrypt(&encrypted_bytes) { + if let Ok(msg_str) = String::from_utf8(decrypted) { + if let Ok(msg) = serde_json::from_str::(&msg_str) { + let mut s = state_for_task.lock().await; + match msg { + HelperMsg::Status { value } => s.connection_state = value, + HelperMsg::Metrics { bytes_sent, bytes_recv, rtt_ms } => { + s.bytes_sent = bytes_sent; + s.bytes_recv = bytes_recv; + s.rtt_ms = rtt_ms; + } + HelperMsg::Error { message } => { + s.connection_state = 0; + s.error_msg = Some(message.clone()); + eprintln!("Helper error: {}", message); + let _ = app.emit("tunnel-error", message); + } + _ => {} + } + } } - _ => {} } } } cmd = cmd_rx.recv() => { - if let Some(c) = cmd { let _ = writer_half.write_all(c.as_bytes()).await; } else { break; } + if let Some(c) = cmd { + if let Ok(enc) = crypto_for_task.encrypt(c.as_bytes()) { + let encoded = hex::encode(&enc); + let _ = writer_half.write_all(format!("{}\n", encoded).as_bytes()).await; + } + } else { + break; + } } } } diff --git a/refactor.py b/refactor.py deleted file mode 100644 index 12b9fc4..0000000 --- a/refactor.py +++ /dev/null @@ -1,658 +0,0 @@ -import sys -import re - -with open("d:/ospab-projects/ostp/ostp-client/src/bridge.rs", "r", encoding="utf-8") as f: - code = f.read() - -start_idx = code.find(" pub async fn run(") -end_idx = -1 -brace_count = 0 -in_run = False -for i in range(start_idx, len(code)): - if code[i] == '{': - in_run = True - brace_count += 1 - elif code[i] == '}': - if in_run: - brace_count -= 1 - if brace_count == 0: - end_idx = i + 1 - break - -prefix = code[:start_idx] -suffix = code[end_idx:] - -# Define the new run function and helpers -new_run_and_helpers = """ - pub async fn run( - mut self, - tx: mpsc::Sender, - mut bridge_rx: mpsc::Receiver, - mut shutdown: watch::Receiver, - mut proxy_rx: mpsc::Receiver, - proxy_tx: mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - ) -> Result<()> { - let mut metrics_tick = interval(Duration::from_millis(500)); - let mut keepalive_tick = tokio::time::interval(Duration::from_secs(self.keepalive_interval_sec.max(1))); - let mut retransmit_tick = tokio::time::interval(Duration::from_millis(10)); - let init_msg = if self.mode == "tun" { - "Bridge initialized (TUN mode)".to_string() - } else { - "Bridge initialized (proxy mode)".to_string() - }; - tx.send(UiEvent::Log(init_msg)).await.ok(); - - let mut sessions_opt: Option> = None; - let mut udp_rx_opt: Option> = None; - let mut proxy_guard: Option = None; - let mut stream_map: std::collections::HashMap = std::collections::HashMap::new(); - - loop { - tokio::select! { - biased; - _ = shutdown.changed() => { - if *shutdown.borrow() { - self.running = false; - self.metrics.connection_state.store(0, Ordering::Relaxed); - proxy_guard = None; - sessions_opt = None; - udp_rx_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "manual stop"); - break; - } - } - udp_msg = async { - match udp_rx_opt.as_mut() { - Some(rx) => rx.recv().await, - None => std::future::pending().await, - } - }, if self.running => { - self.handle_inbound_udp(udp_msg, &mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx).await; - } - cmd = bridge_rx.recv() => { - if !self.handle_bridge_cmd(cmd, &mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx).await { - break; - } - } - _ = metrics_tick.tick() => { - if self.running { - self.emit_metrics(&tx).await; - } - } - _ = keepalive_tick.tick() => { - if self.running { - self.handle_keepalive(&mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx, &mut proxy_rx).await; - } - } - _ = retransmit_tick.tick() => { - if self.running { - self.handle_retransmit(&mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx).await; - } - } - proxy_ev = proxy_rx.recv(), if self.running && sessions_opt.as_ref().map(|s| { - s.iter().any(|ses| ses.machine.in_flight_count() < ses.machine.cwnd_packets().clamp(16, 16384)) - }).unwrap_or(true) => { - self.handle_proxy_event(proxy_ev, &mut sessions_opt, &mut stream_map, &tx, &proxy_tx).await; - } - } - } - - tx.send(UiEvent::Log("Bridge stopped".to_string())).await.ok(); - Ok(()) - } - - async fn handle_inbound_udp( - &mut self, - udp_msg: Option<(usize, Bytes)>, - sessions_opt: &mut Option>, - udp_rx_opt: &mut Option>, - proxy_guard: &mut Option, - stream_map: &mut std::collections::HashMap, - tx: &mpsc::Sender, - proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - ) { - match udp_msg { - Some((session_index, inbound)) => { - self.metrics.bytes_recv.fetch_add(inbound.len() as u64, Ordering::Relaxed); - self.last_valid_recv = Instant::now(); - if let Some(sessions) = sessions_opt.as_mut() { - if session_index < sessions.len() { - let session = &mut sessions[session_index]; - let initial_action = match session.machine.on_event(OstpEvent::Inbound(inbound)) { - Ok(a) => a, - Err(e) => { - let _ = tx.send(UiEvent::Log(format!("Protocol decrypt error: {e}"))).await; - tracing::warn!("Inbound protocol error (session {}): {}", session_index, e); - return; - } - }; - - let mut actions_queue = std::collections::VecDeque::new(); - actions_queue.push_back(initial_action); - - while let Some(current_action) = actions_queue.pop_front() { - match current_action { - ProtocolAction::Multiple(nested) => { - for a in nested { - actions_queue.push_back(a); - } - } - ProtocolAction::DeliverApp(stream_id, dec_payload) => { - match RelayMessage::decode(&dec_payload) { - Ok(relay_msg) => { - match relay_msg { - RelayMessage::ConnectOk => { - let _ = tx.send(UiEvent::Log(format!("Relay CONNECT OK stream_id={stream_id}"))).await; - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::ConnectOk)); - } - RelayMessage::Data(data) => { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Data(Bytes::from(data)))); - } - RelayMessage::Close => { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Close)); - } - RelayMessage::Error(msg) => { - let _ = tx.send(UiEvent::Log(format!("Relay error for stream {stream_id}: {msg}"))).await; - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Error(msg))); - } - RelayMessage::Pong(ts) => { - let now = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64; - self.last_rtt_ms = now.saturating_sub(ts) as f64; - self.metrics.rtt_ms.store(self.last_rtt_ms as u32, Ordering::Relaxed); - } - RelayMessage::UdpAssociate => {} - RelayMessage::UdpData(target, data) => { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::UdpData(target, Bytes::from(data)))); - } - RelayMessage::KeepAlive | RelayMessage::Ping(_) | RelayMessage::Connect(_) => {} - } - } - Err(err) => { - let _ = tx.send(UiEvent::Log(format!("Relay decode error for stream {stream_id}: {err}"))).await; - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Error("relay decode failed".to_string()))); - } - } - } - ProtocolAction::SendDatagram(frame) => { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - _ => {} - } - } - } - } - } - None => { - let _ = tx.send(UiEvent::Log("UDP channel closed, resetting connection".to_string())).await; - self.running = false; - crate::sysproxy::disable_system_proxy(); - *sessions_opt = None; - *udp_rx_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "udp reader closed"); - let _ = tx.send(UiEvent::TunnelStopped).await; - } - } - } - - async fn handle_bridge_cmd( - &mut self, - cmd: Option, - sessions_opt: &mut Option>, - udp_rx_opt: &mut Option>, - proxy_guard: &mut Option, - stream_map: &mut std::collections::HashMap, - tx: &mpsc::Sender, - proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - ) -> bool { - match cmd { - Some(BridgeCommand::ToggleTunnel) => { - if self.running { - self.running = false; - self.metrics.connection_state.store(0, Ordering::Relaxed); - *proxy_guard = None; - *sessions_opt = None; - *udp_rx_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "manual stop"); - tx.send(UiEvent::TunnelStopped).await.ok(); - let stop_msg = if self.mode == "tun" { "TUN tunnel stopped" } else { "Bridge stopped" }; - tx.send(UiEvent::Log(stop_msg.to_string())).await.ok(); - } else { - tx.send(UiEvent::Log("Connecting to remote server...".to_string())).await.ok(); - tx.send(UiEvent::Metrics { status: ConnectionStatus::Handshaking, rtt_ms: 0.0, throughput_bps: 0 }).await.ok(); - self.metrics.connection_state.store(1, Ordering::Relaxed); - - let session_count = if self.mux_enabled { self.mux_sessions.max(1) } else { 1 }; - let (udp_tx, udp_rx) = mpsc::channel(100000); - let mut sessions = Vec::with_capacity(session_count); - let mut rtt_sum = 0.0; - let mut successful_sessions = 0; - - for idx in 0..session_count { - let session_id: u32 = rand::thread_rng().gen(); - match self.perform_handshake_with_id(&tx, session_id).await { - Ok((sock, mach, rtt)) => { - let session_index = sessions.len(); - let socket_clone = sock.clone(); - let udp_tx_clone = udp_tx.clone(); - - tokio::spawn(async move { - let mut buf = vec![0_u8; 65535]; - loop { - match socket_clone.recv(&mut buf).await { - Ok(n) => { - let inbound = Bytes::copy_from_slice(&buf[..n]); - if udp_tx_clone.send((session_index, inbound)).await.is_err() { - break; - } - } - Err(e) => { - tracing::warn!("UDP socket recv error (session {}): {}", session_index, e); - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - } - } - } - }); - - sessions.push(SessionState { socket: sock, machine: mach }); - rtt_sum += rtt; - successful_sessions += 1; - } - Err(err) => { - tx.send(UiEvent::Log(format!("Multiplex session {}/{} handshake failed: {}. Continuing with remaining sessions...", idx + 1, session_count, err))).await.ok(); - } - } - } - - if sessions.is_empty() { - *proxy_guard = None; - tx.send(UiEvent::Log("All multiplexed handshake attempts failed. Connection aborted.".to_string())).await.ok(); - tx.send(UiEvent::TunnelStopped).await.ok(); - self.metrics.connection_state.store(0, Ordering::Relaxed); - return True; - } - - *udp_rx_opt = Some(udp_rx); - *sessions_opt = Some(sessions); - self.last_rtt_ms = rtt_sum / successful_sessions as f64; - self.running = true; - self.last_sample_at = Instant::now(); - self.last_valid_recv = Instant::now(); - - let sys_proxy_addr = self.proxy_addr.replace("0.0.0.0:", "127.0.0.1:"); - *proxy_guard = Some(crate::sysproxy::SystemProxyGuard::enable(&sys_proxy_addr)); - - tx.send(UiEvent::Metrics { - status: ConnectionStatus::Established, - rtt_ms: self.last_rtt_ms, - throughput_bps: 0, - }).await.ok(); - self.metrics.connection_state.store(2, Ordering::Relaxed); - let start_msg = if self.mode == "tun" { "TUN tunnel established" } else { "Connection established" }; - tx.send(UiEvent::Log(start_msg.to_string())).await.ok(); - - for session in sessions_opt.as_mut().unwrap().iter_mut() { - let ts = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64; - let ping_payload = Bytes::from(RelayMessage::Ping(ts).encode()); - if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ping_payload)) { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp").await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - } - } - } - Some(BridgeCommand::NextProfile) => { - self.profile = next_profile(self.profile); - tx.send(UiEvent::ProfileChanged(self.profile)).await.ok(); - tx.send(UiEvent::Log(format!("Obfuscation profile switched to {:?}", self.profile))).await.ok(); - } - Some(BridgeCommand::NetworkChanged) => { - if self.running { - let _ = tx.send(UiEvent::Log("Network changed — starting immediate reconnect".to_string())).await; - self.metrics.connection_state.store(1, Ordering::Relaxed); - self.last_valid_recv = Instant::now() - Duration::from_secs(100); - - let session_count = if self.mux_enabled { self.mux_sessions.max(1) } else { 1 }; - let (udp_tx, udp_rx) = mpsc::channel(100000); - let mut new_sessions = Vec::with_capacity(session_count); - let mut successful_sessions = 0; - let mut rtt_sum = 0.0; - - for idx in 0..session_count { - let session_id: u32 = rand::thread_rng().gen(); - match self.perform_handshake_with_id(&tx, session_id).await { - Ok((sock, mach, rtt)) => { - let session_index = new_sessions.len(); - let socket_clone = sock.clone(); - let udp_tx_clone = udp_tx.clone(); - - tokio::spawn(async move { - let mut buf = vec![0_u8; 65535]; - loop { - match socket_clone.recv(&mut buf).await { - Ok(n) => { - let inbound = Bytes::copy_from_slice(&buf[..n]); - if udp_tx_clone.send((session_index, inbound)).await.is_err() { break; } - } - Err(e) => { - tracing::warn!("UDP recv error (network-change session {}): {}", session_index, e); - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - } - } - } - }); - new_sessions.push(SessionState { socket: sock, machine: mach }); - rtt_sum += rtt; - successful_sessions += 1; - } - Err(err) => { - let _ = tx.send(UiEvent::Log(format!("NetworkChanged reconnect session {}/{} failed: {}", idx + 1, session_count, err))).await; - } - } - } - - if !new_sessions.is_empty() { - *sessions_opt = Some(new_sessions); - *udp_rx_opt = Some(udp_rx); - self.last_rtt_ms = rtt_sum / successful_sessions as f64; - self.last_valid_recv = Instant::now(); - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "network changed"); - self.metrics.connection_state.store(2, Ordering::Relaxed); - let _ = tx.send(UiEvent::Log("NetworkChanged reconnect successful!".to_string())).await; - } else { - let _ = tx.send(UiEvent::Log("NetworkChanged reconnect failed — will retry on keepalive tick".to_string())).await; - } - } - } - Some(BridgeCommand::ReloadConfig) => { - match ClientConfig::reload_from_json_near_binary() { - Ok(cfg) => { - self.apply_runtime_config(&cfg); - tx.send(UiEvent::Log("Runtime config reloaded".to_string())).await.ok(); - if self.running { - self.running = false; - self.metrics.connection_state.store(0, Ordering::Relaxed); - *proxy_guard = None; - *sessions_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "config reload"); - let _ = tx.send(UiEvent::TunnelStopped).await; - } - } - Err(err) => { - let _ = tx.send(UiEvent::Log(format!("Config reload failed: {err}"))).await; - } - } - } - Some(BridgeCommand::Shutdown) | None => { - self.running = false; - *proxy_guard = None; - return False; - } - } - True - } - - async fn handle_keepalive( - &mut self, - sessions_opt: &mut Option>, - udp_rx_opt: &mut Option>, - proxy_guard: &mut Option, - stream_map: &mut std::collections::HashMap, - tx: &mpsc::Sender, - proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - proxy_rx: &mut mpsc::Receiver, - ) { - if self.last_valid_recv.elapsed().as_secs() > 25 { - let elapsed = self.last_valid_recv.elapsed().as_secs(); - if elapsed > 180 { - let _ = tx.send(UiEvent::Log("Connection permanently lost (3-minute hard timeout). Stopping tunnel.".into())).await; - self.running = false; - *proxy_guard = None; - *sessions_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "keepalive hard timeout"); - let _ = tx.send(UiEvent::TunnelStopped).await; - self.metrics.connection_state.store(0, Ordering::Relaxed); - return; - } - - let _ = tx.send(UiEvent::Log(format!("Connection stall detected ({}s silence). Attempting background reconnect...", elapsed))).await; - self.metrics.connection_state.store(1, Ordering::Relaxed); - - let session_count = if self.mux_enabled { self.mux_sessions.max(1) } else { 1 }; - let (udp_tx, udp_rx) = mpsc::channel(100000); - let mut new_sessions = Vec::with_capacity(session_count); - let mut successful_sessions = 0; - let mut rtt_sum = 0.0; - - for idx in 0..session_count { - let session_id: u32 = rand::thread_rng().gen(); - match self.perform_handshake_with_id(&tx, session_id).await { - Ok((sock, mach, rtt)) => { - let session_index = new_sessions.len(); - let socket_clone = sock.clone(); - let udp_tx_clone = udp_tx.clone(); - - tokio::spawn(async move { - let mut buf = vec![0_u8; 65535]; - loop { - match socket_clone.recv(&mut buf).await { - Ok(n) => { - let inbound = Bytes::copy_from_slice(&buf[..n]); - if udp_tx_clone.send((session_index, inbound)).await.is_err() { - break; - } - } - Err(e) => { - tracing::warn!("UDP socket recv error (reconnect session {}): {}", session_index, e); - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - } - } - } - }); - - new_sessions.push(SessionState { socket: sock, machine: mach }); - rtt_sum += rtt; - successful_sessions += 1; - } - Err(err) => { - let _ = tx.send(UiEvent::Log(format!("Background reconnect session {}/{} failed: {}", idx + 1, session_count, err))).await; - } - } - } - - if !new_sessions.is_empty() { - *sessions_opt = Some(new_sessions); - *udp_rx_opt = Some(udp_rx); - self.last_rtt_ms = rtt_sum / successful_sessions as f64; - self.last_valid_recv = Instant::now(); - self.metrics.connection_state.store(2, Ordering::Relaxed); - let _ = tx.send(UiEvent::Log("Background reconnect successful! Connection restored.".into())).await; - - for session in sessions_opt.as_mut().unwrap().iter_mut() { - let ts = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64; - let ping_payload = Bytes::from(RelayMessage::Ping(ts).encode()); - if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ping_payload)) { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp").await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - } - - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "background reconnect"); - - let mut flushed = 0; - while let Ok(stale) = proxy_rx.try_recv() { - if let ProxyEvent::NewStream { stream_id, .. } = stale { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Error("connection reset".into()))); - } - flushed += 1; - } - if flushed > 0 { - let _ = tx.send(UiEvent::Log(format!("Flushed {} stale proxy messages to prevent UDP burst", flushed))).await; - } - } else { - let _ = tx.send(UiEvent::Log("Background reconnect failed. Will retry on next tick...".into())).await; - } - } - - if let Some(sessions) = sessions_opt.as_mut() { - for session in sessions.iter_mut() { - let ts = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64; - let ping_payload = Bytes::from(RelayMessage::Ping(ts).encode()); - if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ping_payload)) { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - - let ka_payload = Bytes::from(RelayMessage::KeepAlive.encode()); - if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ka_payload)) { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - } - } - } - - async fn handle_retransmit( - &mut self, - sessions_opt: &mut Option>, - udp_rx_opt: &mut Option>, - proxy_guard: &mut Option, - stream_map: &mut std::collections::HashMap, - tx: &mpsc::Sender, - proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - ) { - let mut fatal_err = None; - if let Some(sessions) = sessions_opt.as_mut() { - for session in sessions.iter_mut() { - match session.machine.on_event(OstpEvent::Tick) { - Ok(action) => { - let mut queue = vec![action]; - while let Some(current_action) = queue.pop() { - match current_action { - ProtocolAction::Multiple(nested) => { - for a in nested { - queue.push(a); - } - } - ProtocolAction::SendDatagram(frame) => { - let _ = send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await; - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - } - _ => {} - } - } - } - Err(e) => { - fatal_err = Some(e); - break; - } - } - } - } - - if let Some(e) = fatal_err { - let _ = tx.send(UiEvent::Log(format!("Protocol tick fatal error: {e}"))).await; - self.running = false; - *proxy_guard = None; - *sessions_opt = None; - *udp_rx_opt = None; - stream_map.clear(); - self.reset_proxy_streams(&tx, &proxy_tx, "protocol fatal error"); - let _ = tx.send(UiEvent::TunnelStopped).await; - self.metrics.connection_state.store(0, Ordering::Relaxed); - } - } - - async fn handle_proxy_event( - &mut self, - proxy_ev: Option, - sessions_opt: &mut Option>, - stream_map: &mut std::collections::HashMap, - tx: &mpsc::Sender, - proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>, - ) { - if let Some(ev) = proxy_ev { - if let Some(sessions) = sessions_opt.as_mut() { - if sessions.is_empty() { - if let ProxyEvent::NewStream { stream_id, .. } = ev { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Error("tunnel stopped".into()))); - } - return; - } - let (stream_id, relay_msg, is_close) = match ev { - ProxyEvent::NewStream { stream_id, target } => { - let _ = tx.send(UiEvent::Log(format!("Proxy CONNECT stream_id={stream_id} target={target}"))).await; - (stream_id, RelayMessage::Connect(target), false) - } - ProxyEvent::UdpAssociate { stream_id } => { - let _ = tx.send(UiEvent::Log(format!("Proxy UDP ASSOCIATE stream_id={stream_id}"))).await; - (stream_id, RelayMessage::UdpAssociate, false) - } - ProxyEvent::UdpData { stream_id, target, payload } => { - (stream_id, RelayMessage::UdpData(target, payload.to_vec()), false) - } - ProxyEvent::Data { stream_id, payload } => (stream_id, RelayMessage::Data(payload.to_vec()), false), - ProxyEvent::Close { stream_id } => { - let _ = tx.send(UiEvent::Log(format!("Proxy CLOSE stream_id={stream_id}"))).await; - (stream_id, RelayMessage::Close, true) - } - }; - let len = sessions.len(); - let session_index = *stream_map.entry(stream_id).or_insert_with(|| { - rand::thread_rng().gen_range(0..len) - }); - if is_close { - stream_map.remove(&stream_id); - } - let session = &mut sessions[session_index]; - let out_payload = Bytes::from(relay_msg.encode()); - match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) { - Ok(ProtocolAction::SendDatagram(frame)) => { - if send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await.is_ok() { - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - tracing::trace!("Outbound datagram sent stream_id={stream_id} bytes={}", frame.len()); - } - } - Ok(ProtocolAction::Multiple(list)) => { - let mut sent = 0usize; - for item in list { - if let ProtocolAction::SendDatagram(frame) = item { - if send_datagram(&session.socket, &frame, self.transport_mode == "udp" ).await.is_ok() { - self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed); - sent += 1; - } - } - } - tracing::trace!("Outbound datagram batch stream_id={stream_id} sent={sent}"); - } - Ok(ProtocolAction::Noop) => { - tracing::trace!("Outbound datagram noop stream_id={stream_id}"); - } - Ok(_) => { - tracing::trace!("Outbound datagram unexpected action stream_id={stream_id}"); - } - Err(e) => { - tracing::warn!("Protocol error packing outbound stream_id={}: {}", stream_id, e); - let _ = tx.send(UiEvent::Log(format!("Protocol error packing TCP: {e}"))).await; - } - } - } else { - if let ProxyEvent::NewStream { stream_id, .. } = ev { - let _ = proxy_tx.send((stream_id, ProxyToClientMsg::Error("tunnel stopped".into()))); - } - } - } - } -""" - -with open("d:/ospab-projects/ostp/ostp-client/src/bridge.rs", "w", encoding="utf-8") as f: - f.write(prefix + new_run_and_helpers + suffix) - -print("Done")