mirror of https://github.com/ospab/ostp.git
docs: update architecture diagram to be more understandable
This commit is contained in:
parent
4ac2e79e14
commit
90a919df59
59
README.md
59
README.md
|
|
@ -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
|
||||
|
||||
subgraph OSTPCoreClient ["OSTP Core Protocol"]
|
||||
B --> C{Protocol Machine}
|
||||
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
|
||||
D -->|Obfuscated UDP Payload| E((UDP Socket))
|
||||
end
|
||||
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 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]
|
||||
end
|
||||
|
||||
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
|
||||
FB -->|Forward| NGINX[nginx / Caddy]
|
||||
|
||||
H -->|Stats & Traffic| API[Management API]
|
||||
|
||||
I -->|Outbound| WWW((Internet))
|
||||
subgraph Internet["🌐 Hostile Network (DPI/Firewall)"]
|
||||
Tunnel{"Fully Obfuscated\nEncrypted UDP\n(Looks like noise)"}:::network
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
Server -->|Decrypted Traffic| Relay
|
||||
Server -->|Active Probe / Scanner| Fallback
|
||||
Relay -->|Clear Traffic| Target
|
||||
end
|
||||
|
||||
Client <==> Tunnel <==> Server
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
57
README.ru.md
57
README.ru.md
|
|
@ -35,33 +35,42 @@
|
|||
## Архитектура
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Client ["Клиент"]
|
||||
A[Браузер / Прил.] -->|SOCKS5 / HTTP| B(Bridge Multiplexer)
|
||||
TUN[TUN Интерфейс] -->|IP Пакеты| B
|
||||
|
||||
subgraph OSTPCoreClient ["OSTP Core Протокол"]
|
||||
B --> C{Protocol Machine}
|
||||
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
|
||||
D -->|Обфусцированный UDP| E((UDP Сокет))
|
||||
end
|
||||
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 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]
|
||||
end
|
||||
|
||||
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
|
||||
FB -->|Перенаправление| NGINX[nginx / Caddy]
|
||||
|
||||
I -->|Outbound| WWW((Интернет))
|
||||
subgraph Internet["🌐 Сеть с цензурой (DPI)"]
|
||||
Tunnel{"Зашифрованный UDP\n(Выглядит как белый шум)"}:::network
|
||||
end
|
||||
|
||||
subgraph Remote["🖥️ Удаленный сервер (VPS)"]
|
||||
Server["OSTP Сервер\n(Аутентификация)"]:::ostpCore
|
||||
Relay["Мультиплексор соединений"]:::ostpCore
|
||||
Fallback["Фейковый сайт\n(Nginx/Caddy)"]:::fallback
|
||||
Target["Свободный интернет\n(YouTube, Google и т.д.)"]:::external
|
||||
|
||||
Server -->|Расшифрованный трафик| Relay
|
||||
Server -->|Сканеры цензоров| Fallback
|
||||
Relay -->|Чистый трафик| Target
|
||||
end
|
||||
|
||||
Client <==> Tunnel <==> Server
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue