diff --git a/.gitignore b/.gitignore index f906cb3..be6d8ef 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ test_route.ps1 config.json wintun.dll +# Server runtime cache (public IP autodetect) — must never be committed, +# it's regenerated locally and leaks whatever host it ran on last. +.ostp_public_ip + # Logs *.log diff --git a/.ostp_public_ip b/.ostp_public_ip deleted file mode 100644 index e56ea71..0000000 --- a/.ostp_public_ip +++ /dev/null @@ -1 +0,0 @@ -127.0.0.1 \ No newline at end of file diff --git a/ostp-wiki/README.md b/ostp-wiki/README.md deleted file mode 100644 index 39b8182..0000000 --- a/ostp-wiki/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# OSTP Wiki - -This repository contains the documentation and wiki pages for the Ospab Stealth Transport Protocol (OSTP). diff --git a/ostp-wiki/api_endpoints.md b/ostp-wiki/api_endpoints.md deleted file mode 100644 index cf4e41a..0000000 --- a/ostp-wiki/api_endpoints.md +++ /dev/null @@ -1,149 +0,0 @@ -# Справочник API управления OSTP - -Сервер OSTP предоставляет REST API для управления пользователями, просмотра статистики трафика и интерактивного редактирования конфигурации. - -По умолчанию API слушает на порту `9090` (хост настраивается в файле конфигурации). - ---- - -## Авторизация - -Все запросы к API (за исключением подписок) должны содержать заголовок `Authorization` с API-токеном (если токен включен в конфигурационном файле): - -```http -Authorization: Bearer <ваш_api_токен> -``` - -Или в упрощенном виде: -```http -Authorization: <ваш_api_токен> -``` - ---- - -## Формат ответов - -Все ответы API возвращаются в формате JSON следующей структуры: - -```json -{ - "ok": true, - "data": ..., - "error": null -} -``` - -В случае ошибки: -```json -{ - "ok": false, - "data": null, - "error": "Описание ошибки" -} -``` - ---- - -## Список эндпоинтов - -### 1. Статус сервера -Возвращает текущую версию, аптайм и количество пользователей. - -* **URL**: `/api/server/status` -* **Метод**: `GET` -* **Формат `data`**: - ```json - { - "version": "0.2.30", - "uptime_seconds": 12053, - "active_users": 2, - "total_users": 5 - } - ``` - -### 2. Получение текущего конфига -Запрашивает полное содержимое файла `config.json` с удалением комментариев для прямой модификации. - -* **URL**: `/api/server/config` -* **Метод**: `GET` -* **Формат `data`**: Полный JSON-конфиг сервера. - -### 3. Обновление конфига -Записывает новый JSON конфигурации сервера в файл `config.json` на диске. Это автоматически вызывает **hot-reload** ядра (применение ключей доступа и лимитов). - -* **URL**: `/api/server/config` -* **Метод**: `PUT` -* **Тело запроса**: JSON нового конфигурационного файла. -* **Формат `data`**: `true` в случае успешного сохранения. - -### 4. Список клиентов и их статистики -Возвращает список всех зарегистрированных ключей доступа с их текущей загрузкой, скачиванием, активными сессиями и статусом подключения. - -* **URL**: `/api/users` -* **Метод**: `GET` -* **Формат `data`**: - ```json - [ - { - "access_key": "ostp_key_sample1", - "bytes_up": 2405020, - "bytes_down": 491029402, - "connections": 2, - "limit_bytes": 10737418240, - "online": true, - "name": "Ноутбук" - } - ] - ``` - -### 5. Создание клиента -Генерирует новый ключ доступа (или регистрирует пользовательский). - -* **URL**: `/api/users` -* **Метод**: `POST` -* **Тело запроса**: - ```json - { - "access_key": "my_custom_key_optional", - "name": "Имя клиента", - "limit_bytes": 50000000000 - } - ``` -* **Формат `data`**: Строка созданного ключа доступа. - -### 6. Удаление клиента -Отзывает ключ доступа и сбрасывает все связанные активные сессии. - -* **URL**: `/api/users/:key` -* **Метод**: `DELETE` -* **Формат `data`**: `"User removed"` - -### 7. Обновление клиента -Редактирует имя или лимит трафика для клиента. - -* **URL**: `/api/users/:key` -* **Метод**: `PUT` -* **Тело запроса**: - ```json - { - "name": "Новое имя", - "limit_bytes": 100000000000 - } - ``` -* **Формат `data`**: `"User updated"` - -### 8. Сброс счетчиков трафика -Обнуляет показания загрузки и скачивания для определенного пользователя. - -* **URL**: `/api/users/{key}/reset` -* **Метод**: `POST` -* **Формат `data`**: `true` - -### 9. Ссылка подписки клиента -Возвращает ссылку подписки или конфигурационный файл для клиента. Авторизация по Bearer-токену **не требуется** (ключ авторизуется сам через URL). - -* **URL**: `/api/subscribe/:key` -* **Метод**: `GET` -* **Заголовки**: - - `Accept: text/plain` -> Возвращает текстовую ссылку `ostp://@:?...` - - `Accept: application/json` -> Возвращает полный клиентский JSON-конфиг. diff --git a/ostp-wiki/configuration_guide.md b/ostp-wiki/configuration_guide.md deleted file mode 100644 index e9d03dd..0000000 --- a/ostp-wiki/configuration_guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# Руководство по конфигурации OSTP (`config.json`) - -Файл `config.json` является основным конфигурационным файлом для сервера, клиента и реле. - -Ниже приведено подробное описание структуры для режима работы **Server**. - ---- - -## Полный пример конфигурации - -```json -{ - "mode": "server", - "log_level": "info", - "listen": "0.0.0.0:50000", - "access_keys": [ - "some_simple_key", - { - "access_key": "detailed_key_with_limit", - "name": "Рабочий Ноутбук", - "limit_bytes": 107374182400 - } - ], - "api": { - "enabled": true, - "bind": "127.0.0.1:9090", - "token": "7a3f8b2c4d9e0f1a2b3c4d5e6f7a8b9c" - }, - "fallback": { - "enabled": false, - "listen": "0.0.0.0:443", - "target": "127.0.0.1:8080" - }, - "reality": { - "enabled": false, - "dest": "www.microsoft.com:443", - "private_key": "...", - "pbk": "...", - "sid": "...", - "sni_list": ["www.microsoft.com"] - }, - "outbound": { - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - "default_action": "proxy", - "rules": [ - { - "domain_suffix": [".onion"], - "action": "proxy" - } - ] - }, - "debug": false -} -``` - ---- - -## Описание разделов конфигурации - -### 1. Основные параметры -- **`mode`** (строка): Режим работы. Возможные варианты: `"server"`, `"client"`, `"relay"`. -- **`log_level`** (строка): Уровень логирования. Варианты: `"debug"`, `"info"`, `"warn"`, `"error"`. -- **`listen`** (строка или массив строк): Порт и интерфейсы, на которых сервер слушает входящие UDP (и опционально TCP/UoT) соединения. Примеры: - - `"0.0.0.0:50000"` (все IPv4 интерфейсы) - - `["0.0.0.0:50000", "[::]:50000"]` (поддержка IPv4 и IPv6 одновременно) -- **`debug`** (логический): Включает подробное отладочное логирование протокола. - ---- - -### 2. Ключи доступа (`access_keys`) -Раздел содержит массив ключей доступа. Поддерживается два формата записи (для обратной совместимости): -1. **Простая строка**: Текст ключа доступа. Лимит трафика отсутствует. - ```json - "my_secure_key" - ``` -2. **Объект с метаданными**: - - `access_key` (строка, обязательно): Текст ключа для подключения. - - `name` (строка, опционально): Человекочитаемое описание клиента. - - `limit_bytes` (число, опционально): Лимит трафика в байтах (загрузка + скачивание). - -При достижении `limit_bytes` сессия клиента немедленно сбрасывается и подключение блокируется до обнуления счетчика или расширения лимита. - ---- - -### 3. REST API Управления (`api`) -Используется для интеграции с панелью управления `ostp-control`. -- **`enabled`** (логический): Включение встроенного веб-сервера API. -- **`bind`** (строка): Интерфейс и порт для прослушивания (например, `"127.0.0.1:9090"`). -- **`token`** (строка): Bearer-токен для авторизации администратора. Автоматически генерируется сервером при команде `ostp --init server`. - ---- - -### 4. Встроенный TCP Fallback прокси (`fallback`) -Позволяет маскировать порт под веб-сервер при сканировании активными DPI-зондами. -- **`enabled`** (логический): Включить проксирование TCP. -- **`listen`** (строка): Порт прослушивания TCP/TLS (например, `"0.0.0.0:443"`). -- **`target`** (строка): Локальный веб-сервер (например, `"127.0.0.1:8080"` на nginx/caddy), куда будут пересылаться все обычные запросы (не-OSTP трафик). - ---- - -### 5. Reality Маскировка (`reality`) -Реализует спецификацию XTLS-Reality для бесшовной маскировки трафика под легитимный TLS-сервер. -- **`enabled`** (логический): Включение маскировки. -- **`dest`** (строка): Целевой домен маскировки (например, `"www.microsoft.com:443"`). -- **`private_key`** (строка): Приватный ключ Reality сервера (X25519). -- **`pbk`** (строка): Публичный ключ Reality сервера. -- **`sid`** (строка, 8 байт hex): Идентификатор сессии. -- **`sni_list`** (массив строк): Разрешенные SNI заголовки от клиентов. - ---- - -### 6. Правила маршрутизации (`outbound`) -Позволяет пересылать часть исходящего трафика клиентов через прокси-сервер (например, SOCKS5/TOR). -- **`enabled`** (логический): Включить исходящую маршрутизацию. -- **`protocol`** (строка): Протокол прокси. На данный момент поддерживается `"socks5"`. -- **`address`** (строка): Хост прокси-сервера. -- **`port`** (число): Порт прокси-сервера. -- **`default_action`** (строка): Действие для трафика, не попавшего под правила. Варианты: `"direct"` (напрямую с сервера) или `"proxy"` (через прокси). -- **`rules`** (массив объектов): Список правил перенаправления: - - `domain_suffix` (массив строк): Фильтрация по суффиксу домена. - - `ip_cidr` (массив строк): Фильтрация по IP подсетям. - - `action` (строка): Действие при совпадении (`"direct"` или `"proxy"`). 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") diff --git a/server.json b/server.json deleted file mode 100644 index 511adc9..0000000 --- a/server.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - // OSTP Server Configuration - "mode": "server", - "log_level": "info", - - // The address and port the server listens on for incoming OSTP connections. - "listen": "0.0.0.0:50000", - - // List of valid keys. Clients must use one of these to connect. - "access_keys": [ - "a1d8795a93553c08b4e89b017a16ca52" - ], - - // Optional proxy for outbound traffic. - "outbound": { - "enabled": false, - "protocol": "socks5", - "address": "127.0.0.1", - "port": 9050, - // default_action: 'proxy' (all through proxy) or 'direct' (bypass proxy by default). - "default_action": "proxy", - "rules": [ - { - "domain_suffix": [".onion"], - "action": "proxy" - } - ] - }, - - // Web control panel & Management API - "api": { - "enabled": false, - "bind": "0.0.0.0:9090", - // Static API token for Relay servers (optional) - "token": "", - // Secret URL path to hide panel from scanners (e.g. "mySecret123") - "webpath": "", - // Login credentials for web panel (password stored as SHA256 hash) - "username": "", - "password_hash": "" - }, - - // Fallback TCP proxy: unrecognized connections are proxied to a web server (anti-DPI). - "fallback": { - "enabled": false, - "listen": "0.0.0.0:443", - // Target web server (e.g., local nginx or caddy) - "target": "127.0.0.1:8080" - }, - - // Reality (XTLS) / UoT Masquerade parameters - "reality": { - "enabled": false, - "dest": "www.microsoft.com:443", - "private_key": "6FVg53jUBTt-dJ52F1Zu1RBCcW1gr9K84WdynBb7i80", - "pbk": "c9QjERoaqFGoKBd-9ZpNzj51E8B93fcnEQT_cohEk2E", - "sid": "960223edfa174fc5", - "sni_list": ["www.microsoft.com"] - }, - "debug": false, - -} \ No newline at end of file diff --git a/test.json b/test.json deleted file mode 100644 index 46b134b..0000000 --- a/test.json +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test_addr.rs b/test_addr.rs deleted file mode 100644 index 6722f57..0000000 --- a/test_addr.rs +++ /dev/null @@ -1,3 +0,0 @@ -use std::net::SocketAddr; fn main() { println!(\ -:? -\, \[::1]:80\.parse::()); }