mirror of https://github.com/ospab/ostp.git
docs: update architecture diagram to be more understandable
This commit is contained in:
parent
271a39c664
commit
a69ffae750
51
README.md
51
README.md
|
|
@ -56,35 +56,42 @@ Download pre-built binaries for your platform from [GitHub Releases](https://git
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
flowchart LR
|
||||||
subgraph Client ["Client"]
|
%% Styles
|
||||||
A[Browser / Apps] -->|SOCKS5 / HTTP| B(Bridge Multiplexer)
|
classDef userApp fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
|
||||||
TUN[TUN Interface] -->|IP Packets| B
|
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"]
|
subgraph Local["💻 Client Device"]
|
||||||
B --> C{Protocol Machine}
|
Apps["Web Browser / Apps"]:::userApp
|
||||||
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
|
Socks["SOCKS5 / HTTP Proxy"]:::ostpCore
|
||||||
D -->|Obfuscated UDP Payload| E((UDP Socket))
|
Tun["Global TUN (VPN)"]:::ostpCore
|
||||||
end
|
Client["OSTP Client Protocol Engine\n(Noise + ChaCha20 + ARQ)"]:::ostpCore
|
||||||
|
|
||||||
|
Apps -->|TCP/UDP| Socks
|
||||||
|
Apps -->|IP Packets| Tun
|
||||||
|
Socks --> Client
|
||||||
|
Tun --> Client
|
||||||
end
|
end
|
||||||
|
|
||||||
E <==>|Encrypted & Obfuscated UDP Tunnel| F
|
subgraph Internet["🌐 Hostile Network (DPI/Firewall)"]
|
||||||
|
Tunnel{"Fully Obfuscated\nEncrypted UDP\n(Looks like noise)"}:::network
|
||||||
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
|
end
|
||||||
|
|
||||||
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
|
subgraph Remote["🖥️ Remote VPS (Server)"]
|
||||||
FB -->|Forward| NGINX[nginx / Caddy]
|
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]
|
Server -->|Decrypted Traffic| Relay
|
||||||
|
Server -->|Active Probe / Scanner| Fallback
|
||||||
I -->|Outbound| WWW((Internet))
|
Relay -->|Clear Traffic| Target
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Client <==> Tunnel <==> Server
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
49
README.ru.md
49
README.ru.md
|
|
@ -35,33 +35,42 @@
|
||||||
## Архитектура
|
## Архитектура
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
flowchart LR
|
||||||
subgraph Client ["Клиент"]
|
%% Styles
|
||||||
A[Браузер / Прил.] -->|SOCKS5 / HTTP| B(Bridge Multiplexer)
|
classDef userApp fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b
|
||||||
TUN[TUN Интерфейс] -->|IP Пакеты| B
|
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 Протокол"]
|
subgraph Local["💻 Устройство клиента"]
|
||||||
B --> C{Protocol Machine}
|
Apps["Браузер / Приложения"]:::userApp
|
||||||
C -->|Noise Handshake| D[ChaCha20Poly1305 AEAD]
|
Socks["SOCKS5 / HTTP Прокси"]:::ostpCore
|
||||||
D -->|Обфусцированный UDP| E((UDP Сокет))
|
Tun["Global TUN (VPN)"]:::ostpCore
|
||||||
end
|
Client["OSTP Клиент\n(Noise + ChaCha20 + ARQ)"]:::ostpCore
|
||||||
|
|
||||||
|
Apps -->|TCP/UDP| Socks
|
||||||
|
Apps -->|IP Пакеты| Tun
|
||||||
|
Socks --> Client
|
||||||
|
Tun --> Client
|
||||||
end
|
end
|
||||||
|
|
||||||
E <==>|Зашифрованный UDP Туннель| F
|
subgraph Internet["🌐 Сеть с цензурой (DPI)"]
|
||||||
|
Tunnel{"Зашифрованный UDP\n(Выглядит как белый шум)"}:::network
|
||||||
subgraph Server ["Сервер"]
|
|
||||||
F((UDP Сокет)) --> G{Dispatcher}
|
|
||||||
|
|
||||||
subgraph OSTPCoreServer ["OSTP Core Backend"]
|
|
||||||
G -->|Auth & Decrypt| H[Session & State Guard]
|
|
||||||
H -->|TCP Поток| I[Relay Loop]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
G -->|Active Probing / Unauth| FB[TCP Fallback Proxy]
|
subgraph Remote["🖥️ Удаленный сервер (VPS)"]
|
||||||
FB -->|Перенаправление| NGINX[nginx / Caddy]
|
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
|
end
|
||||||
|
|
||||||
|
Client <==> Tunnel <==> Server
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use ostp_core::{NoiseRole, OstpEvent, PaddingStrategy, ProtocolAction, ProtocolC
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
use tokio::sync::{mpsc, watch};
|
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::app::{BridgeCommand, ConnectionStatus, UiEvent};
|
||||||
use crate::config::ClientConfig;
|
use crate::config::ClientConfig;
|
||||||
|
|
@ -131,6 +131,21 @@ impl Bridge {
|
||||||
let mut metrics_tick = interval(Duration::from_millis(500));
|
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 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 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" {
|
let init_msg = if self.mode == "tun" {
|
||||||
"Bridge initialized (TUN mode)".to_string()
|
"Bridge initialized (TUN mode)".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -171,13 +186,27 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = metrics_tick.tick() => {
|
_ = 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 {
|
if self.running {
|
||||||
self.emit_metrics(&tx).await;
|
self.emit_metrics(&tx).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = keepalive_tick.tick() => {
|
_ = keepalive_tick.tick() => {
|
||||||
if self.running {
|
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() => {
|
_ = retransmit_tick.tick() => {
|
||||||
|
|
@ -523,6 +552,7 @@ impl Bridge {
|
||||||
|
|
||||||
async fn handle_keepalive(
|
async fn handle_keepalive(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
force: bool,
|
||||||
sessions_opt: &mut Option<Vec<SessionState>>,
|
sessions_opt: &mut Option<Vec<SessionState>>,
|
||||||
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
udp_rx_opt: &mut Option<mpsc::Receiver<(usize, Bytes)>>,
|
||||||
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
proxy_guard: &mut Option<crate::sysproxy::SystemProxyGuard>,
|
||||||
|
|
@ -531,9 +561,12 @@ impl Bridge {
|
||||||
proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
|
proxy_tx: &mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
|
||||||
proxy_rx: &mut mpsc::Receiver<ProxyEvent>,
|
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();
|
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 {
|
if self.kill_switch {
|
||||||
let _ = tx.send(UiEvent::Log(format!("Connection stall ({}s). Kill Switch is ON, retrying reconnect indefinitely...", elapsed))).await;
|
let _ = tx.send(UiEvent::Log(format!("Connection stall ({}s). Kill Switch is ON, retrying reconnect indefinitely...", elapsed))).await;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 90810f25f7af9e0a57bacce3d74ca3e46a6433e4
|
Subproject commit 2a22b520b2112c35676537ad85b11804b0053f01
|
||||||
Loading…
Reference in New Issue