mirror of https://github.com/ospab/ostp.git
fix: ostp-gui security and stability improvements
- Add IPC encryption using ChaCha20Poly1305 - Reduce helper connection timeout from 60s to 15s - Replace unwrap() with proper error handling in helper connection - Encrypt all messages between GUI and helper with derived key - Add ipc_crypto module for secure communication - Properly decode/encode encrypted messages in IPC loop
This commit is contained in:
parent
b5e830a5eb
commit
d91d5de440
|
|
@ -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<Vec<u8>, 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<u8>` для пакетов
|
|
||||||
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<u8> в горячих путях
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🟠 ВЫСОКИЕ (Неделя 2-3)
|
|
||||||
```
|
|
||||||
5. Добавить backpressure механизм
|
|
||||||
6. RwLock → Arc<Mutex> в 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**.
|
|
||||||
|
|
@ -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 месяц
|
|
||||||
- Пока только для личного использования
|
|
||||||
|
|
||||||
|
|
@ -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<Config> { ... }
|
|
||||||
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<T, E>` вместо `Option<T>` для ошибок
|
|
||||||
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 - смотри файлы по пути, указанному в каждой проблеме.
|
|
||||||
|
|
||||||
|
|
@ -29,4 +29,7 @@ ostp-client = { path = "../../ostp-client" }
|
||||||
portable-atomic = "1"
|
portable-atomic = "1"
|
||||||
json_comments = "0.2"
|
json_comments = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
chacha20poly1305 = "0.10"
|
||||||
|
sha2 = "0.10"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Vec<u8>> {
|
||||||
|
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<Vec<u8>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ use ostp_client::bridge::BridgeMetrics;
|
||||||
use portable_atomic::Ordering;
|
use portable_atomic::Ordering;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
|
|
||||||
|
mod ipc_crypto;
|
||||||
|
|
||||||
// ── Config types ─────────────────────────────────────────────────────────────
|
// ── Config types ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
|
@ -532,26 +534,32 @@ async fn start_tun_via_helper(
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let port = {
|
let port = {
|
||||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").map_err(|e| format!("Bind error: {}", e))?;
|
let listener = std::net::TcpListener::bind("127.0.0.1:0")
|
||||||
listener.local_addr().unwrap().port()
|
.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::<u64>().to_string();
|
let auth_token = rand::random::<u64>().to_string();
|
||||||
let helper_exe = find_helper_exe().ok_or_else(|| "ostp-tun-helper.exe not found.".to_string())?;
|
let helper_exe = find_helper_exe()
|
||||||
launch_as_admin(&helper_exe, &auth_token, port).map_err(|e| format!("Failed to launch helper: {}", e))?;
|
.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;
|
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 {
|
loop {
|
||||||
match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)).await {
|
match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)).await {
|
||||||
Ok(s) => return Ok::<_, std::io::Error>(s),
|
Ok(s) => return Ok::<_, std::io::Error>(s),
|
||||||
Err(_) => tokio::time::sleep(std::time::Duration::from_millis(200)).await,
|
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())?;
|
.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 mapped = parsed_config.clone();
|
||||||
let start_cmd = serde_json::json!({
|
let start_cmd = serde_json::json!({
|
||||||
"cmd": "start",
|
"cmd": "start",
|
||||||
|
|
@ -559,15 +567,26 @@ async fn start_tun_via_helper(
|
||||||
"token": auth_token
|
"token": auth_token
|
||||||
}).to_string();
|
}).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::<String>(16);
|
let (cmd_tx, mut cmd_rx) = tokio::sync::mpsc::channel::<String>(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 state_for_task = pipe_state.clone();
|
||||||
|
let crypto_for_task = ipc_crypto::IpcCrypto::new(&key);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, split};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, split};
|
||||||
let (reader_half, mut writer_half) = split(socket);
|
let (reader_half, mut writer_half) = split(socket);
|
||||||
let mut reader = BufReader::new(reader_half);
|
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();
|
let mut line = String::new();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -576,23 +595,41 @@ async fn start_tun_via_helper(
|
||||||
if result.unwrap_or(0) == 0 { break; }
|
if result.unwrap_or(0) == 0 { break; }
|
||||||
let trimmed = line.trim().to_string();
|
let trimmed = line.trim().to_string();
|
||||||
line.clear();
|
line.clear();
|
||||||
if let Ok(msg) = serde_json::from_str::<HelperMsg>(&trimmed) {
|
|
||||||
let mut s = state_for_task.lock().await;
|
if let Ok(encrypted_bytes) = hex::decode(&trimmed) {
|
||||||
match msg {
|
if let Ok(decrypted) = crypto_for_task.decrypt(&encrypted_bytes) {
|
||||||
HelperMsg::Status { value } => s.connection_state = value,
|
if let Ok(msg_str) = String::from_utf8(decrypted) {
|
||||||
HelperMsg::Metrics { bytes_sent, bytes_recv, rtt_ms } => { s.bytes_sent = bytes_sent; s.bytes_recv = bytes_recv; s.rtt_ms = rtt_ms; }
|
if let Ok(msg) = serde_json::from_str::<HelperMsg>(&msg_str) {
|
||||||
HelperMsg::Error { message } => {
|
let mut s = state_for_task.lock().await;
|
||||||
s.connection_state = 0;
|
match msg {
|
||||||
s.error_msg = Some(message.clone());
|
HelperMsg::Status { value } => s.connection_state = value,
|
||||||
eprintln!("Helper error: {}", message);
|
HelperMsg::Metrics { bytes_sent, bytes_recv, rtt_ms } => {
|
||||||
let _ = app.emit("tunnel-error", message);
|
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() => {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
658
refactor.py
658
refactor.py
|
|
@ -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<UiEvent>,
|
|
||||||
mut bridge_rx: mpsc::Receiver<BridgeCommand>,
|
|
||||||
mut shutdown: watch::Receiver<bool>,
|
|
||||||
mut proxy_rx: mpsc::Receiver<ProxyEvent>,
|
|
||||||
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<Vec<SessionState>> = None;
|
|
||||||
let mut udp_rx_opt: Option<mpsc::Receiver<(usize, Bytes)>> = None;
|
|
||||||
let mut proxy_guard: Option<crate::sysproxy::SystemProxyGuard> = None;
|
|
||||||
let mut stream_map: std::collections::HashMap<u16, usize> = 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<Vec<SessionState>>,
|
|
||||||
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
|
||||||
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
|
||||||
stream_map: &mut std::collections::HashMap<u16, usize>,
|
|
||||||
tx: &mpsc::Sender<UiEvent>,
|
|
||||||
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<BridgeCommand>,
|
|
||||||
sessions_opt: &mut Option<Vec<SessionState>>,
|
|
||||||
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
|
||||||
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
|
||||||
stream_map: &mut std::collections::HashMap<u16, usize>,
|
|
||||||
tx: &mpsc::Sender<UiEvent>,
|
|
||||||
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<Vec<SessionState>>,
|
|
||||||
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
|
||||||
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
|
||||||
stream_map: &mut std::collections::HashMap<u16, usize>,
|
|
||||||
tx: &mpsc::Sender<UiEvent>,
|
|
||||||
proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
|
|
||||||
proxy_rx: &mut mpsc::Receiver<ProxyEvent>,
|
|
||||||
) {
|
|
||||||
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<Vec<SessionState>>,
|
|
||||||
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
|
||||||
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
|
||||||
stream_map: &mut std::collections::HashMap<u16, usize>,
|
|
||||||
tx: &mpsc::Sender<UiEvent>,
|
|
||||||
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<ProxyEvent>,
|
|
||||||
sessions_opt: &mut Option<Vec<SessionState>>,
|
|
||||||
stream_map: &mut std::collections::HashMap<u16, usize>,
|
|
||||||
tx: &mpsc::Sender<UiEvent>,
|
|
||||||
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")
|
|
||||||
Loading…
Reference in New Issue