Files
ai-xray/plan_enduser_.md
2026-03-08 07:09:56 +00:00

1046 lines
40 KiB
Markdown

# Enduser → Local Server Hop Plan
**Tag**: `_enduser_`
**Date**: 2026-02-21
**Context**: Based on the completed juris-server comparative test (winner: VLESS+XHTTP+Reality, 142ms avg, 225ms P95). This plan extends the chain by adding a new hop between mobile end-user devices and the local server, which acts as the new inbound gateway.
---
## Table of Contents
1. [Architecture Overview](#architecture)
2. [Context Verification](#context)
3. [Russia Cellular DPI — In-Depth Analysis](#dpi-analysis)
4. [Local Server: New Inbound Configuration](#local-inbound)
5. [Client Configuration by Device Type](#clients)
6. [Android Remote Testing Setup](#android-remote)
7. [Test Matrix: 9 Configurations](#test-matrix)
8. [Test Automation Plan](#automation)
9. [SNI and Reality Key Strategy](#sni-strategy)
10. [Advanced Improvements for Cellular](#advanced)
11. [Implementation Order](#implementation)
12. [Success Metrics](#metrics)
---
## 1. Architecture Overview <a name="architecture"></a>
### Full Chain (Target State)
```
End-User Device (Russia, cellular/WiFi)
│ ← New hop (this plan) — VLESS+XHTTP+Reality, Russian SNI
Local Server (95.165.85.65, Russia)
│ [new inbound: enduser-facing VLESS+XHTTP+Reality]
│ [routing: enduser inbound → juris-xhttp outbound]
juris Server (83.99.190.32, Latvia) ← existing, proven working
│ [XHTTP+Reality on port 443, SNI: www.delfi.lv]
Internet
```
### Device Types in Scope
| Device | OS | Client App | Fingerprint Recommendation |
|--------|----|-----------|--------------------------|
| Pixel/other Android | Android 11-14 | v2rayNG / Hiddify / NekoBox | `android` |
| iPhone | iOS 16-17 | Streisand / Shadowrocket / Hiddify | `ios` |
| iPad | iPadOS 16-17 | Streisand / Shadowrocket / Hiddify | `ios` |
---
## 2. Context Verification <a name="context"></a>
### Confirmed state from previous work
- **juris link (hop 2)**: VLESS+XHTTP+Reality on port 443, SNI=www.delfi.lv, host header=www.delfi.lv, fingerprint=chrome. Tested and proven. P95=225ms, avg=142ms.
- **Local server inbounds (current)**:
- `inbound-127.0.0.1:8445`: VLESS on localhost:8445, 4 clients (ipad pro, pixel, iphone promax, AlinaSt) — **localhost only, not accessible externally**
- `inbound-56928`: mixed SOCKS/HTTP on 0.0.0.0:56928, routes to AMS-Server — unencrypted, not suitable for cellular
- **Gap**: No external-facing, DPI-resistant inbound exists on local server for mobile clients
### What this plan adds
- A new **external VLESS+XHTTP+Reality inbound** on the local server, accessible from cellular/WiFi
- A routing rule that funnels enduser traffic to `juris-xhttp` outbound
- Mobile client configurations with device-appropriate fingerprints
- Automated testing infrastructure for Android
---
## 3. Russia Cellular DPI — In-Depth Analysis <a name="dpi-analysis"></a>
### 3.1 Difference from Foreign-Destination DPI
The enduser→local server hop is **fundamentally different** from the local→juris hop:
| Dimension | Local → juris (Latvia) | Enduser → Local Server (Russia) |
|-----------|----------------------|--------------------------------|
| Destination IP | Foreign datacenter (Hetzner-like) | Russian IP (95.165.x.x) |
| CIDR block risk | HIGH (datacenter CIDR) | NONE (domestic ASN) |
| TSPU scrutiny level | Maximum | Reduced for domestic traffic |
| SNI whitelist check | Critical (foreign SNI needed) | Less critical — domestic SNI always passes |
| Protocol fingerprint risk | HIGH | MEDIUM |
| Volume-based TCP freezing | Active | Likely less aggressive |
**Critical advantage**: Traffic to a Russian IP is not subject to CIDR-based foreign-IP blocking. The cellular TSPU deploys its harshest rules specifically for connections exiting to foreign ASNs.
### 3.2 What Russian Cellular DPI Actually Does (2026)
Russian cellular operators (MTS, MegaFon, Beeline/VimpelCom, Tele2) deploy TSPU at their peering points and backbone. For **intra-Russia** cellular traffic:
**Applied:**
1. **Content-based filtering (Roskomnadzor)**: Blocks domains/IPs from the RKN registry. This affects the *destination* of the end traffic (juris → internet), not the enduser → local hop itself.
2. **Protocol detection (light)**: TSPU still fingerprints protocol even for domestic traffic. If it detects "obvious proxy" traffic on well-known proxy ports, it may rate-limit.
3. **SNI-based analysis**: The SNI presented in the TLS ClientHello is logged. Suspicious or absent SNIs may trigger deeper inspection even for domestic destinations.
4. **Port restrictions**: Some cellular operators block outbound non-standard ports (anything not 80/443/8080/8443) for end-user subscriber connections. This is operator-specific (Beeline is known to restrict high ports on some tariffs; Tele2 is more permissive).
**NOT applied (for domestic traffic):**
- CIDR-based whitelist blocking (only applied for foreign IP ranges)
- Volume-based TCP freezing (mainly triggered by foreign-IP connections exceeding 15-20 KB/s threshold)
- Anti-probing protection from the perspective of the TSPU — the TSPU probing is directional
### 3.3 Cellular-Specific Challenges
1. **CGNAT**: All mobile subscribers are behind Carrier-Grade NAT. The local server sees a shared public IP. This is fine — it doesn't affect xray operation, but means:
- Cannot use source IP to identify specific devices
- UDP hole-punching does not work (relevant for Hysteria2)
- TCP is fully functional through CGNAT
2. **Port 443 availability**: Port 443 outbound is essentially always open on all Russian cellular networks (it carries HTTPS). **This is the safest port for the enduser inbound**.
3. **High port availability**:
- **MTS**: Generally open, high ports work for most tariffs
- **MegaFon**: Mostly open, some corporate tariffs restrict
- **Beeline**: Some consumer tariffs restrict high ports; business tariffs are open
- **Tele2**: Generally permissive, high ports usually work
- **Recommendation**: Test port 443 first; add port 8443 as secondary; high port (47xxx) as tertiary
4. **UDP availability**:
- Cellular networks allow UDP 443 (for QUIC) in most cases
- But CGNAT can interfere with stateful UDP tracking on longer connections
- Hysteria2 on UDP may have connection drops after inactivity; requires keepalive tuning
5. **IPv6**: Russian cellular networks increasingly support IPv6 dual-stack. Xray supports IPv6. The local server needs to accept IPv6 connections if we want to support this (add `::` or `::0` to listen address).
### 3.4 Key Advantage: Russian SNI = Always Whitelisted
For the enduser→local server Reality configuration, we can use **Russian domestic domains** as the SNI:
```
vk.com → VKontakte, national social network, absolutely always whitelisted
yandex.ru → Yandex, national search engine, always whitelisted
gosuslugi.ru → State services portal, government mandate, never blocked
mos.ru → Moscow city government, never blocked
mail.ru → Mail.ru, national email, always whitelisted
ok.ru → Odnoklassniki, national social network, always whitelisted
```
When a cellular subscriber's DPI inspects the TLS ClientHello and sees SNI=`gosuslugi.ru` or `vk.com`, it **passes the whitelist check unconditionally**. This is better than any foreign SNI, no matter how legitimate the foreign domain appears.
**Reality destination (dest)**: The local server needs to be able to proxy-handshake to the real destination server. Since the local server is in Russia, it can reach all Russian domains without issue:
- `vk.com:443` — local server fetches real TLS from VK servers
- `gosuslugi.ru:443` — fetches from state portal servers
- `yandex.ru:443` — fetches from Yandex
### 3.5 Why Standard VLESS/Shadowsocks Without Reality is Risky on Cellular
Without Reality camouflage:
- Raw VLESS over TLS uses a certificate signed for your server's domain (or self-signed)
- The TLS fingerprint is identifiable as Xray/Go runtime unless uTLS is used
- The server responds to active probing: automated probers send non-proxy traffic and identify the server
- **Reality solves all three**: impersonates a real HTTPS server, uses uTLS to match real browser fingerprints, passes active probing by forwarding probes to the real dest
---
## 4. Local Server: New Inbound Configuration <a name="local-inbound"></a>
### 4.1 Prerequisites
1. **Generate new Reality keypair** (separate from juris link keypair):
```bash
/usr/local/x-ui/bin/xray-linux-amd64 x25519
```
Save `privateKey` (server) and `publicKey` (clients).
2. **Generate new UUID per client** (or reuse existing ones):
```bash
/usr/local/x-ui/bin/xray-linux-amd64 uuid
```
Existing clients from `inbound-127.0.0.1:8445`: ipad pro, pixel, iphone promax, AlinaSt — reuse these UUIDs.
3. **Generate new Short IDs** (one per client or one shared):
```bash
openssl rand -hex 8 # repeat per shortId needed
```
4. **Verify port 443 availability** on local server:
```bash
ss -tlnp | grep ':443' # should be empty if port is free
sudo ufw allow 443/tcp # if ufw firewall is active
```
5. **Check local server's public IP** is 95.165.85.65 and not behind NAT:
```bash
curl -s https://api.ipify.org
```
### 4.2 New Inbound: VLESS + XHTTP + Reality (Primary)
Add this inbound via x-ui API (`POST /panel/api/inbounds/add`):
```json
{
"tag": "inbound-enduser",
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "13a01d7c-9ca7-4ee6-83d8-4c59907b13d1",
"email": "ipad-pro",
"flow": ""
},
{
"id": "108fdf6b-f8e4-4e1a-9bbe-b7c1fa9fffa0",
"email": "pixel",
"flow": ""
},
{
"id": "584cba3d-2d43-464a-9b29-67e02adc092d",
"email": "iphone-promax",
"flow": ""
},
{
"id": "f6781d19-cf77-4f8f-9114-0e612aa3081c",
"email": "AlinaSt",
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"dest": "vk.com:443",
"serverNames": ["vk.com", "gosuslugi.ru", "yandex.ru"],
"privateKey": "<NEW-ENDUSER-PRIVATE-KEY>",
"shortIds": [
"<SHORT-ID-IPAD>",
"<SHORT-ID-PIXEL>",
"<SHORT-ID-IPHONE>",
"<SHORT-ID-ALINA>"
],
"show": false
},
"xhttpSettings": {
"path": "/eu-<SHORT-ID-PIXEL>",
"host": "vk.com",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"xmux": {
"maxConcurrency": "8-16",
"maxConnections": 0,
"cMaxReuseTimes": "32-64",
"cMaxLifetimeMs": 0,
"hMaxRequestTimes": "300-600",
"hMaxReusableSecs": "900-1800"
}
}
}
}
}
```
**Notes on xmux tuning for mobile vs server-server**:
- Mobile devices have more variable connections; use lower concurrency than the juris link (8-16 vs 16-32)
- Lower `hMaxReusableSecs` (900-1800 vs 1800-3000) to handle mobile reconnects more gracefully
- `cMaxReuseTimes: "32-64"` (vs 64-128) — more frequent connection cycling is friendly to CGNAT
### 4.3 New Routing Rule
Add routing rule to route enduser inbound traffic to the proven juris outbound:
```json
{
"type": "field",
"inboundTag": ["inbound-enduser"],
"outboundTag": "juris-xhttp"
}
```
This rule should be inserted **first** (highest priority) in the routing rules list.
### 4.4 x-ui API Method to Apply
```python
import requests, json
BASE = "http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe"
s = requests.Session()
s.post(f"{BASE}/login", data={"username": "3ZHPoQdd89", "password": "1c1QUbKhQP"})
# Read current template
r = s.post(f"{BASE}/panel/xray/")
obj = json.loads(r.json()["obj"])
xray = obj["xraySetting"]
# 1. Add new enduser inbound
xray["inbounds"].append({... enduser inbound ...})
# 2. Add routing rule (prepend)
xray["routing"]["rules"].insert(0, {
"type": "field",
"inboundTag": ["inbound-enduser"],
"outboundTag": "juris-xhttp"
})
# Write back and restart
s.post(f"{BASE}/panel/xray/update", data={"xraySetting": json.dumps(xray)})
s.post(f"{BASE}/panel/api/server/restartXrayService")
```
---
## 5. Client Configuration by Device Type <a name="clients"></a>
### 5.1 Android (v2rayNG / Hiddify / NekoBox)
**Recommended app**: v2rayNG (most widely tested with VLESS+XHTTP+Reality)
**VLESS URI (share link)**:
```
vless://<UUID>@95.165.85.65:443?type=xhttp&security=reality&path=%2Feu-<SHORT-ID>&host=vk.com&fp=android&pbk=<PUBLIC-KEY>&sid=<SHORT-ID>&sni=vk.com#Pixel-enduser
```
**Manual config in v2rayNG**:
- Address: `95.165.85.65`
- Port: `443`
- UUID: `<device UUID>`
- Encryption: `none`
- Flow: (empty)
- Network: `xhttp`
- Header Type: (none)
- Host: `vk.com`
- Path: `/eu-<SHORT-ID>`
- Security: `reality`
- SNI: `vk.com`
- uTLS Fingerprint: `android`
- Public Key: `<enduser public key>`
- ShortId: `<device short id>`
- SpiderX: `/`
**Fingerprint note**: Use `android` fingerprint for Android devices — it matches the Android TLS stack more accurately than `chrome` and avoids the paradox where a "Chrome" fingerprint appears on non-browser traffic.
### 5.2 iOS and iPadOS (Shadowrocket / Streisand / Hiddify)
**Recommended app**: Streisand (sing-box backend, best compatibility) or Shadowrocket (paid, $2.99, very popular)
**iOS-specific considerations**:
- Use `ios` or `safari` fingerprint — matches iOS TLS stack
- iOS apps generate their own TLS fingerprint from the platform stack; `ios` profile matches best
- Hiddify iOS and Streisand use sing-box which implements proper iOS fingerprinting
**VLESS URI for iOS**:
```
vless://<UUID>@95.165.85.65:443?type=xhttp&security=reality&path=%2Feu-<SHORT-ID>&host=vk.com&fp=ios&pbk=<PUBLIC-KEY>&sid=<SHORT-ID>&sni=vk.com#iPhone-enduser
```
**Shadowrocket config**:
- Type: VLESS
- Server: `95.165.85.65`
- Port: `443`
- UUID: `<device UUID>`
- Transport: XHTTP
- Host: `vk.com`
- Path: `/eu-<SHORT-ID>`
- TLS: Reality
- SNI: `vk.com`
- Public Key: `<enduser public key>`
- Short ID: `<device short id>`
- Fingerprint: Safari (or iOS if available)
**iPad**: Identical to iPhone configuration. iPadOS uses same iOS TLS stack. Same app recommendations.
### 5.3 Fingerprint Selection Per Device
| Device | OS | Recommended Fingerprint | Why |
|--------|----|------------------------|-----|
| Android phone | Android | `android` | Matches Android TLS stack |
| iPhone | iOS | `ios` | Matches iOS TLS stack |
| iPad | iPadOS | `ios` | Same as iPhone |
| Android tablet | Android | `android` | Same as Android phone |
| Testing (laptop) | Linux/Windows | `chrome` | Matches browser |
**Reality anti-probing compatibility**: All named fingerprints (`chrome`, `firefox`, `safari`, `ios`, `android`) are accepted. Do NOT use `randomized` (incompatible with Reality — triggers connection reset).
---
## 6. Android Remote Testing Setup <a name="android-remote"></a>
### 6.1 Problem Statement
End-user device testing requires:
1. **Configuring xray** on the device (change protocol, SNI, port)
2. **Running test commands** (curl, speed tests)
3. **Collecting metrics** (latency, throughput)
4. **Doing this for 9+ configurations** without manual intervention per test
### 6.2 Option A: Termux + SSH Reverse Tunnel (RECOMMENDED)
This is the best option: **no USB, fully over WAN, fully automated**.
#### Setup on Android
1. Install **Termux** (from F-Droid, not Play Store — Play version is outdated)
2. In Termux:
```bash
pkg update && pkg install openssh curl python3
# Generate SSH key
ssh-keygen -t ed25519
# Start SSH server in Termux
sshd # runs on port 8022 by default
```
3. Copy public key to Termux's authorized_keys:
```bash
# On local server, generate key and add to Android
cat /home/alvis/.ssh/id_ed25519.pub
# On Android Termux:
mkdir ~/.ssh && echo "<local server pubkey>" >> ~/.ssh/authorized_keys
```
4. Install v2rayNG on Android and configure it with the enduser inbound credentials.
#### SSH Reverse Tunnel (run from Android Termux)
```bash
# On Android Termux — establish reverse tunnel to local server
# This exposes Android's Termux SSH port (8022) as port 2222 on localhost of local server
# Also forwards Android's xray SOCKS port (10808) as 10808 on local server
autossh -M 0 -N -f \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-R 2222:localhost:8022 \
-R 10808:localhost:10808 \
alvis@95.165.85.65
```
Or using regular SSH (without autossh):
```bash
ssh -N -f \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-R 2222:localhost:8022 \
-R 10808:localhost:10808 \
alvis@95.165.85.65
```
**Important**: The SSH connection from Android to local server goes through the EXISTING cellular connection. Once the enduser xray proxy is active, use `-J` or connect through the proxy to avoid circular routing.
Alternative: the Android device connects to local server's SSH directly over cellular (not through the proxy), establishing the tunnel independently.
#### Remote Control from Local Server
```bash
# SSH into Android Termux via reverse tunnel
ssh -p 2222 127.0.0.1
# Run tests on Android (via the reverse-tunneled SOCKS port 10808):
curl -s --socks5 127.0.0.1:10808 https://api.ipify.org
# Or run curl on the Android device directly:
ssh -p 2222 127.0.0.1 "curl -s --socks5 127.0.0.1:10808 https://api.ipify.org"
```
#### Changing xray Config on Android Remotely
v2rayNG stores configs as JSON files. Via Termux SSH:
```bash
# Find v2rayNG config dir (typical path)
ls /data/data/com.v2ray.ang/files/
# Or use v2rayNG's JSON config mode — place a config file and reload
# In Termux (no root needed for Termux files):
cat > ~/xray_test_config.json << 'EOF'
{... new config ...}
EOF
# Then reload v2rayNG via intent (needs adb or Termux:API):
am broadcast -a com.v2ray.ang.action.RELOAD_CONFIG
```
**Simpler approach using Termux xray directly** (avoid v2rayNG complexity):
```bash
# Install xray in Termux:
pkg install xray # or download binary
# Run test configs directly from Termux, no GUI app needed
xray -config /data/data/com.termux/files/home/test_config.json &
curl --socks5 127.0.0.1:10808 https://api.ipify.org
kill %1 # kill xray
```
This gives full programmatic control without interacting with v2rayNG GUI at all.
### 6.3 Option B: ADB Wireless Debugging (Android 11+, Same Network or VPN)
1. On Android: Settings → Developer Options → Wireless Debugging → Enable
2. Tap "Pair device with code" — note IP:port and pairing code
3. On local server: `adb pair <ip>:<port> <code>`
4. Then: `adb connect <ip>:5555` (or the shown port)
5. ADB commands work: `adb shell curl ...`
**Limitation**: ADB wireless requires direct network reachability. Over cellular CGNAT, direct connection from local server to device is impossible (CGNAT blocks inbound). Requires VPN or the SSH tunnel above to be active first.
**Workaround**: After ADB TCP is enabled on the device (`adb tcpip 5555`), create a reverse SSH tunnel specifically for ADB:
```bash
# On Android Termux:
ssh -R 5555:localhost:5555 alvis@95.165.85.65
# On local server:
adb connect 127.0.0.1:5555
adb shell
```
### 6.4 Option C: Remote Desktop Apps (Manual Testing)
For **manual** configuration verification (not automation):
**RustDesk** (recommended open source):
- Install RustDesk server on local server: `docker run -d rustdesk/rustdesk-server`
- Install RustDesk app on Android
- Connect from any machine using RustDesk client with device ID
- Full screen control, mouse/keyboard, no USB needed
- Works over any internet connection including cellular
**AnyDesk** or **TeamViewer**: Commercial alternatives, same capability.
Use these for:
- Initial setup verification
- Visual confirmation that xray is connected
- Troubleshooting when SSH connection fails
### 6.5 Recommended Approach: Termux + autossh
```
[ Local Server ] ←──SSH tunnel──── [ Android + Termux ]
│ │
│ ssh -p 2222 127.0.0.1 │ xray/v2rayNG running
│ │ SOCKS on 10808
│ curl via 10808 (tunneled) ────────┘
Test script runs automatically, collects metrics
```
No USB. No physical access. Fully automated. Works over cellular WAN.
---
## 7. Test Matrix: 9 Configurations <a name="test-matrix"></a>
All tests: enduser device → local server (95.165.85.65). Both in Russia. Device on cellular.
| # | ID | Transport | Port | Security | SNI | Fingerprint | Special | Priority |
|---|----|-----------|------|----------|-----|-------------|---------|----------|
| 1 | `eu-A` | XHTTP | 443 | Reality | vk.com | android/ios | Baseline domestic | **PRIMARY** |
| 2 | `eu-B` | XHTTP | 443 | Reality | gosuslugi.ru | android/ios | Government SNI | HIGH |
| 3 | `eu-C` | XHTTP | 443 | Reality | yandex.ru | android/ios | Search engine SNI | HIGH |
| 4 | `eu-D` | TCP+Vision | 443 | Reality | vk.com | android/ios | TCP baseline | BASELINE |
| 5 | `eu-E` | XHTTP | 8443 | Reality | vk.com | android/ios | Alt port | ALTERNATE |
| 6 | `eu-F` | gRPC | 443 | Reality | vk.com | android/ios | H2 gRPC pattern | SECONDARY |
| 7 | `eu-G` | XHTTP | 443 | Reality | vk.com | android/ios | + TLS ClientHello frag | ENHANCED |
| 8 | `eu-H` | WebSocket | 443 | TLS+nginx | local domain | (real cert) | Nginx camouflage | FALLBACK |
| 9 | `eu-I` | Hysteria2 | 443/UDP | TLS | — | — | UDP bypass | UDP TEST |
### Configuration Details
#### eu-A: XHTTP+Reality, vk.com (Primary Baseline)
- Same proven approach as juris link
- Russian SNI → unconditional whitelist pass
- Expected: best performance, similar to juris link
#### eu-B: XHTTP+Reality, gosuslugi.ru (Government SNI)
- `gosuslugi.ru` is the state services portal — hardcoded whitelist on all Russian ISPs
- Even if vk.com SNI is somehow flagged, government domains are untouchable
- dest: `gosuslugi.ru:443` — local server must be able to reach this
#### eu-C: XHTTP+Reality, yandex.ru
- Yandex is the dominant Russian search engine — always whitelisted
- Tests whether Yandex SNI vs VK SNI matters
#### eu-D: TCP+Reality+Vision, vk.com (TCP Baseline)
- Establishes TCP baseline for domestic link
- Compare against eu-A to see if XHTTP advantage holds for domestic traffic
- Note: volume-based freezing less likely for domestic traffic, TCP+Vision may be competitive
#### eu-E: XHTTP+Reality, port 8443 (Alt Port)
- Tests port 8443 as alternative HTTPS port
- Useful if cellular operator blocks port 443 for non-HTTP traffic
- Lower DPI scrutiny than port 443 on some operators
#### eu-F: gRPC+Reality, vk.com
- H2 gRPC pattern appears as enterprise API traffic
- Tests if gRPC's multiplexing benefits mobile users
- Compare latency vs XHTTP
#### eu-G: XHTTP+Reality + ClientHello Fragmentation
- Same as eu-A but adds TLS ClientHello fragmentation:
```json
"sockopt": {"dialerProxy": "frag-chain1"}
```
- Tests whether fragmentation improves mobile cellular performance
- Note: fragmentation adds connection establishment latency
#### eu-H: WebSocket+TLS via Nginx (Camouflage Layer)
- Requires nginx on local server with real TLS certificate
- If local server has a domain (or can use a Russian free domain), get TLS cert
- WS traffic looks like a regular HTTPS website with WebSocket upgrade
- xray listens on localhost, nginx terminates TLS and proxies WS
- This makes traffic completely indistinguishable from real web traffic
- Cost: requires domain + cert management
#### eu-I: Hysteria2 UDP (Cellular UDP Test)
- Hysteria2 uses QUIC (UDP 443)
- On cellular: QUIC is used for HTTP/3, widely allowed
- Hysteria2's congestion control is optimized for high-loss, high-latency links (like cellular)
- Test requires Hysteria2-capable client app (Hiddify supports it; v2rayNG via Xray v26.x)
- CGNAT caveat: stateful UDP over CGNAT can drop after ~30s of inactivity; set `keepAlive: 10` in Hysteria2 config
### Per-Configuration SNI Advantages
| SNI | Category | Whitelist Confidence | Latency to Dest | Note |
|-----|----------|---------------------|-----------------|------|
| vk.com | Social/National | 100% — state-endorsed | Low (Russia) | Best choice |
| gosuslugi.ru | Government | 100% — state-mandated | Low (Russia) | Most protected |
| yandex.ru | Search/National | 100% — state-endorsed | Low (Russia) | High traffic volume |
| mos.ru | Government/Moscow | 100% | Low (Russia) | Good alternative |
| mail.ru | Email/National | 99% | Low (Russia) | |
---
## 8. Test Automation Plan <a name="automation"></a>
### 8.1 Test Script: `test_enduser_configs.sh`
To be executed on local server. Controls Android device via SSH reverse tunnel.
```bash
#!/usr/bin/env bash
set -euo pipefail
ANDROID_SSH="ssh -p 2222 127.0.0.1" # Android via reverse tunnel
ANDROID_SOCKS="--socks5 127.0.0.1:10808" # Android's xray SOCKS (tunneled)
RESULTS_FILE="/home/alvis/ai-xray/enduser_test_results.md"
CONFIGS_DIR="/home/alvis/ai-xray/enduser_configs"
LATENCY_SAMPLES=10
DOWNLOAD_URL="https://speed.cloudflare.com/__down?bytes=10485760" # 10 MB
run_test() {
local config_name="$1"
local config_file="$2"
echo "=== Testing: $config_name ==="
# Push config to Android, restart xray
scp "$config_file" "android:~/xray_config.json"
$ANDROID_SSH "pkill xray 2>/dev/null || true; sleep 1"
$ANDROID_SSH "xray -config ~/xray_config.json &>/dev/null &"
sleep 3
# Connectivity check
local exit_ip
exit_ip=$($ANDROID_SSH "curl -s $ANDROID_SOCKS https://api.ipify.org" 2>/dev/null || echo "FAIL")
# Latency samples
local latency_sum=0
local latency_samples=()
for i in $(seq 1 $LATENCY_SAMPLES); do
local t
t=$($ANDROID_SSH "curl -s -o /dev/null -w '%{time_total}' $ANDROID_SOCKS https://www.google.com" 2>/dev/null || echo "9.999")
latency_samples+=("$t")
latency_sum=$(echo "$latency_sum + $t" | bc)
done
local avg_ms
avg_ms=$(echo "scale=0; ($latency_sum / $LATENCY_SAMPLES) * 1000 / 1" | bc)
# Download speed (10 MB)
local dl_result
dl_result=$($ANDROID_SSH "curl -s -o /dev/null -w '%{speed_download}' $ANDROID_SOCKS $DOWNLOAD_URL" 2>/dev/null || echo "0")
local dl_mbps
dl_mbps=$(echo "scale=2; $dl_result * 8 / 1048576" | bc)
echo "| $config_name | $exit_ip | ${avg_ms}ms | ${dl_mbps} Mbps |"
}
# Test all 9 configurations
for config in $CONFIGS_DIR/eu-*.json; do
config_name=$(basename "$config" .json)
run_test "$config_name" "$config"
done
```
### 8.2 Remote Inbound Swap (Local Server)
For testing different local server inbounds (transports, ports, SNIs):
- Use local x-ui API to swap the enduser inbound
- Same methodology as the juris server testing (proven pattern)
```python
# Swap enduser inbound transport for each test
# POST /panel/api/inbounds/update/<enduser_inbound_id>
```
### 8.3 iOS/iPad Testing (Semi-Manual)
iOS does not support Termux or similar. Options:
1. **Manual app configuration**: Configure proxy in Shadowrocket/Streisand, run browser speed test (fast.com, speedtest.net)
2. **iOS Shortcuts + curl** (iOS 16+): Use Shortcuts app with curl via SOCKS proxy
3. **Network Extension API**: Professional approach via custom iOS app — overkill for testing
4. **Proxy System-Wide**: Set HTTP proxy in iOS Settings → WiFi → HTTP Proxy → Manual. Point to Android device's proxy port (if on same WiFi). Use Android device as test proxy.
**Practical iOS test procedure**:
1. Configure proxy app (Shadowrocket) with test config → Enable
2. Open Safari → speedtest.net or fast.com
3. Note results manually
4. Compare across configs
For iPhone/iPad, manual testing of 3-4 priority configurations (eu-A, eu-B, eu-D, eu-H) is sufficient to validate the approach.
---
## 9. SNI and Reality Key Strategy <a name="sni-strategy"></a>
### 9.1 Key Architecture
Two distinct Reality keypairs:
```
Keypair 1 (existing): juris link
Private: KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY (on juris server)
Public: 58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw (on local server outbound)
Keypair 2 (NEW, generate): enduser link
Private: <generate with `xray x25519`> (on local server inbound)
Public: <distribute to mobile clients>
```
### 9.2 Short ID Strategy
Assign one Short ID per device (8 bytes hex = 16 hex chars). This allows:
- Per-device identification in logs (by shortId)
- Selective revocation (invalidate one device without affecting others)
- Per-device statistics
```
ipad-pro: <generate: openssl rand -hex 8>
pixel: <generate: openssl rand -hex 8>
iphone-promax: <generate: openssl rand -hex 8>
AlinaSt: <generate: openssl rand -hex 8>
```
### 9.3 Reality Dest Selection: Russian Domestic Servers
For the enduser→local server Reality inbound, test which dest gives best handshake latency:
```bash
# Test Reality dest candidates from local server
for dest in vk.com gosuslugi.ru yandex.ru mos.ru mail.ru ok.ru; do
echo -n "$dest: "
curl -s -o /dev/null -w "%{time_connect}ms TLS=%{time_appconnect}ms\n" \
--tls-max 1.3 --tlsv1.3 "https://$dest/"
done
```
Expected: all will respond well (Russian servers, low latency from Russian IP). Choose the one with lowest TLS handshake time as primary dest.
### 9.4 Multiple SNIs on Single Inbound
The Reality config supports multiple `serverNames`. This allows different devices to use different SNIs with the same inbound:
```json
"serverNames": ["vk.com", "gosuslugi.ru", "yandex.ru", "mail.ru"]
```
Each device config can use whichever SNI works best for their cellular operator.
---
## 10. Advanced Improvements for Cellular <a name="advanced"></a>
### 10.1 Technique: Operator-Specific SNI
Different Russian cellular operators have slightly different whitelist implementations:
| Operator | Confirmed whitelisted SNIs | Notes |
|----------|--------------------------|-------|
| MTS | vk.com, yandex.ru, gosuslugi.ru, mail.ru | Standard whitelist |
| MegaFon | vk.com, yandex.ru, gosuslugi.ru | Similar to MTS |
| Beeline | vk.com, yandex.ru, gosuslugi.ru, ok.ru | Also okko.tv, IVI |
| Tele2 | vk.com, yandex.ru, gosuslugi.ru | More permissive in general |
**Action**: Distribute operator-specific configs to each user. For Beeline users, test `ok.ru` SNI. For all operators, `gosuslugi.ru` is the most universally protected.
### 10.2 Technique: XHTTP Packet-Up Mode for Mobile
Standard `auto` mode uses `stream-up` (single long POST) when possible. For cellular with unreliable connections, `packet-up` may be more resilient:
```json
"xhttpSettings": {
"mode": "packet-up",
"extra": {
"xPaddingBytes": "100-500"
}
}
```
`packet-up` splits each data chunk into separate HTTP POST requests. Benefits for mobile:
- Each request is smaller and completes independently
- A dropped request only loses that chunk, not the entire connection
- More tolerant of cellular handoffs (e.g., switching cell towers)
- Better behavior on high-loss links
Downside: slightly higher overhead (more HTTP request headers per data unit).
**Test eu-A-packet-up** as a variant in the test matrix if eu-A shows connection drops.
### 10.3 Technique: mKCP with Russian Service Header Camouflage
mKCP (KCP transport) tunnels data as UDP packets disguised as specific service headers (wechat-video, dns, etc.). While less common than XHTTP, it provides:
- UDP transport (different detection surface)
- Built-in fake header types
Not recommended as primary (UDP CGNAT issues) but valuable as a fallback diversity option.
### 10.4 Technique: CDN Fronting via Russian CDN
If direct IP connections become problematic (even for domestic traffic), use a Russian CDN:
**Russian CDN options** (all with Russian PoPs, never blocked):
- **VK Cloud CDN** — VK's own CDN, deeply integrated with Russian infrastructure
- **Selectel CDN** — Russian hosting/CDN provider
- **Gcore Russia** — International CDN with Russian PoP
This mirrors the Cloudflare approach but with Russian-based CDN whose IPs are definitively on the domestic whitelist.
Architecture:
```
Mobile Device → Russian CDN (WebSocket/gRPC) → Local Server (origin)
```
Requires: domain pointing to local server, CDN configured in proxy mode, WebSocket enabled on CDN.
### 10.5 Technique: Hysteria2 Keepalive for CGNAT
Hysteria2 over CGNAT drops connections after 30-60s of inactivity due to UDP state expiry in CGNAT tables. Fix:
```yaml
# Hysteria2 server config
quic:
initStreamReceiveWindow: 8388608
maxStreamReceiveWindow: 8388608
initConnReceiveWindow: 20971520
maxConnReceiveWindow: 20971520
# Client keepalive (in xray Hysteria2 outbound)
"hysteria2Settings": {
"password": "...",
"congestion": {
"type": "bbr" # or "brutal" for aggressive throughput
}
}
```
Termux xray client keepalive:
```json
"sockopt": {
"tcpKeepAliveIdle": 15,
"tcpKeepAliveInterval": 10
}
```
For UDP CGNAT: set Hysteria2 keepalive interval < 30s (CGNAT table timeout). Use `idleTimeout: 20s` on server.
### 10.6 Technique: DNS-over-HTTPS for Mobile Devices
Russian cellular operators intercept plain DNS. Configure xray on mobile devices to use DoH:
```json
"dns": {
"servers": [
{
"address": "https://dns.yandex.ru/dns-query",
"domains": ["geosite:category-ru"]
},
{
"address": "https://1.1.1.1/dns-query",
"domains": ["geosite:geolocation-!cn"]
},
"localhost"
],
"queryStrategy": "UseIPv4"
}
```
**Why Yandex DoH for Russian domains**: Using `dns.yandex.ru` for Russian domains keeps Russian-domain DNS queries within Russia (lower latency, avoids censored NXDOMAIN responses that some operators inject).
### 10.7 Technique: XHTTP over HTTP/3 (QUIC)
Xray v26+ supports XHTTP in HTTP/3 mode (QUIC transport). This combines:
- XHTTP's upload splitting
- QUIC's UDP transport (different detection surface)
- HTTP/3's built-in multiplexing
Enable on server:
```json
"xhttpSettings": {
"mode": "auto",
"h3": true
}
```
This is experimental but provides a third detection surface (in addition to TCP-based XHTTP and UDP-based Hysteria2).
### 10.8 Technique: Reality Dest Rotation
For additional resilience, the local server inbound can list multiple dests and rotate. While xray only supports one dest per inbound, x-ui can be programmed to rotate the dest periodically:
```python
# Rotate Reality dest weekly via cron
dests = ["vk.com:443", "gosuslugi.ru:443", "yandex.ru:443"]
# Pick based on current week number
dest = dests[datetime.now().isocalendar().week % len(dests)]
```
This means even if one dest is somehow used to fingerprint Reality traffic, the rotation changes the signature regularly.
### 10.9 Summary: Priority Ranking for Cellular Bypass
Based on the Russia cellular DPI analysis, ranked by expected effectiveness:
| Rank | Technique | Reason |
|------|-----------|--------|
| 1 | XHTTP+Reality+Russian SNI (vk.com/gosuslugi.ru) | Domestic IP + whitelisted SNI + XHTTP splitting = triple advantage |
| 2 | XHTTP+Reality+Government SNI (gosuslugi.ru) | Government domains are legally protected from blocking |
| 3 | WebSocket+TLS via Russian CDN | Hides actual server IP, CDN IP always whitelisted |
| 4 | gRPC+Reality+Russian SNI | H2 pattern, good stealth |
| 5 | Hysteria2 (QUIC/UDP) | Different detection surface, but CGNAT complications |
| 6 | TCP+Reality+Vision+Russian SNI | Simpler, may face volume-based issues on some cellular |
| 7 | XHTTP+Reality+Fragmentation | Added complexity, marginal benefit if XHTTP already works |
---
## 11. Implementation Order <a name="implementation"></a>
### Phase 1: Local Server Setup (Day 1)
- [ ] Generate Reality keypair for enduser link (`xray x25519`)
- [ ] Generate 4 Short IDs (one per device)
- [ ] Test Reality dest candidates (vk.com, gosuslugi.ru, yandex.ru) from local server
- [ ] Add enduser VLESS+XHTTP+Reality inbound via x-ui API (port 443, SNI=vk.com)
- [ ] Add routing rule: enduser inbound → juris-xhttp
- [ ] Verify firewall: port 443 open on local server
- [ ] Test from local network: `curl -s --socks5 <local proxy port> https://api.ipify.org`
### Phase 2: Android Termux Setup (Day 1-2)
- [ ] Install Termux from F-Droid on Android test device
- [ ] Install openssh, curl, xray in Termux
- [ ] Set up SSH key pair (local server → Android)
- [ ] Configure SSH reverse tunnel (autossh)
- [ ] Verify: local server can SSH into Android via reverse tunnel
- [ ] Push test xray config (eu-A) to Android, start xray in Termux
- [ ] Verify: local server can run curl through Android's SOCKS proxy
### Phase 3: Android Test Execution (Day 2-3)
- [ ] Write test configs eu-A through eu-I
- [ ] Write `test_enduser_configs.sh` automation script
- [ ] Run all 9 configurations on Android (cellular)
- [ ] Collect results: latency, P95, download, upload, jitter
- [ ] Document results in `enduser_test_results.md`
### Phase 4: iOS/iPad (Day 3-4)
- [ ] Install Shadowrocket or Streisand on iPhone and iPad
- [ ] Configure eu-A (primary) and eu-B (government SNI)
- [ ] Manual speed test via fast.com or speedtest.net
- [ ] Document results
### Phase 5: Apply Best Configurations (Day 4-5)
- [ ] Select winner(s) from test matrix
- [ ] Update VLESS share links / QR codes for all 4 clients
- [ ] Distribute to end users
- [ ] Update MEMORY.md with results and final configuration
---
## 12. Success Metrics <a name="metrics"></a>
### Connectivity (minimum bar)
- Exit IP shows juris server's exit (Latvia/European IP) — confirms full chain works
- Zero packet loss on 10 ICMP samples
### Performance Targets (based on juris link benchmark as reference)
| Metric | Target | Acceptable | Fail |
|--------|--------|-----------|------|
| Avg latency | < 200ms | < 350ms | > 500ms |
| P95 latency | < 350ms | < 600ms | > 1000ms |
| Download | > 15 Mbps | > 8 Mbps | < 3 Mbps |
| Upload | > 2 Mbps | > 1 Mbps | < 0.5 Mbps |
| Jitter | < 20ms | < 50ms | > 100ms |
### Resilience Targets
- Connection stable for 10+ minutes without drops (streaming video proxy test)
- Reconnection after cellular handoff < 5 seconds
- No connection drops during a 5 MB file transfer
### Comparison Points
- Baseline: direct HTTPS from device to internet (no proxy) — establishes cellular link capacity
- Reference: local server → juris link (142ms avg, 225ms P95) — shows how much the extra hop adds
- Target: enduser → local → juris chain adds ≤ 80ms average vs reference
---
## Appendix A: VLESS URI Templates
### Android (vk.com SNI, android fingerprint):
```
vless://<UUID>@95.165.85.65:443?type=xhttp&security=reality&path=%2Feu-<SHORT-ID>&host=vk.com&fp=android&pbk=<ENDUSER-PUBLIC-KEY>&sid=<DEVICE-SHORT-ID>&sni=vk.com&mode=auto#Enduser-Android
```
### iOS/iPad (gosuslugi.ru SNI, ios fingerprint):
```
vless://<UUID>@95.165.85.65:443?type=xhttp&security=reality&path=%2Feu-<SHORT-ID>&host=gosuslugi.ru&fp=ios&pbk=<ENDUSER-PUBLIC-KEY>&sid=<DEVICE-SHORT-ID>&sni=gosuslugi.ru&mode=auto#Enduser-iOS
```
---
## Appendix B: Files to Create
| File | Purpose |
|------|---------|
| `enduser_configs/eu-A.json` | XHTTP+Reality+vk.com baseline |
| `enduser_configs/eu-B.json` | XHTTP+Reality+gosuslugi.ru |
| `enduser_configs/eu-C.json` | XHTTP+Reality+yandex.ru |
| `enduser_configs/eu-D.json` | TCP+Reality+Vision+vk.com |
| `enduser_configs/eu-E.json` | XHTTP+Reality+vk.com port 8443 |
| `enduser_configs/eu-F.json` | gRPC+Reality+vk.com |
| `enduser_configs/eu-G.json` | XHTTP+Reality+vk.com+fragment chain |
| `enduser_configs/eu-H.json` | WebSocket+TLS (nginx) |
| `enduser_configs/eu-I.json` | Hysteria2 UDP |
| `test_enduser_configs.sh` | Automated test runner |
| `enduser_test_results.md` | Test results (filled after testing) |
---
## Appendix C: Key Differences From Juris Link Plan
| Dimension | juris Link (hop 2) | enduser Link (hop 1, this plan) |
|-----------|-------------------|---------------------------------|
| Destination IP | Foreign (Latvia, Hetzner) | Domestic (Russia, 95.165.x.x) |
| SNI | www.delfi.lv (Latvian) | vk.com / gosuslugi.ru (Russian) |
| CIDR blocking risk | High | None |
| Volume-based TCP freezing risk | High | Low-medium |
| Port 443 scrutiny | Very high (foreign) | Medium (domestic) |
| xmux concurrency | 16-32 | 8-16 (mobile) |
| Fingerprint | chrome | android / ios (device-matched) |
| Connection stability | Server-to-server | Mobile (CGNAT, handoffs) |
| Main DPI concern | Protocol fingerprint + volume | Protocol fingerprint + port |
| Key advantage | XHTTP splitting vs TCP freeze | Russian SNI + XHTTP = best camouflage |