40 KiB
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
- Architecture Overview
- Context Verification
- Russia Cellular DPI — In-Depth Analysis
- Local Server: New Inbound Configuration
- Client Configuration by Device Type
- Android Remote Testing Setup
- Test Matrix: 9 Configurations
- Test Automation Plan
- SNI and Reality Key Strategy
- Advanced Improvements for Cellular
- Implementation Order
- Success Metrics
1. Architecture Overview
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
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 externallyinbound-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-xhttpoutbound - Mobile client configurations with device-appropriate fingerprints
- Automated testing infrastructure for Android
3. Russia Cellular DPI — In-Depth Analysis
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:
- 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.
- 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.
- SNI-based analysis: The SNI presented in the TLS ClientHello is logged. Suspicious or absent SNIs may trigger deeper inspection even for domestic destinations.
- 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
-
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
-
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.
-
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
-
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
-
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::0to 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 serversgosuslugi.ru:443— fetches from state portal serversyandex.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
4.1 Prerequisites
-
Generate new Reality keypair (separate from juris link keypair):
/usr/local/x-ui/bin/xray-linux-amd64 x25519Save
privateKey(server) andpublicKey(clients). -
Generate new UUID per client (or reuse existing ones):
/usr/local/x-ui/bin/xray-linux-amd64 uuidExisting clients from
inbound-127.0.0.1:8445: ipad pro, pixel, iphone promax, AlinaSt — reuse these UUIDs. -
Generate new Short IDs (one per client or one shared):
openssl rand -hex 8 # repeat per shortId needed -
Verify port 443 availability on local server:
ss -tlnp | grep ':443' # should be empty if port is free sudo ufw allow 443/tcp # if ufw firewall is active -
Check local server's public IP is 95.165.85.65 and not behind NAT:
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):
{
"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:
{
"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
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
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
iosorsafarifingerprint — matches iOS TLS stack - iOS apps generate their own TLS fingerprint from the platform stack;
iosprofile 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
6.1 Problem Statement
End-user device testing requires:
- Configuring xray on the device (change protocol, SNI, port)
- Running test commands (curl, speed tests)
- Collecting metrics (latency, throughput)
- 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
-
Install Termux (from F-Droid, not Play Store — Play version is outdated)
-
In Termux:
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 -
Copy public key to Termux's authorized_keys:
# 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 -
Install v2rayNG on Android and configure it with the enduser inbound credentials.
SSH Reverse Tunnel (run from Android Termux)
# 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):
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
# 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:
# 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):
# 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)
- On Android: Settings → Developer Options → Wireless Debugging → Enable
- Tap "Pair device with code" — note IP:port and pairing code
- On local server:
adb pair <ip>:<port> <code> - Then:
adb connect <ip>:5555(or the shown port) - 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:
# 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
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.ruis 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:
"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: 10in 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
8.1 Test Script: test_enduser_configs.sh
To be executed on local server. Controls Android device via SSH reverse tunnel.
#!/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)
# 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:
- Manual app configuration: Configure proxy in Shadowrocket/Streisand, run browser speed test (fast.com, speedtest.net)
- iOS Shortcuts + curl (iOS 16+): Use Shortcuts app with curl via SOCKS proxy
- Network Extension API: Professional approach via custom iOS app — overkill for testing
- 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:
- Configure proxy app (Shadowrocket) with test config → Enable
- Open Safari → speedtest.net or fast.com
- Note results manually
- 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
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:
# 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:
"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
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:
"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:
# 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:
"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:
"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:
"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:
# 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
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.shautomation 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
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 |