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

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

  1. Architecture Overview
  2. Context Verification
  3. Russia Cellular DPI — In-Depth Analysis
  4. Local Server: New Inbound Configuration
  5. Client Configuration by Device Type
  6. Android Remote Testing Setup
  7. Test Matrix: 9 Configurations
  8. Test Automation Plan
  9. SNI and Reality Key Strategy
  10. Advanced Improvements for Cellular
  11. Implementation Order
  12. 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 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

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

4.1 Prerequisites

  1. Generate new Reality keypair (separate from juris link keypair):

    /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):

    /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):

    openssl rand -hex 8   # repeat per shortId needed
    
  4. 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
    
  5. 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 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

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

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:

    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:

    # 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)

# 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)

  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:

# 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
[ 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.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:
    "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

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:

  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

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.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

Connectivity (minimum bar)

  • Exit IP shows juris server's exit (Latvia/European IP) — confirms full chain works
  • Zero packet loss on 10 ICMP samples
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)

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