content/writing/hitchhikers-guide-to-a-safe-and-free-internet.md (28742B)
1 ---
2 title: "The Hitchhiker's Guide to a Safe and Free Internet"
3 titleKo: "안전하고 자유로운 인터넷을 여행하는 히치하이커를 위한 안내서"
4 date: 2026-07-20
5 deck: ""
6 type:
7 tags: []
8 draft:
9 ---
10 > ℹ️ **About the example values**: **The server IP (`203.0.113.10`), the camouflage SNI domain (`www.example.jp`), the Reality public key, the shortId and the client UUID in this post are all documentation placeholders** (the IP comes from `203.0.113.0/24`, the range RFC 5737 reserves for documentation, and `example.jp` is a domain JPRS reserves for documentation). If you build this yourself, replace them with your own values. **Never publish your private keys (privateKey/secretKey).** **The certificate and SNI bytes in the PoC are illustrative values based on this example domain** (in practice, the genuine certificate of whichever camouflage domain you choose comes back unchanged). On the other hand, **the exit IP, the performance numbers and the traffic flows are real measurements taken from a live setup**, and the exit IP belongs to a Cloudflare WARP range shared by a huge number of users, so it identifies neither the server nor any individual.
11
12 ## 1. A journey toward using the internet freely
13
14 The motivation is simple: **I want to use the internet safely, freely and anonymously.** So I built a proxy system with VLESS + Xray + Reality + WARP, and this post is based on three months of running it.
15
16 Internet regulation in Korea gets tighter every year — blocking has already gone past the DNS level to **reading the destination name (SNI) in plaintext**, and its scope keeps widening. Nor is this only a Korean story. China's Great Firewall (GFW) and Russia's "sovereign internet" are much further down the same road. What plays out in every one of these places is an endless **battle between spear and shield** — someone gets around a restriction, a new restriction appears to stop that, and then a way around that one appears. There is a hint in that.
17
18 First, you need to know how modern censorship works. Censorship today is not a simple blacklist along the lines of "block this domain." ISP middleboxes **judge traffic by its "shape"**:
19
20 1. **Packet signatures** — they identify a protocol's unique fingerprint (WireGuard's handshake has a fixed structure, so it is caught in one shot).
21 2. **Plaintext SNI** — they read the destination name of a TLS connection as-is.
22 3. **Behavioral patterns** — they flag long-lived sessions to unknown IPs that don't look like browsing as anomalies.
23
24 The signs are already here. Run a self-hosted VPN in Korea for a few days and **only the persistent TLS sessions get picked out** and dropped (EOF), while WireGuard and OpenVPN are often blocked right at the handshake. It isn't a bandwidth problem; it's that the traffic's "shape" differs from an ordinary user's.
25
26 So the goal is clear.
27
28 > **"When a censor looks at my traffic, it must not differ by a single bit from an ordinary person viewing a well-known site over HTTPS."**
29
30 What creates this "indistinguishability" is **Reality**. And to erase **who is connecting from where** as well, the final exit is anonymized once more through **Cloudflare WARP**. All so that I can just use the internet, without being watched or controlled by anyone.
31
32 > 📎 **Reference** — [*How I Hid My VPN*](https://youtu.be/v7Pal3AQyVM)
33
34 ---
35
36 ## 2. Overall architecture
37
38 ```mermaid
39 flowchart LR
40 subgraph KR["🇰🇷 Korea · KT line (exposed to DPI)"]
41 C["Client<br/>sing-box · TUN<br/>uTLS = chrome"]
42 end
43 subgraph JP["🇯🇵 AWS Tokyo · ap-northeast-1"]
44 X["Xray server<br/>VLESS + Reality :443<br/>camouflage SNI = www.example.jp"]
45 end
46 subgraph WP["☁️ Cloudflare WARP"]
47 W["WARP edge · NRT<br/>AS13335<br/>exit IP 104.28.243.105"]
48 end
49 NET(("🌐 Internet<br/>ordinary destinations"))
50
51 C ==>|"① Reality TLS 1.3 handshake<br/>to DPI it looks like 'Chrome viewing example.jp'"| X
52 X ==>|"② WireGuard / WARP<br/>(default route)"| W ==> NET
53
54 classDef censored stroke:#e11d48,stroke-width:3px;
55 class C,X censored;
56 ```
57
58 **Two key design choices:**
59
60 1. **The censored leg (client ↔ Tokyo server)** is wrapped in Reality and disguised as *normal TLS 1.3 to a well-known Japanese site*.
61 2. WireGuard (WARP) is used **only on the uncensored leg (Tokyo server ↔ Cloudflare)**. → This takes the benefit of "laundering the exit IP" without exposing WireGuard's fingerprint problem to the censoring network.
62
63 > **Why put WARP at the end?** The IP the final destination sees becomes **Cloudflare (AS13335)** instead of **AWS (AS16509)**. AWS ranges are easily blocked as "data center = suspected bot/proxy," but WARP edge IPs are consumer ranges shared by huge numbers of real users, so their reputation is good. The point is that **not even my server's IP is exposed to the final destination.**
64
65 ---
66
67 ## 3. The weaknesses of a conventional WireGuard VPN
68
69 WireGuard is fast, small in code and cryptographically excellent, but **as a censorship circumvention tool it has fatal weaknesses**.
70
71 | Weakness | Description | What DPI does |
72 |---|---|---|
73 | **Fixed handshake fingerprint** | The Handshake Initiation is **exactly 148 bytes**, followed by a Responder message of **exactly 92 bytes**. The first 4 bytes of every packet carry the message type (`0x01` init, `0x02` response …) in the clear. There is no obfuscation at all. | A single rule, `148-byte → 92-byte UDP flow`, catches almost every WireGuard handshake |
74 | **UDP-based** | Many censoring and corporate networks throttle 443/UDP outright, or drop unidentified UDP that isn't QUIC. | Blocks "high-volume, long-lived UDP" itself without even knowing the protocol |
75 | **Vulnerable to active probing** | Anyone can throw a WG handshake at it, and the server either responds or stays silent → the response pattern identifies the server. | Fires probe packets at a suspicious IP:port → confirms a VPN server, then blacklists it |
76 | **No camouflage or cover traffic** | There is no notion of SNI camouflage, certificate camouflage or mimicking normal traffic at all. Even moved to port 443, the payload isn't TLS, so it gives itself away immediately. | "Port 443 but not TLS" → flagged as an anomaly |
77 | **Identified instantly once exposed** | The protocol's identity shows plainly in the traffic, so once an endpoint is singled out it stays blocked for good. | Listed in IP reputation databases |
78
79 As of 2026, commercial DPI appliances from Sandvine, Allot and Huawei **ship with WireGuard detection rules built in**, and the latest middleboxes identify WireGuard without decrypting it, from as few as 100 packets, using ML classification based on timing and entropy. The real-world blocking record is grim — **vanilla WireGuard succeeds about 12% of the time in Russia, suffers 98% packet loss in Iran, and is caught within seconds by China's GFW**, and more than ten other countries, including Turkey, Belarus and Myanmar, block or throttle it at the DPI level.
80
81 > This is why "obfuscated WireGuard" derivatives such as **AmneziaWG** have appeared. They pad vanilla WireGuard's fixed packet sizes with random junk and variable headers to scatter the signature, which is itself evidence that the industry has conceded *"WireGuard gets caught if you use it as-is."* The Reality setup in this post goes one step further: it looks not like **"something obfuscated" but like "plain, normal TLS itself."**
82
83 **In short**: WireGuard connects by shouting *"I am WireGuard!"* The censor only needs ears to hear it. Reality, by contrast, says *"I'm just someone browsing example.jp,"* and **presents the real example.jp TLS certificate as proof** (demonstrated in sections 4 and 5 below).
84
85 ---
86
87 ## 4. Components
88
89 ### 4.1 VLESS
90
91 VLESS is the successor to VMess, and it **strips out the built-in encryption and timestamp authentication.** Why? Because the outside will be wrapped in TLS 1.3 (Reality) anyway, so double encryption is a waste of CPU. The VLESS header is no more than a **thin 25–50-byte routing header** made up of roughly a version byte, a UUID, a command and the destination address (compared with 100+ bytes for OpenVPN). On top of that, the `xtls-rprx-vision` flow suppresses the telltale record-size pattern that TLS-in-TLS produces.
92
93 > 📎 **Reference** — An article that dissects VLESS byte by byte as a "minimal TLS 1.3 wrapper" and lays out DPI's three main detection techniques (protocol fingerprinting, statistical analysis, active probing): [habr.com/en/articles/990144](https://habr.com/en/articles/990144/) (sections 1 and 3).
94
95 ### 4.2 Xray-core — the server engine
96
97 What actually runs on the server is Xray.
98
99 ```console
100 $ xray version
101 Xray 26.3.27 (Xray, Penetrates Everything.) — go1.26.1 linux/amd64
102 $ ps -o etime,cmd -C xray
103 ELAPSED CMD
104 30-13:49:26 /usr/local/bin/xray run -config /usr/local/etc/xray/config.json # 30 days without interruption
105 ```
106
107 It has been running for 30 days without interruption as `nobody`.
108
109 ### 4.3 Reality — the heart of this setup
110
111 The fundamental problem with conventional TLS camouflage (TLS + a web server, e.g. Trojan) is that **"the certificate I present is not the real thing."** If a censor digs through the Certificate Transparency logs or verifies the certificate chain, the self-made domain gives itself away.
112
113 **Reality's change of approach:**
114
115 - My server **does not issue or present a certificate of its own.**
116 - Instead, it **"borrows" a real, well-known site (here, `www.example.jp`).**
117 - When a legitimate client (one holding the right x25519 key + shortId) connects → Xray operates in proxy mode.
118 - **When anyone without the key (= a censor's active probe) connects → Xray forwards that handshake untouched to the real `www.example.jp:443`**, and the client receives example.jp's genuine certificate.
119
120 Here is that "legitimate user ↔ censor probe" branch as a diagram:
121
122 ```mermaid
123 sequenceDiagram
124 autonumber
125 participant P as Client / censor probe
126 participant R as Xray Reality server :443
127 participant N as Real www.example.jp
128 alt Holds the right key (legitimate user)
129 P->>R: ClientHello + embedded x25519 · shortId · UUID
130 R->>R: Key verified → proxy mode
131 R-->>P: Disguised TLS session established → proxy tunnel open
132 else No key (censor's active probe)
133 P->>R: ClientHello (no valid key)
134 R->>N: Forwards the handshake untouched to the real example.jp
135 N-->>P: Real example.jp certificate (Verification OK)
136 Note over P: Verdict "just an example.jp server" → detection fails
137 end
138 ```
139
140 In other words, however hard the censor pokes at it, the only conclusion it can reach is **"this is just an example.jp server."**
141
142 > 📎 **Reference (how Reality works)** — the official implementation [XTLS/REALITY](https://github.com/XTLS/REALITY), the documentation on SNI fallback camouflage [Project X — Fallbacks with SNI](https://xtls.github.io/en/document/level-1/fallbacks-with-sni.html), and a source-level analysis [How REALITY works (ObjShadow)](https://objshadow.pages.dev/en/posts/how-reality-works/). For a discussion of defending against active probing, see [Xray-core Discussion #2623](https://github.com/XTLS/Xray-core/discussions/2623).
143
144 **Server inbound configuration:**
145
146 ```jsonc
147 {
148 "tag": "vless-reality-in",
149 "listen": "0.0.0.0", "port": 443,
150 "protocol": "vless",
151 "settings": {
152 "clients": [{ "id": "<client-UUID>", "flow": "xtls-rprx-vision" }],
153 "decryption": "none"
154 },
155 "streamSettings": {
156 "network": "tcp",
157 "security": "reality",
158 "realitySettings": {
159 "dest": "www.example.jp:443", // camouflage target (handshakes really are forwarded here)
160 "serverNames": ["www.example.jp"], // allowed SNI
161 "privateKey": "<xray-x25519-private-key>", // never publish
162 "shortIds": ["", "0123456789abcdef"]
163 }
164 },
165 "sniffing": { "enabled": true, "destOverride": ["http","tls","quic"], "routeOnly": true }
166 }
167 ```
168
169 > **How to choose the camouflage target (dest)** — Not just any site will do. It looks natural only if ① it supports TLS 1.3 + X25519 and ② it sits in **the same cloud/ASN/region** as the server. For a server on AWS Tokyo, picking **a large Japanese site hosted on AWS (→ whose certificate issuer shows up as `Amazon`)** makes the camouflage far more natural (same cloud and ASN as the server). This post writes the camouflage domain as the documentation example `www.example.jp`. In practice, when KT's DPI flagged the proxy IP, matching the SNI to **a host in the same region and ASN as the server** was decisive for stability.
170
171 ### 4.4 WARP outbound — laundering the exit
172
173 The server's default exit is not `direct` (which exposes the AWS IP) but **Cloudflare WARP (userspace WireGuard)**.
174
175 ```jsonc
176 {
177 "tag": "warp-out",
178 "protocol": "wireguard",
179 "settings": {
180 "secretKey": "<WARP-private-key>",
181 "address": ["172.16.0.2/32", "2606:4700:110:84e7:...../128"],
182 "peers": [{
183 "publicKey": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=",
184 "endpoint": "engage.cloudflareclient.com:2408",
185 "allowedIPs": ["0.0.0.0/0", "::/0"],
186 "keepAlive": 25
187 }],
188 "mtu": 1280
189 }
190 }
191 ```
192
193 The important point here: **this WireGuard is a userspace implementation inside Xray**, so it does not show up as a kernel interface (`wg show`), and it **flows only on the AWS ↔ Cloudflare leg (the uncensored overseas leg)**. That is, it takes only the benefit of "exit IP = Cloudflare" while never exposing WireGuard's fingerprint problem to Korea's censoring network.
194
195 ### 4.5 Routing — hardening
196
197 ```jsonc
198 "routing": {
199 "rules": [
200 { "ip": ["geoip:private"], "outboundTag": "block" }, // block SSRF / internal network scans
201 { "protocol": ["bittorrent"], "outboundTag": "block" }, // prevent abuse (protects IP reputation)
202 { "ip": ["203.0.113.10/32"], "port": "22", "outboundTag": "loopback-ssh" }, // for administration
203 { "ip": ["203.0.113.10/32"], "port": "8080", "outboundTag": "loopback-speedtest" },
204 { "network": "tcp,udp", "outboundTag": "warp-out" } // everything else → WARP
205 ]
206 }
207 ```
208
209 - **`geoip:private` → block**: shuts out SSRF-style scanning of internal networks through the proxy at the source.
210 - **bittorrent → block**: keeps torrent-driven abuse reports from killing the reputation of the WARP/AWS IPs.
211 - **loopback-ssh**: instead of opening a separate port, the server's own port 22 is reached from inside the proxy tunnel (so admin traffic stays disguised too).
212
213 ### 4.6 Client — sing-box (TUN + FakeIP)
214
215 The client runs sing-box in TUN mode, transparently swallowing **all of the system's traffic**.
216
217 ```jsonc
218 {
219 "outbounds": [{
220 "type": "vless",
221 "server": "203.0.113.10", "server_port": 443,
222 "uuid": "<client-UUID>",
223 "flow": "xtls-rprx-vision",
224 "tls": {
225 "enabled": true,
226 "server_name": "www.example.jp",
227 "reality": { "enabled": true,
228 "public_key": "<server-Reality-public-key>",
229 "short_id": "0123456789abcdef" },
230 "utls": { "enabled": true, "fingerprint": "chrome" } // ← disguise as Chrome
231 }
232 }],
233 "dns": { "servers": [
234 { "tag": "proxy-dns", "type": "https", "server": "1.1.1.1", "detour": "proxy" }, // DoH on the far side of the proxy
235 { "tag": "fakeip", "type": "fakeip", "inet4_range": "198.18.0.0/15" } // prevents DNS leaks
236 ]}
237 }
238 ```
239
240 Two things matter most here.
241
242 - **`utls: chrome`** — generates a TLS ClientHello identical to a real Chrome browser's fingerprint (proven with packets later on). uTLS is the [refraction-networking/utls](https://github.com/refraction-networking/utls) library, which imitates a specific browser's fingerprint in place of the telltale fingerprint of Go's standard TLS.
243 - **FakeIP + DoH-over-proxy** — domains are never resolved locally. A fake `198.18.x` IP is returned immediately and the real resolution happens on the far side of the proxy, so **DNS leakage is zero**.
244
245 ---
246
247 ## 5. PoC — proof at the packet level
248
249 **Real commands and real responses.** Each test checks whether the setup holds up against one of the censor's three approaches: ① tracing the exit IP, ② active probing, ③ passive packet observation.
250
251 ### PoC-1. Confirming the exit IP is laundered
252
253 ```console
254 $ curl -s https://api.ipify.org
255 104.28.243.105
256
257 $ curl -s https://www.cloudflare.com/cdn-cgi/trace | grep -E '^(ip|warp|colo|loc)='
258 ip=2a09:bac5:4305:dc::16:175
259 colo=NRT
260 loc=JP
261 warp=on
262
263 $ curl -s "http://ip-api.com/line/?fields=query,country,city,isp,as"
264 104.28.243.105
265 Japan
266 Tokyo
267 Cloudflare, Inc.
268 AS13335 Cloudflare, Inc.
269 ```
270
271 **Interpretation:**
272
273 | Viewpoint | Actual value | What the destination sees |
274 |---|---|---|
275 | My real location | Korea, KT | ❌ Not visible |
276 | Proxy server | `203.0.113.10` (AWS, **AS16509**, Tokyo) | ❌ Not visible |
277 | **Exit the destination sees** | — | ✅ `104.28.243.105` (**Cloudflare AS13335**, Tokyo) |
278
279 `warp=on` confirms the traffic goes through WARP. It passed through three hops (Korea → proxy server → WARP), yet the final destination sees only **a Cloudflare consumer range**. **The proxy server's IP is never exposed.**
280
281 ### PoC-2. Resisting active probing — "poke it all you like, it's just example.jp"
282
283 Suppose a censor connects directly to the suspicious IP `203.0.113.10:443`, using openssl with no credentials:
284
285 ```console
286 $ echo | openssl s_client -connect 203.0.113.10:443 -servername www.example.jp 2>/dev/null \
287 | openssl x509 -noout -subject -issuer -dates
288 subject=CN=www.example.jp
289 issuer=C=US, O=Amazon, CN=Amazon RSA 2048 M01
290 notBefore=Jan 19 00:00:00 2026 GMT
291 notAfter=Jan 14 23:59:59 2027 GMT
292
293 # For comparison: connecting directly to the real www.example.jp
294 $ echo | openssl s_client -connect www.example.jp:443 -servername www.example.jp 2>/dev/null \
295 | openssl x509 -noout -subject -issuer
296 subject=CN=www.example.jp
297 issuer=C=US, O=Amazon, CN=Amazon RSA 2048 M01
298
299 # TLS parameters
300 $ echo | openssl s_client -connect 203.0.113.10:443 -servername www.example.jp 2>/dev/null \
301 | grep -E 'Protocol|Cipher|Verification'
302 Verification: OK
303 New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
304 Protocol: TLSv1.3
305 ```
306
307 **Interpretation: identical in every respect.**
308
309 - The certificate the proxy server returned: `CN=www.example.jp`, issued by `Amazon RSA 2048 M01`.
310 - The certificate the real example.jp returns: **exactly the same.**
311 - `Verification: OK` — a self-made certificate would have failed chain verification, but this one passes because it is **the real certificate of the real example.jp**.
312
313 However much a censor actively probes this IP, the only conclusion it gets is **"this is an example.jp server."** Had it been a WireGuard server, a single probe would have given it away.
314
315 ### PoC-3. Passive observation — the bytes DPI actually reads on the wire
316
317 This time, suppose the censor simply eavesdrops on packets in the middle of the line. I capture the traffic leaving the client's physical NIC (`eth0`) for the server with `tshark`, and meanwhile fire curl at several sites to trigger **new upstream connections (= new Reality handshakes)**.
318
319 ```console
320 $ tshark -i eth0 -f "host 203.0.113.10 and tcp port 443" -w reality.pcap &
321 $ for h in example.com www.wikipedia.org api.ipify.org neverssl.com; do
322 curl -s -o /dev/null "https://$h/?cachebust=$RANDOM"; done
323 ```
324
325 **(3-a) The SNI and TLS version DPI sees:**
326
327 ```console
328 $ tshark -r reality.pcap -Y "tls.handshake.type==1" \
329 -T fields -e frame.number -e ip.dst -e tcp.dstport \
330 -e tls.handshake.extensions_server_name -e tls.handshake.ja3_full
331 ```
332
333 | frame | ip.dst | port | SNI | Extension order |
334 |---|---|---|---|---|
335 | 31 | 203.0.113.10 | 443 | **www.example.jp** | `0-45-17613-16-11-35-18-5-27-51-13-23-10-65037-65281-43` |
336 | 73 | 203.0.113.10 | 443 | **www.example.jp** | `18-35-65281-16-11-17613-65037-5-13-43-51-45-10-23-0-27` |
337 | 118 | 203.0.113.10 | 443 | **www.example.jp** | `16-23-0-51-13-10-18-5-11-27-17613-45-65037-43-35-65281` |
338 | 186 | 203.0.113.10 | 443 | **www.example.jp** | `65037-23-43-27-16-51-45-18-5-13-35-10-0-65281-11-17613` |
339
340 Two observations:
341
342 1. **Every SNI is `www.example.jp`** — the only destination hint DPI can read in plaintext is "example.jp." I actually went to example.com, Wikipedia and ipify, but **none of that leaves a trace on the wire.**
343 2. **The extension order is shuffled on every connection** — this is no accident; it is uTLS faithfully imitating **the real behavior of Chrome 110+ (randomized extension order)**. The leading cipher suites, `4865-4866-4867` (the three TLS 1.3 AEADs), match Chrome too. In other words, the JA3 fingerprint changes with every connection, so **"fixed-fingerprint matching" can never catch it.** The extension list even carries Chrome-specific extensions such as `65037` (GREASE ECH), `17613` (ALPS) and `27` (compress_certificate).
344
345 **(3-b) The wire bytes — the SNI hostname sits there in plaintext:**
346
347 ```console
348 # Hostname bytes of the SNI extension inside the ClientHello (frame 31)
349 0e 77 77 77 2e 65 78 61 6d 70 6c 65 2e 6a 70 -> "www.example.jp" (0e = length 14)
350 ```
351
352 After the length byte `0e` (14), `77 77 77 2e 65 78 61 6d 70 6c 65 2e 6a 70` = **"www.example.jp"** is carried as-is, in plaintext. **This is everything DPI reads off the line** — byte for byte, it is identical to an ordinary Chrome user opening example.jp.
353
354 ### PoC-4. Performance
355
356 Even across three hops (Korea → Tokyo Reality → Cloudflare WARP), real-world performance is solid.
357
358 ```console
359 $ for i in 1 2 3; do curl -s -o /dev/null \
360 -w "ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
361 https://www.cloudflare.com/cdn-cgi/trace; done
362 ttfb=0.234s total=0.235s
363 ttfb=0.258s total=0.259s
364 ttfb=0.258s total=0.258s
365
366 $ curl -s -o /dev/null -w "speed=%{speed_download} bytes/s\n" \
367 "https://speed.cloudflare.com/__down?bytes=10000000"
368 speed=7615746 bytes/s # ≈ 61 Mbps (10MB in 1.31s)
369 ```
370
371 - **TTFB ≈ 235–260 ms** (including the Korea↔Tokyo round trip and the WARP hop). No noticeable impact on browsing.
372 - **Download ≈ 61 Mbps** (single stream, 3 hops). That's thanks to VLESS dropping double encryption and `xtls-rprx-vision` cutting overhead.
373
374 ---
375
376 ## 6. The whole journey (end to end)
377
378 Here is what actually happens when you run `curl https://example.com`, as a sequence:
379
380 ```mermaid
381 sequenceDiagram
382 autonumber
383 participant A as curl / app
384 participant S as sing-box (TUN)
385 participant X as Xray Tokyo :443
386 participant W as WARP / Cloudflare
387 participant D as example.com
388
389 A->>S: DNS lookup example.com
390 S-->>A: FakeIP 198.18.x.x (no real resolution → zero leakage)
391 A->>S: TCP → 198.18.x.x:443 (absorbed by TUN)
392 Note over S: Routing decision = proxy (everything proxied)
393 S->>X: Reality handshake<br/>SNI=www.example.jp · uTLS=chrome
394 Note over S,X: ⚠ The only scene DPI sees<br/>'a Chrome user viewing example.jp'
395 S->>X: VLESS header: real destination = example.com:443
396 X->>W: warp-out (WireGuard inside Xray)
397 W->>D: Connects from exit 104.28.243.105
398 D-->>W: Response
399 W-->>X: Response
400 X-->>S: Response
401 S-->>A: Response (curl done)
402 ```
403
404 Step by step:
405
406 1. **DNS** — sing-box intercepts `example.com` and immediately returns the **FakeIP `198.18.x.x`** (no real resolution → zero leakage).
407 2. **TUN absorption** — `singbox-tun` swallows the packets headed for `198.18.x.x`.
408 3. **Routing decision** — `final: proxy`; everything goes to the proxy.
409 4. **Reality handshake** — sing-box connects to the server at `203.0.113.10:443`. **It uses uTLS to generate a ClientHello with Chrome's fingerprint**, with SNI=`www.example.jp`. The shortId and UUID ride along in the x25519 key exchange to prove to the server that this is "a legitimate client." ← **DPI sees this only as a connection to example.jp.**
410 5. **VLESS routing** — inside the tunnel, the client tells the server "the real destination is example.com:443."
411 6. **Server routing** — Xray picks `warp-out`.
412 7. **WARP egress** — Xray's internal WireGuard hands the packets to the Cloudflare edge (NRT).
413 8. **Final exit** — Cloudflare connects to example.com from `104.28.243.105`. **The origin sees only a Cloudflare IP.**
414 9. **Return path** — the response travels back in reverse, 8→1, and reaches curl.
415
416 What the censor sees: **a single scene, step 4**, and even that is "a Chrome user viewing example.jp."
417
418 ---
419
420 ## 7. Limitations and operational considerations
421
422 The weaknesses of this setup, and what could be improved.
423
424 - **Traffic correlation**: if a censor can see both ends of the line, timing and volume correlation still leave room for inference. Reality hides only the content and the destination; it does not stop metadata correlation.
425 - **Camouflage domain risk**: if the site chosen as `dest` goes down or changes its TLS stack, the camouflage starts to look off. Staying in the same ASN/region as the server matters.
426 - **IP reputation**: WARP exits are shared IPs, so some sites block WARP ranges or show them CAPTCHAs. That is why torrents are blocked, to manage IP reputation.
427 - **More sophisticated active probes**: a censor could look statistically for "behavior a legitimate example.jp user would not show" (e.g., connecting and then never sending an HTTP request). Reality defends against this well, but the theoretical attack surface is there.
428 - **Single point of failure**: one server in Tokyo. Multiple servers with automatic failover are future work.
429
430 Even so, **from a censorship-circumvention standpoint it is overwhelmingly better than plain WireGuard/OpenVPN**. Where WireGuard "gives itself away in the first packet," the PoCs above demonstrate that this setup **cannot be told apart from normal traffic at any layer — packets, certificates or fingerprints.**
431
432 ---
433
434 ## 8. Conclusion
435
436 > **The essence of censorship circumvention is not encryption but "indistinguishability."**
437
438 WireGuard encrypts traffic perfectly, yet it gets caught because it cannot hide *the very fact that it is encrypted*. Reality survives the opposite way: by **turning my traffic into something the censor can never block (ordinary TLS 1.3 to a well-known site)**. Put WARP on top, and even the exit IP gets laundered.
439
440 - **Exit IP** → Cloudflare (even my server's IP is hidden) ✅
441 - **Active probing** → answered with the real example.jp certificate ✅
442 - **Passive observation** → plaintext SNI is `www.example.jp`, with a Chrome uTLS fingerprint (shuffled per connection) ✅
443 - **Performance** → TTFB 250 ms, 61 Mbps ✅
444
445 The fight against DPI comes down to a game of *"how closely does my traffic resemble the normal traffic a censor can't bring itself to cut?"* — and this setup is designed to win that game.
446
447 ---
448
449 ## References
450
451 **Concepts**
452 - 🎬 [*How I Hid My VPN* (YouTube)](https://youtu.be/v7Pal3AQyVM) — an introduction to the idea from a "concealment, not encryption" angle
453 - 📄 [VLESS dissected in depth (Habr #990144)](https://habr.com/en/articles/990144/) — analyzes VLESS as a minimal TLS wrapper; DPI's three main detection techniques
454
455 **Reality / XTLS**
456 - [XTLS/REALITY (official repository)](https://github.com/XTLS/REALITY)
457 - [Project X — Fallbacks with SNI (official documentation)](https://xtls.github.io/en/document/level-1/fallbacks-with-sni.html)
458 - [How REALITY works — a source-level analysis (ObjShadow)](https://objshadow.pages.dev/en/posts/how-reality-works/)
459 - [Defending against active probing — Xray-core Discussion #2623](https://github.com/XTLS/Xray-core/discussions/2623)
460 - [A deep dive into VLESS + Reality + XTLS Vision (Hakim Mhioul)](https://www.mhioul.com/blog/vless-reality-xtls-deep-dive)
461
462 **Client / libraries / exit**
463 - [sing-box official documentation](https://sing-box.sagernet.org)
464 - [uTLS — refraction-networking/utls](https://github.com/refraction-networking/utls) (browser TLS fingerprint camouflage)
465 - [Cloudflare WARP client documentation](https://developers.cloudflare.com/warp-client/)
466
467 **WireGuard's detectability (the control group)**
468 - [The Imitation Game — making WireGuard invisible to DPI (NT Kernel)](https://www.ntkernel.com/the-imitation-game-how-modern-solutions-make-wireguard-invisible-to-dpi/)
469 - [VLESS vs WireGuard/Shadowsocks/Trojan — DPI and speed compared (Proxy Poland)](https://proxypoland.com/blog/vless-vs-wireguard-shadowsocks-trojan-comparison)
470 - [AmneziaWG 2.0 — self-hosting an obfuscated WireGuard (DEV)](https://dev.to/bivlked/amneziawg-20-self-host-an-obfuscated-wireguard-vpn-that-bypasses-dpi-4692)
471 - [How DPI detects and blocks VPNs (CrazyPrivacy)](https://www.crazyprivacy.com/2025/10/27/understanding-deep-packet-inspection-dpi-how-isps-detect-and-block-vpn-traffic/)
472
473 ---