docs: update architecture diagram to be more understandable

This commit is contained in:
ospab 2026-07-12 00:34:10 +03:00
parent 271a39c664
commit a69ffae750
4 changed files with 104 additions and 55 deletions

View File

@ -56,35 +56,42 @@ Download pre-built binaries for your platform from [GitHub Releases](https://git
## Architecture
```mermaid
graph TD
subgraph Client ["Client"]
A[Browser / Apps] -->|SOCKS5 / HTTP| B(Bridge Multiplexer)
TUN[TUN Interface] -->|IP Packets| B
flowchart LR
%% Styles
classDef userApp fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
classDef ostpCore fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#2e7d32
classDef network fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100,stroke-dasharray: 5 5
classDef external fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#4a148c
classDef fallback fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#c62828
subgraph OSTPCoreClient ["OSTP Core Protocol"]
B --> C{Protocol Machine}
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
D -->|Obfuscated UDP Payload| E((UDP Socket))
end
subgraph Local["💻 Client Device"]
Apps["Web Browser / Apps"]:::userApp
Socks["SOCKS5 / HTTP Proxy"]:::ostpCore
Tun["Global TUN (VPN)"]:::ostpCore
Client["OSTP Client Protocol Engine\n(Noise + ChaCha20 + ARQ)"]:::ostpCore
Apps -->|TCP/UDP| Socks
Apps -->|IP Packets| Tun
Socks --> Client
Tun --> Client
end
E <==>|Encrypted & Obfuscated UDP Tunnel| F
subgraph Server ["Server"]
F((UDP Socket)) --> G{Dispatcher}
subgraph OSTPCoreServer ["OSTP Core Backend"]
G -->|Auth & Decrypt| H[Session & State Guard]
H -->|TCP Stream| I[Relay Loop]
subgraph Internet["🌐 Hostile Network (DPI/Firewall)"]
Tunnel{"Fully Obfuscated\nEncrypted UDP\n(Looks like noise)"}:::network
end
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
FB -->|Forward| NGINX[nginx / Caddy]
subgraph Remote["🖥️ Remote VPS (Server)"]
Server["OSTP Server Protocol Engine\n(Authentication & Decryption)"]:::ostpCore
Relay["Connection Multiplexer"]:::ostpCore
Fallback["Fake Website\n(Nginx/Caddy)"]:::fallback
Target["Open Internet\n(YouTube, Google, etc)"]:::external
H -->|Stats & Traffic| API[Management API]
I -->|Outbound| WWW((Internet))
Server -->|Decrypted Traffic| Relay
Server -->|Active Probe / Scanner| Fallback
Relay -->|Clear Traffic| Target
end
Client <==> Tunnel <==> Server
```
---

View File

@ -35,33 +35,42 @@
## Архитектура
```mermaid
graph TD
subgraph Client ["Клиент"]
A[Браузер / Прил.] -->|SOCKS5 / HTTP| B(Bridge Multiplexer)
TUN[TUN Интерфейс] -->|IP Пакеты| B
flowchart LR
%% Styles
classDef userApp fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
classDef ostpCore fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#2e7d32
classDef network fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100,stroke-dasharray: 5 5
classDef external fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#4a148c
classDef fallback fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#c62828
subgraph OSTPCoreClient ["OSTP Core Протокол"]
B --> C{Protocol Machine}
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
D -->|Обфусцированный UDP| E((UDP Сокет))
end
subgraph Local["💻 Устройство клиента"]
Apps["Браузер / Приложения"]:::userApp
Socks["SOCKS5 / HTTP Прокси"]:::ostpCore
Tun["Global TUN (VPN)"]:::ostpCore
Client["OSTP Клиент\n(Noise + ChaCha20 + ARQ)"]:::ostpCore
Apps -->|TCP/UDP| Socks
Apps -->|IP Пакеты| Tun
Socks --> Client
Tun --> Client
end
E <==>|Зашифрованный UDP Туннель| F
subgraph Server ["Сервер"]
F((UDP Сокет)) --> G{Dispatcher}
subgraph OSTPCoreServer ["OSTP Core Backend"]
G -->|Auth & Decrypt| H[Session & State Guard]
H -->|TCP Поток| I[Relay Loop]
subgraph Internet["🌐 Сеть с цензурой (DPI)"]
Tunnel{"Зашифрованный UDP\n(Выглядит как белый шум)"}:::network
end
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
FB -->|Перенаправление| NGINX[nginx / Caddy]
subgraph Remote["🖥️ Удаленный сервер (VPS)"]
Server["OSTP Сервер\n(Аутентификация)"]:::ostpCore
Relay["Мультиплексор соединений"]:::ostpCore
Fallback["Фейковый сайт\n(Nginx/Caddy)"]:::fallback
Target["Свободный интернет\n(YouTube, Google и т.д.)"]:::external
I -->|Outbound| WWW((Интернет))
Server -->|Расшифрованный трафик| Relay
Server -->|Сканеры цензоров| Fallback
Relay -->|Чистый трафик| Target
end
Client <==> Tunnel <==> Server
```
---

View File

@ -10,7 +10,7 @@ use ostp_core::{NoiseRole, OstpEvent, PaddingStrategy, ProtocolAction, ProtocolC
use rand::Rng;
use tokio::net::UdpSocket;
use tokio::sync::{mpsc, watch};
use tokio::time::{interval, timeout, Instant};
use tokio::time::{interval, timeout, Instant, MissedTickBehavior};
use crate::app::{BridgeCommand, ConnectionStatus, UiEvent};
use crate::config::ClientConfig;
@ -131,6 +131,21 @@ impl Bridge {
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));
// CRITICAL for suspend/resume: the default MissedTickBehavior is `Burst`,
// which after a laptop sleep or a phone backgrounding the app fires ALL
// the ticks that "should" have happened during the gap back-to-back. For
// the 10ms retransmit tick that is tens of thousands of instant ticks on
// resume — a CPU storm that hangs the bridge and manifests as the app
// freezing or getting stuck "Connecting". Skip missed ticks instead.
metrics_tick.set_missed_tick_behavior(MissedTickBehavior::Skip);
keepalive_tick.set_missed_tick_behavior(MissedTickBehavior::Skip);
retransmit_tick.set_missed_tick_behavior(MissedTickBehavior::Skip);
// Wall-clock anchor for suspend/resume detection. tokio's timers run on a
// monotonic clock; comparing it against wall-clock lets us notice that
// the machine slept (or the app was frozen in the background) and force
// one clean reconnect instead of trying to resume a long-dead session.
let mut last_wall_check = SystemTime::now();
let init_msg = if self.mode == "tun" {
"Bridge initialized (TUN mode)".to_string()
} else {
@ -171,13 +186,27 @@ impl Bridge {
}
}
_ = metrics_tick.tick() => {
// Suspend/resume detection: the wall clock jumps forward on
// wake even when the monotonic timer clock does not, so a
// large gap here means the machine slept / the app was frozen.
// The session is almost certainly dead (the server evicts
// idle sessions after 10 min), so force one clean reconnect
// rather than waiting on stale-session heuristics.
let wall_gap = last_wall_check.elapsed().unwrap_or_default();
last_wall_check = SystemTime::now();
if self.running && wall_gap > Duration::from_secs(15) {
let _ = tx.send(UiEvent::Log(format!(
"Resumed after ~{}s suspend — forcing clean reconnect", wall_gap.as_secs()
))).await;
self.handle_keepalive(true, &mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx, &mut proxy_rx).await;
}
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;
self.handle_keepalive(false, &mut sessions_opt, &mut udp_rx_opt, &mut proxy_guard, &mut stream_map, &tx, &proxy_tx, &mut proxy_rx).await;
}
}
_ = retransmit_tick.tick() => {
@ -523,6 +552,7 @@ impl Bridge {
async fn handle_keepalive(
&mut self,
force: bool,
sessions_opt: &mut Option<Vec<SessionState>>,
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
@ -531,9 +561,12 @@ impl Bridge {
proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
proxy_rx: &mut mpsc::Receiver<ProxyEvent>,
) {
if self.last_valid_recv.elapsed().as_secs() > 25 {
if force || self.last_valid_recv.elapsed().as_secs() > 25 {
let elapsed = self.last_valid_recv.elapsed().as_secs();
if elapsed > 180 {
// On a forced (post-resume) reconnect the monotonic clock may not
// have advanced, so `elapsed` can be small — never treat a forced
// reconnect as a hard timeout; we specifically want to re-establish.
if !force && elapsed > 180 {
if self.kill_switch {
let _ = tx.send(UiEvent::Log(format!("Connection stall ({}s). Kill Switch is ON, retrying reconnect indefinitely...", elapsed))).await;
} else {

@ -1 +1 @@
Subproject commit 90810f25f7af9e0a57bacce3d74ca3e46a6433e4
Subproject commit 2a22b520b2112c35676537ad85b11804b0053f01