docs: add Mermaid diagrams throughout docs

- plan.md: architecture flow diagram
- README.md: simple forwarder overview diagram
- story.md: DNS censorship and SOCKS5 proxy diagrams
- intra-comparison.md: all ASCII diagrams converted to Mermaid
This commit is contained in:
2026-07-06 23:09:06 +03:30
parent 861559ccca
commit d28f861075
4 changed files with 169 additions and 93 deletions

View File

@@ -14,48 +14,56 @@ encrypted and returns the correct IP, the **actual HTTPS connection** to
the target server can be blocked by Deep Packet Inspection (DPI) at the
TCP/TLS level.
```
doh-forwarder flow:
Browser → "youtube.com?" → doh-forwarder → DoH → returns IP ✅
Browser → HTTPS to YouTube IP:443 ──────────── BLOCKED by DPI ❌
```mermaid
sequenceDiagram
participant Browser
participant DNS as doh-forwarder
participant DPI as ISP Firewall
participant YT as YouTube
Intra flow:
Browser → HTTPS to YouTube IP:443 → TUN → Intra split/retry → YouTube
Note over Browser,YT: doh-forwarder flow (DNS only)
Browser->>DNS: youtube.com?
DNS-->>Browser: 142.250.80.46 ✅
Browser->>DPI: HTTPS to 142.250.80.46:443
DPI-->>Browser: ❌ BLOCKED (SNI inspection)
Note over Browser,YT: Intra flow (full traffic)
Browser->>DPI: HTTPS to 142.250.80.46:443 (via TUN)
DPI->>DPI: Split/retry Client Hello
DPI-->>YT: ✅ PASSED
YT-->>Browser: Response
```
## Architectural Differences
### doh-forwarder — DNS-only forwarder
```
+---------------------+
| DNS Client |
| (browser, app, OS) |
+--------+------------+
|
+------------+-------------+
| |
HTTPS (POST/GET) UDP/TCP (port 53)
| |
v v
+-------------------+ +-------------------+
| Axum HTTP Server | | DnsListener |
| (port 8800) | | (UDP + TCP) |
+--------+----------+ +--------+----------+
| |
+-----------+-------------+
|
v
+-------------------+
| DohClient |
| (reqwest HTTP) |
+--------+----------+
|
+-----------+-----------+
| | |
v v v
Cloudflare Google Quad9
(DoH) (DoH) (DoH)
```mermaid
flowchart TD
subgraph clients["DNS Clients"]
browser["Browser / App"]
os["OS DNS Client"]
end
subgraph forwarder["doh-forwarder"]
doh["Axum HTTP Server\n(port 8800)"]
dns["DnsListener\n(UDP + TCP)"]
client["DohClient\n(reqwest HTTP)"]
end
subgraph upstreams["Upstream DoH Resolvers"]
cf["Cloudflare"]
google["Google"]
quad9["Quad9"]
end
browser -- "HTTPS (POST/GET)" --> doh
os -- "UDP/TCP :53" --> dns
doh --> client
dns --> client
client -- "HTTPS POST" --> cf
client -- "HTTPS POST" --> google
client -- "HTTPS POST" --> quad9
```
- Accepts DNS queries on port 53 (UDP/TCP) and port 8800 (DoH)
@@ -65,30 +73,21 @@ Intra flow:
### Intra — Full traffic interception + circumvention
```
+-----------------------------------------------------+
| TUN Device |
| (captures ALL device traffic) |
+---------------------------+--------------------------+
|
+---------------+----------------+
| |
DNS packets (port 53) TCP packets (port 443)
| |
v v
+------------------+ +------------------------+
| DNS Interception | | intraStreamDialer |
| → DoH query via | | → DialWithSplitRetry() |
| HTTP POST | | → TLS hello splitting |
+------------------+ +------------------------+
|
+--------+--------+
| |
Success? Blocked?
| |
v v
Normal TLS Close + retry with
handshake split Client Hello
```mermaid
flowchart TD
subgraph tun["TUN Device (captures ALL traffic)"]
direction TB
dns["DNS Interception\n→ DoH query via HTTP POST"]
tcp["intraStreamDialer\n→ DialWithSplitRetry()\n→ TLS hello splitting"]
end
dns_packets["DNS packets\n(port 53)"] --> dns
tcp_packets["TCP packets\n(port 443)"] --> tcp
tcp --> success{"Success?"}
success -- "Yes" --> normal["Normal TLS handshake"]
success -- "No (blocked)" --> retry["Close + retry with\nsplit Client Hello"]
retry --> normal
```
- Creates a **TUN device** that captures all device traffic
@@ -134,17 +133,23 @@ connection if the domain is on a blocklist.
Intra's `splitHello()` function splits the TLS Client Hello into **two
TCP segments** at a random offset (664 bytes):
```
Normal Client Hello (one TCP segment):
[TLS Header (5 bytes)] [SNI: www.youtube.com ... rest of hello]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DPI reads this → finds "youtube.com" → BLOCKS
```mermaid
sequenceDiagram
participant Client
participant DPI as DPI Firewall
participant Server
Split Client Hello (two TCP segments):
Segment 1: [TLS Header (5 bytes)] [first N bytes...]
Segment 2: [remaining bytes... SNI: www.youtube.com ...]
Note over Client,Server: Normal (BLOCKED)
Client->>DPI: [TLS Header] [SNI: youtube.com ... full hello]
DPI->>DPI: Read SNI → youtube.com
DPI-->>Client: ❌ BLOCKED
DPI can't reassemble fast enough → PASSES THROUGH
Note over Client,Server: Split (PASSES THROUGH)
Client->>DPI: Segment 1: [TLS Header] [first N bytes...]
Client->>DPI: Segment 2: [remaining... SNI: youtube.com ...]
DPI->>DPI: Can't reassemble fast enough
DPI-->>Server: ✅ PASSED
Server-->>Client: Response
```
For TLS Client Hello specifically, Intra also **properly fragments the
@@ -186,14 +191,24 @@ into or control over:
The traffic flow is:
```
1. Browser asks OS: "What's the IP for youtube.com?"
2. OS sends DNS query to doh-forwarder (port 53)
3. doh-forwarder forwards via DoH → gets correct IP (e.g., 142.250.80.46)
4. Browser connects directly to 142.250.80.46:443
5. Browser sends TLS Client Hello with SNI=www.youtube.com
6. ISP's DPI reads SNI → blocks connection
7. Browser shows "connection timed out" or "connection reset"
```mermaid
sequenceDiagram
participant Browser
participant OS as OS DNS
participant DF as doh-forwarder
participant DPI as ISP DPI
participant YT as YouTube (142.250.80.46)
Browser->>OS: youtube.com?
OS->>DF: DNS query (port 53)
DF-->>OS: 142.250.80.46 ✅
OS-->>Browser: 142.250.80.46
Note over Browser,YT: Steps 47 are outside doh-forwarder's scope
Browser->>DPI: TCP SYN to 142.250.80.46:443
Browser->>DPI: TLS ClientHello (SNI: youtube.com)
DPI->>DPI: Read SNI → block
DPI-->>Browser: ❌ Connection reset
```
Step 47 happen entirely outside doh-forwarder's scope.
@@ -205,8 +220,10 @@ DNS forwarder into a **traffic interception and circumvention proxy**:
### Option A: SOCKS5 Proxy with Split Transport (M6 path)
```
Browser → SOCKS5 proxy (doh-forwarder) → split TCP → target server
```mermaid
flowchart LR
browser["Browser"] -- "SOCKS5" --> proxy["doh-forwarder\n(SOCKS5 proxy)"]
proxy -- "split TCP" --> target["Target Server"]
```
- Configure browser to use SOCKS5 proxy
@@ -215,8 +232,11 @@ Browser → SOCKS5 proxy (doh-forwarder) → split TCP → target server
### Option B: Transparent Proxy with TUN (Intra-like)
```
All traffic → TUN device → doh-forwarder → DNS: DoH / TCP: split → target
```mermaid
flowchart LR
all["All Traffic"] -- "TUN" --> forwarder["doh-forwarder"]
forwarder -- "DNS: DoH" --> dns["DNS Resolver"]
forwarder -- "TCP: split" --> target["Target Server"]
```
- Zero browser configuration