Add all project files, configs, scripts and results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvis
2026-03-08 07:09:56 +00:00
parent 803823c370
commit c1bcf3d85e
19 changed files with 4917 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
venv/

File diff suppressed because it is too large Load Diff

459
benchmark_improvements.sh Executable file
View File

@@ -0,0 +1,459 @@
#!/usr/bin/env bash
# benchmark_improvements.sh
# Tests each DPI-resistance improvement independently against baseline.
# Metrics: avg/P95 latency, jitter (std dev), download Mbps, upload Mbps.
set -euo pipefail
XRAY_BIN="/usr/local/x-ui/bin/xray-linux-amd64"
SOCKS_PORT=11083
REMOTE_IP="83.99.190.32"
VENV="/home/alvis/ai-xray/venv"
RESULTS_FILE="/home/alvis/ai-xray/improvement_results.md"
LATENCY_SAMPLES=20
# Current config values
PUBLIC_KEY="58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw"
PRIVATE_KEY="KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY"
UUID_B="6e422ab5-070a-43f6-8241-38cd56d23d24"
SID_B="6036d37d12c443c4"
XHTTP_PATH="/xt-6036d37d"
DEST="www.delfi.lv:443"
SNI="www.delfi.lv"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
hdr() { echo ""; echo -e "${BOLD}${CYAN}══ $1 ══${NC}"; }
pass() { echo -e " ${GREEN}${NC} $1"; }
fail() { echo -e " ${RED}${NC} $1"; }
info() { echo -e " ${YELLOW}${NC} $1"; }
XRAY_PID=""
cleanup() {
if [[ -n "$XRAY_PID" ]]; then
kill "$XRAY_PID" 2>/dev/null || true
wait "$XRAY_PID" 2>/dev/null || true
XRAY_PID=""
fi
}
trap 'cleanup; rm -f /tmp/bench-cfg.json' EXIT
# ── Remote inbound updater ─────────────────────────────────────────────────
remote_update() {
local stream_json="$1"
local uuid="$2"
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = $stream_json
client = {"id": "$uuid", "flow": "", "email": "bench",
"limitIp": 0, "totalGB": 0, "expiryTime": 0,
"enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {"id": 1, "tag": "inbound-443", "enable": True, "port": 443,
"listen": "", "protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}),
"remark": "inbound-443", "expiryTime": 0}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
ok = r.json().get("success")
print(f" Remote updated: {ok}")
PYEOF
}
restore_remote() {
remote_update '{
"network": "xhttp",
"security": "reality",
"realitySettings": {
"show": False,
"dest": "'"$DEST"'",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "'"$PRIVATE_KEY"'",
"shortIds": ["'"$SID_B"'", "48b4c16249ad44ff", ""]
},
"xhttpSettings": {
"path": "'"$XHTTP_PATH"'", "host": "",
"mode": "auto",
"extra": {"xPaddingBytes": "100-1000", "xmux": {
"maxConcurrency": "16-32", "maxConnections": 0,
"cMaxReuseTimes": "64-128", "cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900", "hMaxReusableSecs": "1800-3000"}}
}
}' "$UUID_B"
sleep 2
}
# ── Core benchmark runner ──────────────────────────────────────────────────
bench() {
local label="$1"
local cfg_file="$2"
# Validate
if ! "$XRAY_BIN" -test -c "$cfg_file" &>/dev/null; then
fail "Config invalid — skipping"
echo "$label|INVALID|—|—|—|—|—" >> /tmp/bench.tsv
return
fi
# Start xray
cleanup
"$XRAY_BIN" -c "$cfg_file" >/tmp/bench-xray.log 2>&1 &
XRAY_PID=$!
sleep 2
if ! kill -0 "$XRAY_PID" 2>/dev/null; then
fail "Xray failed to start"
echo "$label|FAIL|—|—|—|—|—" >> /tmp/bench.tsv
return
fi
# Connectivity check
local exit_ip
exit_ip=$(curl -s --socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 15 \
https://api.ipify.org 2>/dev/null || echo "FAIL")
if [[ "$exit_ip" != "$REMOTE_IP" ]]; then
fail "No connectivity (exit IP: $exit_ip)"
cleanup
echo "$label|NO-CONN|—|—|—|—|—" >> /tmp/bench.tsv
return
fi
pass "Connected via $exit_ip"
# Latency — N samples
info "Latency ($LATENCY_SAMPLES samples)..."
local lats=()
for i in $(seq 1 $LATENCY_SAMPLES); do
local ms
ms=$(curl -s -o /dev/null -w "%{time_total}" --socks5-hostname 127.0.0.1:$SOCKS_PORT \
--max-time 8 https://www.gstatic.com/generate_204 2>/dev/null \
| awk '{printf "%d", $1*1000}')
if [[ -n "$ms" && "$ms" -gt 0 ]]; then
lats+=("$ms")
fi
done
local n=${#lats[@]} avg=0 p95=0 jitter=0 min=0 max=0
if [[ $n -gt 0 ]]; then
local sorted=($(printf '%s\n' "${lats[@]}" | sort -n))
min=${sorted[0]}; max=${sorted[-1]}
local sum=0; for v in "${lats[@]}"; do sum=$((sum+v)); done
avg=$((sum/n))
local p95i=$((n*95/100)); [[ $p95i -ge $n ]] && p95i=$((n-1))
p95=${sorted[$p95i]}
# Jitter = std dev (via python for float math)
local csv_lats
csv_lats=$(printf '%s,' "${lats[@]}" | sed 's/,$//')
jitter=$(python3 -c "
import math
d=[$csv_lats]
m=sum(d)/len(d)
print(int(math.sqrt(sum((x-m)**2 for x in d)/len(d))))
")
fi
pass "Latency: avg=${avg}ms p95=${p95}ms jitter=${jitter}ms (n=$n)"
# Download 10MB
info "Download 10MB..."
local dl_out
dl_out=$(curl -s -o /dev/null -w "%{size_download} %{time_total}" \
--socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 30 \
"https://speed.cloudflare.com/__down?bytes=10485760" 2>/dev/null || echo "0 1")
local dl_bytes dl_time dl_mbps=0
dl_bytes=$(echo "$dl_out" | awk '{print $1}')
dl_time=$(echo "$dl_out" | awk '{print $2}')
if [[ "${dl_bytes:-0}" -gt 1000000 ]]; then
dl_mbps=$(echo "scale=1; $dl_bytes*8/$dl_time/1000000" | bc)
pass "Download: ${dl_mbps} Mbps"
else
fail "Download failed"
fi
# Upload 5MB
info "Upload 5MB..."
local ul_out
ul_out=$(dd if=/dev/urandom bs=1M count=5 2>/dev/null | \
curl -s -o /dev/null -w "%{size_upload} %{time_total}" \
--socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 30 \
-X POST --data-binary @- https://httpbin.org/post 2>/dev/null || echo "0 1")
local ul_bytes ul_time ul_mbps=0
ul_bytes=$(echo "$ul_out" | awk '{print $1}')
ul_time=$(echo "$ul_out" | awk '{print $2}')
if [[ "${ul_bytes:-0}" -gt 100000 ]]; then
ul_mbps=$(echo "scale=1; $ul_bytes*8/$ul_time/1000000" | bc)
pass "Upload: ${ul_mbps} Mbps"
else
fail "Upload failed"
ul_mbps=0
fi
echo "$label|OK|$avg|$p95|$jitter|$dl_mbps|$ul_mbps" >> /tmp/bench.tsv
cleanup
sleep 1
}
# ══════════════════════════════════════════════════════════════════════════
# CONFIGS
# ══════════════════════════════════════════════════════════════════════════
# Baseline config writer helper
write_xhttp_cfg() {
local fp="$1" use_frag="$2" host_hdr="$3" path="$4" sni_val="$5"
local frag_sockopt=""
[[ "$use_frag" == "yes" ]] && frag_sockopt='"sockopt": {"dialerProxy": "frag-chain1"},'
cat > /tmp/bench-cfg.json << EOF
{
"log": {"loglevel": "error"},
"inbounds": [{"listen":"127.0.0.1","port":$SOCKS_PORT,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],
"outbounds": [
{
"tag": "proxy", "protocol": "vless",
"settings": {"vnext": [{"address": "share.alogins.net", "port": 443,
"users": [{"id": "$UUID_B", "flow": "", "encryption": "none"}]}]},
"streamSettings": {
$frag_sockopt
"network": "xhttp",
"security": "reality",
"realitySettings": {
"fingerprint": "$fp",
"serverName": "$sni_val",
"publicKey": "$PUBLIC_KEY",
"shortId": "$SID_B",
"spiderX": "/"
},
"xhttpSettings": {
"path": "$path",
"host": "$host_hdr",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"xmux": {"maxConcurrency": "16-32", "maxConnections": 0,
"cMaxReuseTimes": "64-128", "cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900", "hMaxReusableSecs": "1800-3000"}
}
}
}
},
{
"tag": "frag-chain1", "protocol": "freedom",
"settings": {"fragment": {"packets": "tlshello", "length": "100-200", "interval": "10-20"}},
"streamSettings": {"sockopt": {"dialerProxy": "frag-chain2"}}
},
{
"tag": "frag-chain2", "protocol": "freedom",
"settings": {"fragment": {"packets": "1-3", "length": "1-5", "interval": "1-2"}}
},
{"tag": "direct", "protocol": "freedom"}
]
}
EOF
}
# ══════════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════════
echo "label|status|avg_ms|p95_ms|jitter_ms|dl_mbps|ul_mbps" > /tmp/bench.tsv
# Ensure baseline remote config is active
info "Ensuring baseline remote config..."
restore_remote
# ── 0. BASELINE ────────────────────────────────────────────────────────────
hdr "0. BASELINE (current config)"
write_xhttp_cfg "chrome" "no" "" "$XHTTP_PATH" "$SNI"
bench "0-baseline" /tmp/bench-cfg.json
# ── 1. fingerprint=randomized ──────────────────────────────────────────────
hdr "1. fingerprint: randomized"
write_xhttp_cfg "randomized" "no" "" "$XHTTP_PATH" "$SNI"
bench "1-fp-randomized" /tmp/bench-cfg.json
# ── 2. fingerprint=firefox ────────────────────────────────────────────────
hdr "2. fingerprint: firefox"
write_xhttp_cfg "firefox" "no" "" "$XHTTP_PATH" "$SNI"
bench "2-fp-firefox" /tmp/bench-cfg.json
# ── 3. + TLS ClientHello fragment chain ───────────────────────────────────
hdr "3. fragment chain on TLS ClientHello"
write_xhttp_cfg "chrome" "yes" "" "$XHTTP_PATH" "$SNI"
bench "3-fragment-chain" /tmp/bench-cfg.json
# ── 4. + host header = SNI domain ─────────────────────────────────────────
hdr "4. host header = www.delfi.lv"
write_xhttp_cfg "chrome" "no" "www.delfi.lv" "$XHTTP_PATH" "$SNI"
bench "4-host-header" /tmp/bench-cfg.json
# ── 5. realistic XHTTP path + host header (needs server update) ───────────
hdr "5. realistic path /api/v2/stream + host header"
info "Updating remote path to /api/v2/stream ..."
remote_update '{
"network": "xhttp",
"security": "reality",
"realitySettings": {
"show": False,
"dest": "'"$DEST"'",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "'"$PRIVATE_KEY"'",
"shortIds": ["'"$SID_B"'", ""]
},
"xhttpSettings": {
"path": "/api/v2/stream", "host": "www.delfi.lv",
"mode": "auto",
"extra": {"xPaddingBytes": "100-1000", "xmux": {
"maxConcurrency": "16-32", "maxConnections": 0,
"cMaxReuseTimes": "64-128", "cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900", "hMaxReusableSecs": "1800-3000"}}
}
}' "$UUID_B"
sleep 2
write_xhttp_cfg "chrome" "no" "www.delfi.lv" "/api/v2/stream" "$SNI"
bench "5-realistic-path" /tmp/bench-cfg.json
restore_remote
# ── 6. SNI = e-klase.lv (highest throughput in SNI test) ──────────────────
hdr "6. SNI = e-klase.lv (top SNI from previous benchmark)"
write_xhttp_cfg "chrome" "no" "" "$XHTTP_PATH" "e-klase.lv"
bench "6-sni-eklase" /tmp/bench-cfg.json
# ── 7. SNI = www.lmt.lv ───────────────────────────────────────────────────
hdr "7. SNI = www.lmt.lv"
write_xhttp_cfg "chrome" "no" "" "$XHTTP_PATH" "www.lmt.lv"
bench "7-sni-lmt" /tmp/bench-cfg.json
# ── 8. BBR check + enable on remote ───────────────────────────────────────
hdr "8. BBR congestion control on remote server"
source "$VENV/bin/activate"
BBR_STATUS=$(python3 << 'PYEOF'
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("83.99.190.32", username="juris", password="VitaIeva2A.")
_, out, _ = ssh.exec_command("lxc exec xray -- sysctl net.ipv4.tcp_congestion_control 2>/dev/null")
cc = out.read().decode().strip()
_, out2, _ = ssh.exec_command("lxc exec xray -- sysctl net.core.default_qdisc 2>/dev/null")
qd = out2.read().decode().strip()
print(f"cc={cc} | qd={qd}")
ssh.close()
PYEOF
)
info "Current: $BBR_STATUS"
if echo "$BBR_STATUS" | grep -q "bbr"; then
info "BBR already enabled — testing as-is"
write_xhttp_cfg "chrome" "no" "" "$XHTTP_PATH" "$SNI"
bench "8-bbr-already-on" /tmp/bench-cfg.json
else
info "Enabling BBR on remote container..."
python3 << 'PYEOF'
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("83.99.190.32", username="juris", password="VitaIeva2A.")
for cmd in [
"lxc exec xray -- sysctl -w net.core.default_qdisc=fq",
"lxc exec xray -- sysctl -w net.ipv4.tcp_congestion_control=bbr",
"lxc exec xray -- sysctl -w net.core.rmem_max=16777216",
"lxc exec xray -- sysctl -w net.core.wmem_max=16777216",
]:
_, out, err = ssh.exec_command(cmd)
print(f" {cmd.split('--')[1].strip()}: {(out.read()+err.read()).decode().strip()}")
ssh.close()
PYEOF
sleep 1
write_xhttp_cfg "chrome" "no" "" "$XHTTP_PATH" "$SNI"
bench "8-bbr-enabled" /tmp/bench-cfg.json
fi
# ── Restore remote to canonical state ─────────────────────────────────────
hdr "Restoring remote to baseline"
restore_remote
pass "Remote restored"
# ══════════════════════════════════════════════════════════════════════════
# SUMMARY
# ══════════════════════════════════════════════════════════════════════════
hdr "RESULTS SUMMARY"
echo ""
printf "%-32s %-7s %-7s %-9s %-9s %-9s\n" "Test" "Avg ms" "P95 ms" "Jitter ms" "DL Mbps" "UL Mbps"
printf "%-32s %-7s %-7s %-9s %-9s %-9s\n" "────────────────────────────────" "───────" "───────" "─────────" "───────" "───────"
BASELINE_AVG=0; BASELINE_P95=0; BASELINE_JIT=0; BASELINE_DL=0; BASELINE_UL=0
while IFS='|' read -r label status avg p95 jit dl ul; do
[[ "$label" == "label" ]] && continue
if [[ "$status" == "OK" ]]; then
if [[ "$label" == "0-baseline" ]]; then
BASELINE_AVG=$avg; BASELINE_P95=$p95; BASELINE_JIT=$jit
BASELINE_DL=$dl; BASELINE_UL=$ul
printf "${BOLD}%-32s${NC} %-7s %-7s %-9s %-9s %-9s\n" \
"$label" "${avg}ms" "${p95}ms" "${jit}ms" "${dl}" "${ul}"
else
# Delta indicators
local_delta_avg="" local_delta_jit="" local_delta_dl=""
[[ -n "$BASELINE_AVG" && "$BASELINE_AVG" -gt 0 ]] && {
diff=$((avg - BASELINE_AVG))
[[ $diff -lt 0 ]] && local_delta_avg="${GREEN}${diff}${NC}" || local_delta_avg="${RED}+${diff}${NC}"
}
printf "%-32s %-7s %-7s %-9s %-9s %-9s\n" \
"$label" "${avg}ms" "${p95}ms" "${jit}ms" "${dl}" "${ul}"
fi
else
printf "${RED}%-32s${NC} %s\n" "$label" "$status"
fi
done < /tmp/bench.tsv
# Write markdown
{
cat << MDEOF
# DPI Resistance Improvement Benchmark
**Date**: $(date '+%Y-%m-%d %H:%M')
**Baseline**: VLESS+XHTTP+Reality, fingerprint=chrome, SNI=www.delfi.lv, path=/xt-6036d37d
**Latency samples per test**: $LATENCY_SAMPLES
**Jitter**: standard deviation of latency samples
## Results
| Test | Avg ms | P95 ms | Jitter ms | DL Mbps | UL Mbps | Notes |
|------|--------|--------|-----------|---------|---------|-------|
MDEOF
while IFS='|' read -r label status avg p95 jit dl ul; do
[[ "$label" == "label" ]] && continue
notes=""
case "$label" in
0-baseline) notes="Current active config" ;;
1-fp-randomized) notes="uTLS fingerprint rotated per connection" ;;
2-fp-firefox) notes="Firefox uTLS profile" ;;
3-fragment-chain) notes="TLS ClientHello split 100-200B + micro-frag 1-5B" ;;
4-host-header) notes="HTTP Host header = www.delfi.lv" ;;
5-realistic-path) notes="Path=/api/v2/stream + Host header" ;;
6-sni-eklase) notes="SNI switched to e-klase.lv" ;;
7-sni-lmt) notes="SNI switched to www.lmt.lv" ;;
8-bbr*) notes="BBR congestion control on remote" ;;
esac
if [[ "$status" == "OK" ]]; then
echo "| $label | ${avg}ms | ${p95}ms | ${jit}ms | ${dl} | ${ul} | $notes |"
else
echo "| $label | — | — | — | — | — | $status |"
fi
done < /tmp/bench.tsv
cat << 'MDEOF'
## What Each Test Changes
- **fingerprint=randomized**: uTLS fingerprint rotated per connection — defeats fingerprint-based blocking
- **fingerprint=firefox**: Firefox uTLS profile instead of Chrome
- **fragment chain**: TLS ClientHello split into 100-200B chunks, then micro-fragmented 1-5B + noise — defeats handshake DPI
- **host header**: Sets HTTP `Host:` header to match SNI — makes request look more legitimate
- **realistic path**: Changes XHTTP path from synthetic to `/api/v2/stream` with matching host header
- **SNI e-klase.lv / lmt.lv**: Alternative SNIs from previous benchmark (dest stays www.delfi.lv)
- **BBR**: Linux BBR congestion control + larger TCP buffers on remote — improves throughput under loss
MDEOF
} > "$RESULTS_FILE"
echo ""
echo -e "${BOLD}Results: $RESULTS_FILE${NC}"

44
config_test_results.md Normal file
View File

@@ -0,0 +1,44 @@
# Xray Configuration Comparison — Port 443
**Date**: 2026-02-20 15:49:15
**Local xray**: 25.10.15 | **Remote xray**: 26.2.6
**Remote**: share.alogins.net (83.99.190.32), LXD container "xray"
## Results
| Configuration | Status | Avg Latency | P95 Latency | Download | Upload |
|---------------|--------|-------------|-------------|----------|--------|
| A: TCP+Reality+Vision (baseline) | ✓ OK | 301ms | 1225ms | 26.78 Mbps | 2.99 Mbps |
| B: XHTTP+Reality | ✓ OK | 142ms | 225ms | 27.54 Mbps | 3.44 Mbps |
| C: gRPC+Reality | ✓ OK | 144ms | 250ms | 18.83 Mbps | 3.19 Mbps |
| D: TCP+Reality+Vision+Fragment+Noise | ✓ OK | 393ms | 452ms | 36.10 Mbps | 3.18 Mbps |
## Configuration Descriptions
| Config | Transport | Port | Flow | DPI Target |
|--------|-----------|------|------|------------|
| A | TCP + Reality | 443 | xtls-rprx-vision | Baseline — standard Russia anti-DPI |
| B | XHTTP + Reality | 443 | none | Volume-based TCP freezing (split uploads, XMUX padding) |
| C | gRPC + Reality | 443 | none | H2 pattern analysis (looks like enterprise API traffic) |
| D | TCP + Reality + Fragment/Noise | 443 | xtls-rprx-vision | TLS ClientHello DPI signature (fragment chains + noise) |
## Winner: B — XHTTP + Reality
**XHTTP is now the active configuration** (remote inbound + local outbound `juris-xhttp`).
- Best average latency: **142ms** (vs 301ms for baseline TCP+Vision)
- Best P95 latency: **225ms** (vs 1225ms for baseline — 5× improvement)
- Best upload: **3.44 Mbps**
- Download comparable to baseline: **27.54 Mbps**
Config A (`juris-reality`, TCP+Vision) remains as fallback outbound on local server.
### Why XHTTP beats TCP+Vision here
TCP+Vision (baseline) showed a severe P95 spike (1225ms) — likely caused by the volume-based TCP freezing that TSPU applies. XHTTP splits uploads into multiple small HTTP POST requests, preventing the single-connection volume trigger. XMUX multiplexing also reduces per-request overhead.
## Methodology
- Each config tested sequentially on the **same port 443**
- Remote inbound swapped on-the-fly via 3x-ui API
- 10 latency samples to google.com
- 10 MB download from Cloudflare speed test
- 5 MB upload to httpbin.org

37
configs/e-klase.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "e-klase.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "share.alogins.net",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

37
configs/www.delfi.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.delfi.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

37
configs/www.inbox.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.inbox.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

37
configs/www.lmt.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.lmt.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

37
configs/www.lsm.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.lsm.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.microsoft.com",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

37
configs/www.tele2.lv.json Normal file
View File

@@ -0,0 +1,37 @@
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.tele2.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}

40
improvement_results.md Normal file
View File

@@ -0,0 +1,40 @@
# DPI Resistance Improvement Benchmark
**Date**: 2026-02-20 16:26
**Baseline**: VLESS+XHTTP+Reality, fingerprint=chrome, SNI=www.delfi.lv, path=/xt-6036d37d
**Latency samples per test**: 20
**Jitter**: standard deviation of latency samples
## Results
| Test | Avg ms | P95 ms | Jitter ms | DL Mbps | UL Mbps | Notes |
|------|--------|--------|-----------|---------|---------|-------|
| 0-baseline | 76ms | 95ms | 5ms | 38.7 | 4.0 | Current active config |
| 1-fp-randomized | 75ms | 89ms | 3ms | 33.5 | 3.6 | uTLS fingerprint rotated per connection |
| 2-fp-firefox | 73ms | 76ms | 0ms | 24.5 | 2.8 | Firefox uTLS profile |
| 3-fragment-chain | 75ms | 79ms | 2ms | 30.4 | 3.2 | TLS ClientHello split 100-200B + micro-frag 1-5B |
| 4-host-header | 74ms | 77ms | 1ms | 27.6 | 3.8 | HTTP Host header = www.delfi.lv |
| 5-realistic-path | 74ms | 87ms | 3ms | 31.2 | 3.8 | Path=/api/v2/stream + Host header |
| 6-sni-eklase | 73ms | 78ms | 1ms | 30.7 | 2.3 | SNI switched to e-klase.lv |
| 7-sni-lmt | 75ms | 80ms | 1ms | 26.3 | 4.0 | SNI switched to www.lmt.lv |
| 8-bbr-enabled | 74ms | 82ms | 2ms | 31.3 | 3.4 | BBR congestion control on remote |
## Applied Improvements
After benchmarking, the following were applied permanently to the local x-ui config:
- `juris-xhttp` (chrome): host header `www.delfi.lv` — primary outbound
- `juris-xhttp-firefox` (firefox): host header `www.delfi.lv` — low-jitter alternate
- `juris-xhttp-safari` (safari): host header `www.delfi.lv` — fingerprint diversity
> **Note**: `fingerprint=randomized` was NOT applied. Reality's anti-probing rejects random TLS
> fingerprints (connection reset by peer). Only named browser profiles are accepted.
## What Each Test Changes
- **fingerprint=randomized**: uTLS fingerprint rotated per connection — defeats fingerprint-based blocking
⚠️ **Incompatible with Reality** — triggers anti-probing protection, connection reset
- **fingerprint=firefox**: Firefox uTLS profile instead of Chrome
- **fragment chain**: TLS ClientHello split into 100-200B chunks, then micro-fragmented 1-5B + noise — defeats handshake DPI
- **host header**: Sets HTTP `Host:` header to match SNI — makes request look more legitimate
- **realistic path**: Changes XHTTP path from synthetic to `/api/v2/stream` with matching host header
- **SNI e-klase.lv / lmt.lv**: Alternative SNIs from previous benchmark (dest stays www.delfi.lv)
- **BBR**: Linux BBR congestion control + larger TCP buffers on remote — improves throughput under loss

1045
plan_enduser_.md Normal file

File diff suppressed because it is too large Load Diff

100
sni_test_results.md Normal file
View File

@@ -0,0 +1,100 @@
# SNI Configuration Test Results
Date: 2026-02-20 04:47 UTC
Server: share.alogins.net (83.99.190.32)
Protocol: VLESS + XTLS-Reality + Vision
Reality dest: www.delfi.lv:443
## Results
| # | SNI | Type | Conn | Latency Min | Latency Avg | Latency P95 | Latency Max | Download | Upload |
|---|-----|------|------|-------------|-------------|-------------|-------------|----------|--------|
| 1 | `www.delfi.lv` | Latvia news #1 | OK | 120ms | 121ms | 124ms | 124ms | 44.79 Mbps | 33.90 Mbps |
| 2 | `www.lmt.lv` | Latvia telecom #1 | OK | 130ms | 133ms | 136ms | 136ms | 50.58 Mbps | 37.33 Mbps |
| 3 | `www.tele2.lv` | Latvia telecom #2 | FAIL | — | — | — | — | — | — |
| 4 | `www.lsm.lv` | Latvia public broadcasting | FAIL | — | — | — | — | — | — |
| 5 | `www.inbox.lv` | Latvia webmail | OK | 120ms | 121ms | 124ms | 124ms | 45.48 Mbps | 32.98 Mbps |
| 6 | `e-klase.lv` | Latvia education | OK | 119ms | 121ms | 123ms | 123ms | 54.84 Mbps | 29.58 Mbps |
| 7 | `share.alogins.net` | Custom domain | FAIL | — | — | — | — | — | — |
| 8 | `www.microsoft.com` | Worldwide baseline | FAIL | — | — | — | — | — | — |
## Why Some SNIs Failed
SNIs #3, #4, #7, #8 failed because the Reality `dest` is `www.delfi.lv:443`. When a probe or handshake arrives with a mismatched SNI (e.g. `www.microsoft.com`), the dest server (delfi.lv) rejects the TLS ClientHello. Only SNIs whose TLS stacks are compatible with the dest server work. This is a Reality protocol constraint: **the `dest` domain determines which SNIs are viable**.
The 4 working SNIs (`www.delfi.lv`, `www.lmt.lv`, `www.inbox.lv`, `e-klase.lv`) are all hosted behind CDN infrastructure compatible with delfi.lv's TLS responses.
To use other SNIs (e.g. `www.microsoft.com`), change `dest` to match (e.g. `www.microsoft.com:443`).
## Best Configuration
**Winner: `www.delfi.lv`** (Latvia news #1)
- Average latency: 121ms
- Download: 44.79 Mbps
- Upload: 33.90 Mbps
### Recommended client outbound config
```json
{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.delfi.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}
```
## All Configurations
Individual config files are stored in `configs/` directory.
- `configs/www.delfi.lv.json` — SNI: `www.delfi.lv`
- `configs/www.lmt.lv.json` — SNI: `www.lmt.lv`
- `configs/www.tele2.lv.json` — SNI: `www.tele2.lv`
- `configs/www.lsm.lv.json` — SNI: `www.lsm.lv`
- `configs/www.inbox.lv.json` — SNI: `www.inbox.lv`
- `configs/e-klase.lv.json` — SNI: `e-klase.lv`
- `configs/share.alogins.net.json` — SNI: `share.alogins.net`
- `configs/www.microsoft.com.json` — SNI: `www.microsoft.com`
## Server-Side Reality Settings
```json
"realitySettings": {
"dest": "www.delfi.lv:443",
"serverNames": [
"www.delfi.lv",
"www.lmt.lv",
"www.tele2.lv",
"www.lsm.lv",
"www.inbox.lv",
"e-klase.lv",
"share.alogins.net",
"www.microsoft.com"
],
"privateKey": "KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY",
"shortIds": ["48b4c16249ad44ff", ""]
}
```

525
test_all_configs.sh Executable file
View File

@@ -0,0 +1,525 @@
#!/usr/bin/env bash
# test_all_configs.sh — Sequential test of 4 xray configurations on port 443
# For each config: swap remote inbound, test locally, restore, next.
#
# Configs:
# A: VLESS + TCP + Reality + Vision (baseline)
# B: VLESS + XHTTP + Reality (split uploads, anti-freeze)
# C: VLESS + gRPC + Reality (H2 enterprise traffic)
# D: VLESS + TCP + Reality + Vision + Fragment+Noise (TLS handshake obfuscation)
set -euo pipefail
XRAY_BIN="/usr/local/x-ui/bin/xray-linux-amd64"
SOCKS_PORT=11082
REMOTE_IP="83.99.190.32"
RESULTS_FILE="/home/alvis/ai-xray/config_test_results.md"
VENV="/home/alvis/ai-xray/venv"
# Shared keys
PUBLIC_KEY="58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw"
PRIVATE_KEY="KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY"
DEST="www.delfi.lv:443"
SERVER_NAMES='["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"]'
# Config A
UUID_A="64522a14-54aa-4b3c-8071-8c8b17aa1f08"
SID_A="48b4c16249ad44ff"
# Config B (XHTTP)
UUID_B="6e422ab5-070a-43f6-8241-38cd56d23d24"
SID_B="6036d37d12c443c4"
XHTTP_PATH="/xt-6036d37d"
# Config C (gRPC)
UUID_C="d0dd1e83-43d8-4bf8-a2b0-005362076b7b"
SID_C="52dfa6856de91d0f"
GRPC_SVC="grpc-52dfa685"
# Config D (Fragment) — uses Config A server-side
UUID_D="fdc4fbc1-d3c0-43e9-917f-4026ba6d4f7c"
SID_D="90abc7d195f7341d"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
header() { echo ""; echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"; echo -e "${BOLD}${CYAN} $1${NC}"; echo -e "${BOLD}${CYAN}══════════════════════════════════════════${NC}"; }
pass() { echo -e " ${GREEN}${NC} $1"; }
fail() { echo -e " ${RED}${NC} $1"; }
info() { echo -e " ${YELLOW}${NC} $1"; }
XRAY_PID=""
cleanup_xray() {
if [[ -n "$XRAY_PID" ]]; then
kill "$XRAY_PID" 2>/dev/null || true
wait "$XRAY_PID" 2>/dev/null || true
XRAY_PID=""
fi
}
trap 'cleanup_xray; rm -f /tmp/xray-test-cfg.json' EXIT
# ── Remote inbound management ─────────────────────────────────────────────
remote_update_inbound() {
# $1 = inbound JSON string (the streamSettings portion as Python dict repr)
local stream_json="$1"
local uuid="$2"
local flow="$3"
local sid="$4"
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = $stream_json
client = {"id": "$uuid", "flow": "$flow", "email": "test-client",
"limitIp": 0, "totalGB": 0, "expiryTime": 0,
"enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {
"id": 1,
"tag": "inbound-443",
"enable": True,
"port": 443,
"listen": "",
"protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}),
"remark": "inbound-443",
"expiryTime": 0
}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
print(r.json().get("success"), r.json().get("msg",""))
PYEOF
}
restore_baseline() {
info "Restoring baseline (TCP+Vision) on remote..."
remote_update_inbound '{
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": False,
"dest": "www.delfi.lv:443",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "'"$PRIVATE_KEY"'",
"shortIds": ["'"$SID_A"'", ""]
},
"tcpSettings": {"header": {"type": "none"}}
}' "$UUID_A" "xtls-rprx-vision" "$SID_A"
}
# ── Local standalone test config writers ──────────────────────────────────
write_cfg_A() { cat > /tmp/xray-test-cfg.json << EOF
{
"log": {"loglevel": "error"},
"inbounds": [{"listen":"127.0.0.1","port":$SOCKS_PORT,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],
"outbounds": [
{"tag":"proxy","protocol":"vless",
"settings":{"vnext":[{"address":"share.alogins.net","port":443,"users":[{"id":"$UUID_A","flow":"xtls-rprx-vision","encryption":"none"}]}]},
"streamSettings":{
"network":"tcp","security":"reality",
"realitySettings":{"fingerprint":"chrome","serverName":"www.delfi.lv","publicKey":"$PUBLIC_KEY","shortId":"$SID_A","spiderX":"/"},
"tcpSettings":{"header":{"type":"none"}}
}
},
{"tag":"direct","protocol":"freedom"}
]
}
EOF
}
write_cfg_B() { cat > /tmp/xray-test-cfg.json << EOF
{
"log": {"loglevel": "error"},
"inbounds": [{"listen":"127.0.0.1","port":$SOCKS_PORT,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],
"outbounds": [
{"tag":"proxy","protocol":"vless",
"settings":{"vnext":[{"address":"share.alogins.net","port":443,"users":[{"id":"$UUID_B","flow":"","encryption":"none"}]}]},
"streamSettings":{
"network":"xhttp","security":"reality",
"realitySettings":{"fingerprint":"chrome","serverName":"www.delfi.lv","publicKey":"$PUBLIC_KEY","shortId":"$SID_B","spiderX":"/"},
"xhttpSettings":{"path":"$XHTTP_PATH","host":"","mode":"auto",
"extra":{"xPaddingBytes":"100-1000","xmux":{"maxConcurrency":"16-32","maxConnections":0,"cMaxReuseTimes":"64-128","cMaxLifetimeMs":0,"hMaxRequestTimes":"600-900","hMaxReusableSecs":"1800-3000"}}}
}
},
{"tag":"direct","protocol":"freedom"}
]
}
EOF
}
write_cfg_C() { cat > /tmp/xray-test-cfg.json << EOF
{
"log": {"loglevel": "error"},
"inbounds": [{"listen":"127.0.0.1","port":$SOCKS_PORT,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],
"outbounds": [
{"tag":"proxy","protocol":"vless",
"settings":{"vnext":[{"address":"share.alogins.net","port":443,"users":[{"id":"$UUID_C","flow":"","encryption":"none"}]}]},
"streamSettings":{
"network":"grpc","security":"reality",
"realitySettings":{"fingerprint":"chrome","serverName":"www.delfi.lv","publicKey":"$PUBLIC_KEY","shortId":"$SID_C","spiderX":"/"},
"grpcSettings":{"serviceName":"$GRPC_SVC","multiMode":true,"idle_timeout":60,"health_check_timeout":20,"initial_windows_size":65536}
}
},
{"tag":"direct","protocol":"freedom"}
]
}
EOF
}
write_cfg_D() { cat > /tmp/xray-test-cfg.json << EOF
{
"log": {"loglevel": "error"},
"inbounds": [{"listen":"127.0.0.1","port":$SOCKS_PORT,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],
"outbounds": [
{"tag":"proxy","protocol":"vless",
"settings":{"vnext":[{"address":"share.alogins.net","port":443,"users":[{"id":"$UUID_D","flow":"xtls-rprx-vision","encryption":"none"}]}]},
"streamSettings":{
"network":"tcp","security":"reality",
"realitySettings":{"fingerprint":"chrome","serverName":"www.delfi.lv","publicKey":"$PUBLIC_KEY","shortId":"$SID_D","spiderX":"/"},
"tcpSettings":{"header":{"type":"none"}},
"sockopt":{"dialerProxy":"frag-chain1"}
}
},
{"tag":"frag-chain1","protocol":"freedom",
"settings":{"fragment":{"packets":"tlshello","length":"100-200","interval":"10-20"}},
"streamSettings":{"sockopt":{"dialerProxy":"frag-chain2"}}
},
{"tag":"frag-chain2","protocol":"freedom",
"settings":{
"fragment":{"packets":"1-3","length":"1-5","interval":"1-2"},
"noises":[
{"type":"rand","packet":"50-150","delay":"10-16"},
{"type":"base64","packet":"7nQBAAABAAAAAAAABnQtcmluZwZtc2VkZ2UDbmV0AAABAAE=","delay":"10-16"}
]
}
},
{"tag":"direct","protocol":"freedom"}
]
}
EOF
}
# ── Core test runner ──────────────────────────────────────────────────────
run_test() {
local label="$1"
local write_fn="$2" # function name to write the config
local update_fn="$3" # function name to update remote (or empty)
header "$label"
# Update remote if needed
if [[ -n "$update_fn" ]]; then
info "Updating remote inbound..."
$update_fn
sleep 3 # give xray time to pick up the change
fi
# Write local test config
$write_fn
# Validate
if ! "$XRAY_BIN" -test -c /tmp/xray-test-cfg.json &>/dev/null; then
fail "Config validation FAILED"
echo "$label|INVALID|—|—|—|—|—|—" >> /tmp/results.tsv
return
fi
pass "Config valid"
# Start test xray
cleanup_xray
"$XRAY_BIN" -c /tmp/xray-test-cfg.json >/tmp/xray-test.log 2>&1 &
XRAY_PID=$!
sleep 2
if ! kill -0 "$XRAY_PID" 2>/dev/null; then
fail "Xray failed to start: $(tail -3 /tmp/xray-test.log)"
echo "$label|FAIL-START|—|—|—|—|—|—" >> /tmp/results.tsv
return
fi
pass "Xray started (PID $XRAY_PID)"
# Connectivity
info "Checking connectivity..."
local exit_ip
exit_ip=$(curl -s --socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 15 https://api.ipify.org 2>/dev/null || echo "FAIL")
if [[ "$exit_ip" != "$REMOTE_IP" ]]; then
fail "Exit IP '$exit_ip' (expected $REMOTE_IP)"
cleanup_xray
echo "$label|FAIL-CONNECT|—|—|—|—|—|—" >> /tmp/results.tsv
return
fi
pass "Exit IP: $exit_ip"
# Latency — 10 samples
info "Measuring latency (10 samples)..."
local latencies=()
for i in $(seq 1 10); do
local ms
ms=$(curl -s -o /dev/null -w "%{time_total}" --socks5-hostname 127.0.0.1:$SOCKS_PORT \
--max-time 10 https://www.google.com 2>/dev/null | awk '{printf "%d", $1*1000}')
if [[ -n "$ms" && "$ms" -gt 0 ]]; then
latencies+=("$ms")
printf " [%2d/10] %s ms\n" "$i" "$ms"
else
printf " [%2d/10] TIMEOUT\n" "$i"
fi
done
local n=${#latencies[@]} avg_ms=0 min_ms=0 max_ms=0 p95_ms=0
if [[ $n -gt 0 ]]; then
local sorted=($(printf '%s\n' "${latencies[@]}" | sort -n))
min_ms=${sorted[0]}; max_ms=${sorted[-1]}
local sum=0; for v in "${latencies[@]}"; do sum=$((sum+v)); done
avg_ms=$((sum/n))
local p95_idx=$(( n * 95 / 100 )); [[ $p95_idx -ge $n ]] && p95_idx=$((n-1))
p95_ms=${sorted[$p95_idx]}
fi
pass "Latency ($n/10): min=${min_ms}ms avg=${avg_ms}ms p95=${p95_ms}ms max=${max_ms}ms"
# Throughput
info "Testing throughput..."
local dl_mbps=0 ul_mbps=0
# Download 10MB
local dl_out
dl_out=$(curl -s -o /dev/null -w "%{size_download} %{time_total}" \
--socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 30 \
"https://speed.cloudflare.com/__down?bytes=10485760" 2>/dev/null || echo "0 0")
local dl_bytes dl_time
dl_bytes=$(echo "$dl_out" | awk '{print $1}')
dl_time=$(echo "$dl_out" | awk '{print $2}')
if [[ "${dl_bytes:-0}" -gt 1000000 ]] 2>/dev/null; then
dl_mbps=$(echo "scale=2; $dl_bytes * 8 / $dl_time / 1000000" | bc)
pass "Download: ${dl_mbps} Mbps (${dl_bytes} bytes in ${dl_time}s)"
else
fail "Download failed (bytes=${dl_bytes:-0})"
fi
# Upload 5MB
local ul_out
ul_out=$(dd if=/dev/urandom bs=1M count=5 2>/dev/null | curl -s -o /dev/null -w "%{size_upload} %{time_total}" \
--socks5-hostname 127.0.0.1:$SOCKS_PORT --max-time 30 \
-X POST --data-binary @- https://httpbin.org/post 2>/dev/null || echo "0 0")
local ul_bytes ul_time
ul_bytes=$(echo "$ul_out" | awk '{print $1}')
ul_time=$(echo "$ul_out" | awk '{print $2}')
if [[ "${ul_bytes:-0}" -gt 100000 ]] 2>/dev/null; then
ul_mbps=$(echo "scale=2; $ul_bytes * 8 / $ul_time / 1000000" | bc)
pass "Upload: ${ul_mbps} Mbps (${ul_bytes} bytes in ${ul_time}s)"
else
fail "Upload failed"
ul_mbps=0
fi
echo "$label|OK|$avg_ms|$min_ms|$p95_ms|$max_ms|$dl_mbps|$ul_mbps" >> /tmp/results.tsv
cleanup_xray
}
# ── Remote update functions for each config ───────────────────────────────
update_remote_B() {
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"show": False, "dest": "$DEST",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "$PRIVATE_KEY",
"shortIds": ["$SID_B", ""]
},
"xhttpSettings": {
"path": "$XHTTP_PATH", "host": "", "mode": "auto",
"extra": {"xPaddingBytes": "100-1000", "xmux": {"maxConcurrency": "16-32", "maxConnections": 0,
"cMaxReuseTimes": "64-128", "cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900", "hMaxReusableSecs": "1800-3000"}}
}
}
client = {"id": "$UUID_B", "flow": "", "email": "test-b",
"limitIp": 0, "totalGB": 0, "expiryTime": 0, "enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {"id": 1, "tag": "inbound-443", "enable": True, "port": 443, "listen": "", "protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}), "remark": "inbound-443", "expiryTime": 0}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
print(" Remote updated (XHTTP):", r.json().get("success"), r.json().get("msg",""))
PYEOF
}
update_remote_C() {
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = {
"network": "grpc",
"security": "reality",
"realitySettings": {
"show": False, "dest": "$DEST",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "$PRIVATE_KEY",
"shortIds": ["$SID_C", ""]
},
"grpcSettings": {"serviceName": "$GRPC_SVC", "multiMode": True,
"idle_timeout": 60, "health_check_timeout": 20, "initial_windows_size": 65536}
}
client = {"id": "$UUID_C", "flow": "", "email": "test-c",
"limitIp": 0, "totalGB": 0, "expiryTime": 0, "enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {"id": 1, "tag": "inbound-443", "enable": True, "port": 443, "listen": "", "protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}), "remark": "inbound-443", "expiryTime": 0}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
print(" Remote updated (gRPC):", r.json().get("success"), r.json().get("msg",""))
PYEOF
}
update_remote_D() {
# Config D uses same server as A but adds UUID_D as client
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": False, "dest": "$DEST",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "$PRIVATE_KEY",
"shortIds": ["$SID_D", ""]
},
"tcpSettings": {"header": {"type": "none"}}
}
client = {"id": "$UUID_D", "flow": "xtls-rprx-vision", "email": "test-d",
"limitIp": 0, "totalGB": 0, "expiryTime": 0, "enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {"id": 1, "tag": "inbound-443", "enable": True, "port": 443, "listen": "", "protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}), "remark": "inbound-443", "expiryTime": 0}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
print(" Remote updated (TCP+Fragment):", r.json().get("success"), r.json().get("msg",""))
PYEOF
}
restore_A() {
source "$VENV/bin/activate"
python3 << PYEOF
import requests, json
import urllib3; urllib3.disable_warnings()
s = requests.Session(); s.verify = False
BASE = "https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR"
s.post(f"{BASE}/login", data={"username": "xrayadmin", "password": "Admin2026!"})
stream = {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": False, "dest": "$DEST",
"serverNames": ["www.delfi.lv","www.lmt.lv","www.inbox.lv","e-klase.lv"],
"privateKey": "$PRIVATE_KEY",
"shortIds": ["$SID_A", ""]
},
"tcpSettings": {"header": {"type": "none"}}
}
client = {"id": "$UUID_A", "flow": "xtls-rprx-vision", "email": "local-outbound",
"limitIp": 0, "totalGB": 0, "expiryTime": 0, "enable": True, "tgId": "", "subId": "", "comment": ""}
payload = {"id": 1, "tag": "inbound-443", "enable": True, "port": 443, "listen": "", "protocol": "vless",
"settings": json.dumps({"clients": [client], "decryption": "none", "fallbacks": []}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": False}), "remark": "inbound-443", "expiryTime": 0}
r = s.post(f"{BASE}/panel/api/inbounds/update/1", json=payload)
print(" Restored baseline (TCP+Vision):", r.json().get("success"))
PYEOF
}
# ── Run all tests ─────────────────────────────────────────────────────────
echo "Config|Status|Avg_ms|Min_ms|P95_ms|Max_ms|DL_Mbps|UL_Mbps" > /tmp/results.tsv
# Ensure we start with baseline on remote
info "Ensuring baseline is active on remote..."
restore_A
run_test "A: TCP+Reality+Vision (baseline)" write_cfg_A ""
run_test "B: XHTTP+Reality" write_cfg_B update_remote_B
restore_A; sleep 2
run_test "C: gRPC+Reality" write_cfg_C update_remote_C
restore_A; sleep 2
run_test "D: TCP+Reality+Vision+Fragment+Noise" write_cfg_D update_remote_D
# Restore baseline permanently
header "Restoring baseline on remote"
restore_A
pass "Baseline restored"
# ── Summary ───────────────────────────────────────────────────────────────
header "RESULTS SUMMARY"
printf "\n%-42s %-12s %-9s %-9s %-9s %-9s\n" "Configuration" "Status" "Avg ms" "DL Mbps" "UL Mbps" "P95 ms"
printf "%-42s %-12s %-9s %-9s %-9s %-9s\n" "──────────────────────────────────────────" "──────────" "───────" "───────" "───────" "───────"
while IFS='|' read -r name status avg min p95 max dl ul; do
[[ "$name" == "Config" ]] && continue
if [[ "$status" == "OK" ]]; then
printf "${GREEN}%-42s${NC} %-12s %-9s %-9s %-9s %-9s\n" "$name" "$status" "${avg}ms" "$dl" "$ul" "${p95}ms"
else
printf "${RED}%-42s${NC} %-12s\n" "$name" "$status"
fi
done < /tmp/results.tsv
# ── Write markdown ────────────────────────────────────────────────────────
{
cat << MDEOF
# Xray Configuration Comparison — Port 443
**Date**: $(date '+%Y-%m-%d %H:%M:%S')
**Local xray**: 25.10.15 | **Remote xray**: 26.2.6
**Remote**: share.alogins.net (83.99.190.32), LXD container "xray"
## Results
| Configuration | Status | Avg Latency | P95 Latency | Download | Upload |
|---------------|--------|-------------|-------------|----------|--------|
MDEOF
while IFS='|' read -r name status avg min p95 max dl ul; do
[[ "$name" == "Config" ]] && continue
if [[ "$status" == "OK" ]]; then
echo "| $name | ✓ OK | ${avg}ms | ${p95}ms | ${dl} Mbps | ${ul} Mbps |"
else
echo "| $name | ✗ $status | — | — | — | — |"
fi
done < /tmp/results.tsv
cat << 'MDEOF'
## Configuration Descriptions
| Config | Transport | Port | Flow | DPI Target |
|--------|-----------|------|------|------------|
| A | TCP + Reality | 443 | xtls-rprx-vision | Baseline — standard Russia anti-DPI |
| B | XHTTP + Reality | 443 | none | Volume-based TCP freezing (split uploads, XMUX padding) |
| C | gRPC + Reality | 443 | none | H2 pattern analysis (looks like enterprise API traffic) |
| D | TCP + Reality + Fragment/Noise | 443 | xtls-rprx-vision | TLS ClientHello DPI signature (fragment chains + noise) |
## Methodology
- Each config tested sequentially on the **same port 443**
- Remote inbound swapped on-the-fly via 3x-ui API
- 10 latency samples to google.com
- 10 MB download from Cloudflare speed test
- 5 MB upload to httpbin.org
MDEOF
} > "$RESULTS_FILE"
echo ""
echo -e "${BOLD}Results saved: $RESULTS_FILE${NC}"

334
test_sni_configs.sh Executable file
View File

@@ -0,0 +1,334 @@
#!/usr/bin/env bash
#
# test_sni_configs.sh — Test multiple SNI configurations for juris-reality
# Runs test_xray_connection.sh for each SNI and collects results
#
set -euo pipefail
XRAY_BIN="/usr/local/x-ui/bin/xray-linux-amd64"
SOCKS_PORT=11080
REMOTE_IP="83.99.190.32"
RESULTS_FILE="/home/alvis/ai-xray/sni_test_results.md"
CONFIGS_DIR="/home/alvis/ai-xray/configs"
mkdir -p "$CONFIGS_DIR"
# SNI configurations to test
declare -A SNIS
SNIS["www.delfi.lv"]="Latvia news #1"
SNIS["www.lmt.lv"]="Latvia telecom #1"
SNIS["www.tele2.lv"]="Latvia telecom #2"
SNIS["www.lsm.lv"]="Latvia public broadcasting"
SNIS["www.inbox.lv"]="Latvia webmail"
SNIS["e-klase.lv"]="Latvia education"
SNIS["share.alogins.net"]="Custom domain"
SNIS["www.microsoft.com"]="Worldwide baseline"
# Ordered list for consistent iteration
SNI_ORDER=(
"www.delfi.lv"
"www.lmt.lv"
"www.tele2.lv"
"www.lsm.lv"
"www.inbox.lv"
"e-klase.lv"
"share.alogins.net"
"www.microsoft.com"
)
generate_config() {
local sni="$1"
local file="$2"
cat > "$file" << JSONEOF
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": $SOCKS_PORT,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "$sni",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": { "header": { "type": "none" } }
}
}]
}
JSONEOF
}
run_test() {
local sni="$1"
local config="$2"
local xray_pid=""
# Start xray
$XRAY_BIN -config "$config" > /dev/null 2>&1 &
xray_pid=$!
sleep 2
if ! kill -0 "$xray_pid" 2>/dev/null; then
echo "XRAY_FAIL"
return
fi
local proxy="socks5h://127.0.0.1:$SOCKS_PORT"
# 1. Connectivity
local exit_ip
exit_ip=$(curl -s --connect-timeout 10 --max-time 15 -x "$proxy" https://ifconfig.me 2>/dev/null || echo "FAIL")
if [[ "$exit_ip" != "$REMOTE_IP" ]]; then
kill "$xray_pid" 2>/dev/null; wait "$xray_pid" 2>/dev/null || true
echo "CONN_FAIL|$exit_ip|0|0|0|0|0|0"
return
fi
# 2. Latency — 10 samples
local latencies=()
for i in $(seq 1 10); do
local t
t=$(curl -s -o /dev/null -w '%{time_total}' --connect-timeout 10 --max-time 15 -x "$proxy" https://www.gstatic.com/generate_204 2>/dev/null || echo "0")
local ms
ms=$(echo "$t * 1000" | bc 2>/dev/null | cut -d. -f1)
if [[ -n "$ms" && "$ms" -gt 0 ]]; then
latencies+=("$ms")
fi
done
local lat_min=0 lat_avg=0 lat_p95=0 lat_max=0
if [[ ${#latencies[@]} -gt 0 ]]; then
local sorted
sorted=($(printf '%s\n' "${latencies[@]}" | sort -n))
local cnt=${#sorted[@]}
lat_min=${sorted[0]}
lat_max=${sorted[$((cnt - 1))]}
local sum=0
for v in "${sorted[@]}"; do sum=$((sum + v)); done
lat_avg=$((sum / cnt))
local p95_idx
p95_idx=$(echo "($cnt * 95 + 99) / 100 - 1" | bc)
[[ $p95_idx -ge $cnt ]] && p95_idx=$((cnt - 1))
lat_p95=${sorted[$p95_idx]}
fi
# 3. Throughput — download 10MB
local dl_result
dl_result=$(curl -s -o /dev/null -w '%{size_download} %{time_total} %{speed_download}' \
--connect-timeout 15 --max-time 60 \
-x "$proxy" "http://speedtest.tele2.net/10MB.zip" 2>/dev/null || echo "0 0 0")
local dl_bytes dl_speed
dl_bytes=$(echo "$dl_result" | awk '{print $1}')
dl_speed=$(echo "$dl_result" | awk '{print $3}')
local dl_mbps="0"
if [[ "$dl_bytes" -gt 0 ]] 2>/dev/null; then
dl_mbps=$(echo "scale=2; $dl_speed * 8 / 1048576" | bc)
fi
# 3b. Throughput — upload 5MB
local ul_result
ul_result=$(dd if=/dev/urandom bs=1M count=5 2>/dev/null | \
curl -s -o /dev/null -w '%{size_upload} %{time_total} %{speed_upload}' \
--connect-timeout 15 --max-time 60 \
-x "$proxy" -X POST -H "Content-Type: application/octet-stream" \
--data-binary @- "http://speedtest.tele2.net/upload.php" 2>/dev/null || echo "0 0 0")
local ul_bytes ul_speed
ul_bytes=$(echo "$ul_result" | awk '{print $1}')
ul_speed=$(echo "$ul_result" | awk '{print $3}')
local ul_mbps="0"
if [[ "$ul_bytes" -gt 0 ]] 2>/dev/null; then
ul_mbps=$(echo "scale=2; $ul_speed * 8 / 1048576" | bc)
fi
kill "$xray_pid" 2>/dev/null; wait "$xray_pid" 2>/dev/null || true
echo "OK|$exit_ip|$lat_min|$lat_avg|$lat_p95|$lat_max|$dl_mbps|$ul_mbps"
}
# ── Main ──────────────────────────────────────────────────────────────────────
echo "============================================"
echo " SNI Configuration Benchmark"
echo " $(date -u '+%Y-%m-%d %H:%M UTC')"
echo "============================================"
echo ""
declare -A RESULTS
for sni in "${SNI_ORDER[@]}"; do
desc="${SNIS[$sni]}"
config="$CONFIGS_DIR/${sni//[^a-zA-Z0-9._-]/_}.json"
echo "[$sni] ($desc)"
echo -n " Generating config... "
generate_config "$sni" "$config"
echo "done"
echo -n " Running tests... "
result=$(run_test "$sni" "$config")
RESULTS["$sni"]="$result"
status=$(echo "$result" | cut -d'|' -f1)
if [[ "$status" == "OK" ]]; then
avg=$(echo "$result" | cut -d'|' -f4)
dl=$(echo "$result" | cut -d'|' -f7)
ul=$(echo "$result" | cut -d'|' -f8)
echo "OK (avg=${avg}ms, dl=${dl}Mbps, ul=${ul}Mbps)"
else
echo "FAILED ($status)"
fi
echo ""
done
# ── Write results ─────────────────────────────────────────────────────────────
{
echo "# SNI Configuration Test Results"
echo ""
echo "Date: $(date -u '+%Y-%m-%d %H:%M UTC')"
echo "Server: share.alogins.net (83.99.190.32)"
echo "Protocol: VLESS + XTLS-Reality + Vision"
echo "Reality dest: www.delfi.lv:443"
echo ""
echo "## Results"
echo ""
echo "| # | SNI | Type | Conn | Latency Min | Latency Avg | Latency P95 | Latency Max | Download | Upload |"
echo "|---|-----|------|------|-------------|-------------|-------------|-------------|----------|--------|"
idx=0
best_sni=""
best_score=999999
for sni in "${SNI_ORDER[@]}"; do
idx=$((idx + 1))
desc="${SNIS[$sni]}"
result="${RESULTS[$sni]}"
status=$(echo "$result" | cut -d'|' -f1)
if [[ "$status" == "OK" ]]; then
ip=$(echo "$result" | cut -d'|' -f2)
lat_min=$(echo "$result" | cut -d'|' -f3)
lat_avg=$(echo "$result" | cut -d'|' -f4)
lat_p95=$(echo "$result" | cut -d'|' -f5)
lat_max=$(echo "$result" | cut -d'|' -f6)
dl=$(echo "$result" | cut -d'|' -f7)
ul=$(echo "$result" | cut -d'|' -f8)
echo "| $idx | \`$sni\` | $desc | OK | ${lat_min}ms | ${lat_avg}ms | ${lat_p95}ms | ${lat_max}ms | ${dl} Mbps | ${ul} Mbps |"
# Score: lower avg latency + higher throughput = better
# score = avg_latency - (dl_mbps + ul_mbps) (lower is better)
score=$(echo "$lat_avg" | bc)
if [[ $score -lt $best_score ]]; then
best_score=$score
best_sni=$sni
fi
else
echo "| $idx | \`$sni\` | $desc | FAIL | — | — | — | — | — | — |"
fi
done
echo ""
echo "## Best Configuration"
echo ""
if [[ -n "$best_sni" ]]; then
result="${RESULTS[$best_sni]}"
lat_avg=$(echo "$result" | cut -d'|' -f4)
dl=$(echo "$result" | cut -d'|' -f7)
ul=$(echo "$result" | cut -d'|' -f8)
echo "**Winner: \`$best_sni\`** (${SNIS[$best_sni]})"
echo ""
echo "- Average latency: ${lat_avg}ms"
echo "- Download: ${dl} Mbps"
echo "- Upload: ${ul} Mbps"
echo ""
echo "### Recommended client outbound config"
echo ""
echo '```json'
echo "{"
echo ' "tag": "juris-reality",'
echo ' "protocol": "vless",'
echo ' "settings": {'
echo ' "vnext": [{'
echo ' "address": "share.alogins.net",'
echo ' "port": 443,'
echo ' "users": [{'
echo ' "id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",'
echo ' "flow": "xtls-rprx-vision",'
echo ' "encryption": "none"'
echo ' }]'
echo ' }]'
echo ' },'
echo ' "streamSettings": {'
echo ' "network": "tcp",'
echo ' "security": "reality",'
echo ' "realitySettings": {'
echo ' "fingerprint": "chrome",'
echo " \"serverName\": \"$best_sni\","
echo ' "publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",'
echo ' "shortId": "48b4c16249ad44ff",'
echo ' "spiderX": "/"'
echo ' },'
echo ' "tcpSettings": { "header": { "type": "none" } }'
echo ' }'
echo "}"
echo '```'
else
echo "No configuration passed all tests."
fi
echo ""
echo "## All Configurations"
echo ""
echo "Individual config files are stored in \`configs/\` directory."
echo ""
for sni in "${SNI_ORDER[@]}"; do
config_name="${sni//[^a-zA-Z0-9._-]/_}.json"
echo "- \`configs/$config_name\` — SNI: \`$sni\`"
done
echo ""
echo "## Server-Side Reality Settings"
echo ""
echo '```json'
echo '"realitySettings": {'
echo ' "dest": "www.delfi.lv:443",'
echo ' "serverNames": ['
echo ' "www.delfi.lv",'
echo ' "www.lmt.lv",'
echo ' "www.tele2.lv",'
echo ' "www.lsm.lv",'
echo ' "www.inbox.lv",'
echo ' "e-klase.lv",'
echo ' "share.alogins.net",'
echo ' "www.microsoft.com"'
echo ' ],'
echo ' "privateKey": "KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY",'
echo ' "shortIds": ["48b4c16249ad44ff", ""]'
echo '}'
echo '```'
} > "$RESULTS_FILE"
echo "============================================"
echo "Results written to: $RESULTS_FILE"
echo "============================================"

263
test_xray_connection.sh Executable file
View File

@@ -0,0 +1,263 @@
#!/usr/bin/env bash
#
# test_xray_connection.sh — Test xray juris-xhttp outbound
# Tests: connectivity, latency (SLA), throughput
#
set -euo pipefail
XRAY_BIN="/usr/local/x-ui/bin/xray-linux-amd64"
TEST_CONFIG="/tmp/test-juris-sla.json"
SOCKS_PORT=11080
REMOTE_IP="83.99.190.32"
XRAY_PID=""
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
cleanup() {
if [[ -n "$XRAY_PID" ]] && kill -0 "$XRAY_PID" 2>/dev/null; then
kill "$XRAY_PID" 2>/dev/null
wait "$XRAY_PID" 2>/dev/null || true
fi
rm -f "$TEST_CONFIG"
}
trap cleanup EXIT
header() {
echo ""
echo -e "${BOLD}${CYAN}=== $1 ===${NC}"
}
pass() { echo -e " ${GREEN}PASS${NC} $1"; }
fail() { echo -e " ${RED}FAIL${NC} $1"; }
info() { echo -e " ${YELLOW}INFO${NC} $1"; }
# ── Write temporary xray config ──────────────────────────────────────────────
cat > "$TEST_CONFIG" << 'EOF'
{
"log": { "loglevel": "error" },
"inbounds": [{
"listen": "127.0.0.1",
"port": 11080,
"protocol": "socks",
"settings": { "auth": "noauth", "udp": true },
"tag": "test-socks"
}],
"outbounds": [{
"tag": "juris-xhttp",
"protocol": "vless",
"settings": {
"vnext": [{
"address": "share.alogins.net",
"port": 443,
"users": [{
"id": "6e422ab5-070a-43f6-8241-38cd56d23d24",
"flow": "",
"encryption": "none"
}]
}]
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.delfi.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "6036d37d12c443c4",
"spiderX": "/"
},
"xhttpSettings": {
"path": "/xt-6036d37d",
"host": "",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"xmux": {
"maxConcurrency": "16-32",
"maxConnections": 0,
"cMaxReuseTimes": "64-128",
"cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900",
"hMaxReusableSecs": "1800-3000"
}
}
}
}
}]
}
EOF
# ── Start xray ───────────────────────────────────────────────────────────────
header "Starting test xray instance (SOCKS on 127.0.0.1:$SOCKS_PORT)"
$XRAY_BIN -test -config "$TEST_CONFIG" > /dev/null 2>&1
pass "Config validation OK"
$XRAY_BIN -config "$TEST_CONFIG" > /dev/null 2>&1 &
XRAY_PID=$!
sleep 2
if kill -0 "$XRAY_PID" 2>/dev/null; then
pass "Xray started (PID $XRAY_PID)"
else
fail "Xray failed to start"
exit 1
fi
# ── 1. Connectivity test ─────────────────────────────────────────────────────
header "1. Connectivity"
PROXY="socks5h://127.0.0.1:$SOCKS_PORT"
ERRORS=0
# Test: exit IP
EXIT_IP=$(curl -s --connect-timeout 10 --max-time 15 -x "$PROXY" https://ifconfig.me 2>/dev/null || echo "FAIL")
if [[ "$EXIT_IP" == "$REMOTE_IP" ]]; then
pass "Exit IP: $EXIT_IP (matches remote server)"
else
fail "Exit IP: '$EXIT_IP' (expected $REMOTE_IP)"
ERRORS=$((ERRORS + 1))
fi
# Test: HTTPS connectivity
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 --max-time 15 -x "$PROXY" https://www.google.com 2>/dev/null || echo "000")
if [[ "$HTTP_CODE" == "200" ]]; then
pass "HTTPS to google.com: HTTP $HTTP_CODE"
else
fail "HTTPS to google.com: HTTP $HTTP_CODE"
ERRORS=$((ERRORS + 1))
fi
# Test: DNS resolution through tunnel
DNS_IP=$(curl -s --connect-timeout 10 --max-time 15 -x "$PROXY" https://dns.google/resolve?name=example.com\&type=A 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['Answer'][0]['data'])" 2>/dev/null || echo "FAIL")
if [[ "$DNS_IP" != "FAIL" ]]; then
pass "DNS resolution through tunnel: example.com → $DNS_IP"
else
fail "DNS resolution through tunnel"
ERRORS=$((ERRORS + 1))
fi
# ── 2. Latency (SLA) ─────────────────────────────────────────────────────────
header "2. Latency (SLA)"
LATENCIES=()
LATENCY_ERRORS=0
SAMPLES=10
info "Running $SAMPLES requests to measure round-trip time..."
for i in $(seq 1 $SAMPLES); do
T=$(curl -s -o /dev/null -w '%{time_total}' --connect-timeout 10 --max-time 15 -x "$PROXY" https://www.gstatic.com/generate_204 2>/dev/null || echo "0")
MS=$(echo "$T * 1000" | bc 2>/dev/null | cut -d. -f1)
if [[ -n "$MS" && "$MS" -gt 0 ]]; then
LATENCIES+=("$MS")
printf " [%2d/%d] %s ms\n" "$i" "$SAMPLES" "$MS"
else
LATENCY_ERRORS=$((LATENCY_ERRORS + 1))
printf " [%2d/%d] ${RED}timeout${NC}\n" "$i" "$SAMPLES"
fi
done
if [[ ${#LATENCIES[@]} -gt 0 ]]; then
# Calculate min/avg/max/p95
SORTED=($(printf '%s\n' "${LATENCIES[@]}" | sort -n))
COUNT=${#SORTED[@]}
MIN=${SORTED[0]}
MAX=${SORTED[$((COUNT - 1))]}
SUM=0
for v in "${SORTED[@]}"; do SUM=$((SUM + v)); done
AVG=$((SUM / COUNT))
P95_IDX=$(echo "($COUNT * 95 + 99) / 100 - 1" | bc)
[[ $P95_IDX -ge $COUNT ]] && P95_IDX=$((COUNT - 1))
P95=${SORTED[$P95_IDX]}
echo ""
info "Latency summary ($COUNT/$SAMPLES successful):"
echo -e " ${BOLD} Min: ${MIN} ms${NC}"
echo -e " ${BOLD} Avg: ${AVG} ms${NC}"
echo -e " ${BOLD} P95: ${P95} ms${NC}"
echo -e " ${BOLD} Max: ${MAX} ms${NC}"
if [[ $AVG -lt 300 ]]; then
pass "Average latency ${AVG}ms < 300ms threshold"
elif [[ $AVG -lt 500 ]]; then
info "Average latency ${AVG}ms — acceptable but elevated"
else
fail "Average latency ${AVG}ms exceeds 500ms"
ERRORS=$((ERRORS + 1))
fi
else
fail "All latency samples failed"
ERRORS=$((ERRORS + 1))
fi
# ── 3. Throughput ─────────────────────────────────────────────────────────────
header "3. Throughput"
info "Downloading 10MB test file through tunnel..."
DL_RESULT=$(curl -s -o /dev/null -w '%{size_download} %{time_total} %{speed_download}' \
--connect-timeout 15 --max-time 60 \
-x "$PROXY" \
"http://speedtest.tele2.net/10MB.zip" 2>/dev/null || echo "0 0 0")
DL_BYTES=$(echo "$DL_RESULT" | awk '{print $1}')
DL_TIME=$(echo "$DL_RESULT" | awk '{print $2}')
DL_SPEED=$(echo "$DL_RESULT" | awk '{print $3}')
if [[ "$DL_BYTES" -gt 0 ]] 2>/dev/null; then
DL_MB=$(echo "scale=2; $DL_BYTES / 1048576" | bc)
DL_MBPS=$(echo "scale=2; $DL_SPEED * 8 / 1048576" | bc)
DL_TIME_S=$(echo "scale=2; $DL_TIME" | bc)
pass "Downloaded ${DL_MB} MB in ${DL_TIME_S}s"
echo -e " ${BOLD} Download speed: ${DL_MBPS} Mbps${NC}"
else
fail "Download test failed"
ERRORS=$((ERRORS + 1))
fi
info "Uploading 5MB test payload through tunnel..."
# Generate 5MB of random data and POST it
UL_RESULT=$(dd if=/dev/urandom bs=1M count=5 2>/dev/null | \
curl -s -o /dev/null -w '%{size_upload} %{time_total} %{speed_upload}' \
--connect-timeout 15 --max-time 60 \
-x "$PROXY" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary @- \
"http://speedtest.tele2.net/upload.php" 2>/dev/null || echo "0 0 0")
UL_BYTES=$(echo "$UL_RESULT" | awk '{print $1}')
UL_TIME=$(echo "$UL_RESULT" | awk '{print $2}')
UL_SPEED=$(echo "$UL_RESULT" | awk '{print $3}')
if [[ "$UL_BYTES" -gt 0 ]] 2>/dev/null; then
UL_MB=$(echo "scale=2; $UL_BYTES / 1048576" | bc)
UL_MBPS=$(echo "scale=2; $UL_SPEED * 8 / 1048576" | bc)
UL_TIME_S=$(echo "scale=2; $UL_TIME" | bc)
pass "Uploaded ${UL_MB} MB in ${UL_TIME_S}s"
echo -e " ${BOLD} Upload speed: ${UL_MBPS} Mbps${NC}"
else
fail "Upload test failed"
ERRORS=$((ERRORS + 1))
fi
# ── Summary ───────────────────────────────────────────────────────────────────
header "Summary"
if [[ $ERRORS -eq 0 ]]; then
echo -e " ${GREEN}${BOLD}ALL TESTS PASSED${NC}"
else
echo -e " ${RED}${BOLD}$ERRORS TEST(S) FAILED${NC}"
fi
echo ""
exit $ERRORS

383
xray_config.md Normal file
View File

@@ -0,0 +1,383 @@
# Xray Infrastructure Configuration
## Servers
| Role | IP | Domain | OS | Xray | x-ui |
|------|-----|--------|-----|------|------|
| Local (client) | 95.165.85.65 | — | Ubuntu, kernel 6.8.0-94 | 25.10.15 | 2.8.10 |
| Remote (server) | 83.99.190.32 | share.alogins.net | Ubuntu 24.04.3 (LXD container) | 26.2.6 | 2.8.10 |
---
## Access Credentials
### Remote server SSH
- Host: `83.99.190.32`
- User: `juris`
- LXD container access: `lxc exec xray -- <command>`
### Remote 3x-ui panel
- URL: `https://share.alogins.net:16627/gBdsRLtVZdgZ63wmVR/`
- Username: `xrayadmin`
- Password: `Admin2026!`
### Local x-ui panel
- URL: `http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe/`
- Username: `3ZHPoQdd89`
- Password: `1c1QUbKhQP`
### Local server sudo
- User: `alvis`
---
## Reality Keys (shared keypair)
| Key | Value |
|-----|-------|
| Private key (server) | `KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY` |
| Public key (client) | `58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw` |
### juris-xhttp (ACTIVE — XHTTP+Reality)
| Field | Value |
|-------|-------|
| UUID | `6e422ab5-070a-43f6-8241-38cd56d23d24` |
| Short ID | `6036d37d12c443c4` |
| XHTTP path | `/xt-6036d37d` |
### juris-reality (FALLBACK — TCP+Reality+Vision)
| Field | Value |
|-------|-------|
| UUID | `64522a14-54aa-4b3c-8071-8c8b17aa1f08` |
| Short ID | `48b4c16249ad44ff` |
---
## SNI Test Results
Reality dest on remote server: `www.delfi.lv:443`
| SNI | Status | Avg Latency | Download | Upload |
|-----|--------|-------------|----------|--------|
| `www.delfi.lv` | **ACTIVE** | 121ms | 44.79 Mbps | 33.90 Mbps |
| `www.lmt.lv` | OK | 133ms | 50.58 Mbps | 37.33 Mbps |
| `www.inbox.lv` | OK | 121ms | 45.48 Mbps | 32.98 Mbps |
| `e-klase.lv` | OK | 121ms | 54.84 Mbps | 29.58 Mbps |
| `www.tele2.lv` | FAIL (dest mismatch) | — | — | — |
| `www.lsm.lv` | FAIL (dest mismatch) | — | — | — |
| `share.alogins.net` | FAIL (dest mismatch) | — | — | — |
| `www.microsoft.com` | FAIL (dest mismatch) | — | — | — |
Full benchmark results: `sni_test_results.md`
---
## LXD Port Forwarding (remote host → container)
```
proxy-443: tcp:0.0.0.0:443 → tcp:127.0.0.1:443
proxy-16627: tcp:0.0.0.0:16627 → tcp:127.0.0.1:16627
```
---
## Remote Server Config (VLESS+XHTTP+Reality inbound) — ACTIVE
Server: `share.alogins.net` / LXD container `xray` (10.187.159.41)
**Winner of comparative test**: lowest latency (142ms avg, 225ms P95), best upload.
```json
{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"tag": "inbound-443",
"settings": {
"clients": [
{
"id": "6e422ab5-070a-43f6-8241-38cd56d23d24",
"email": "juris-xhttp",
"flow": ""
},
{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"email": "juris-reality-legacy",
"flow": ""
}
],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"dest": "www.delfi.lv:443",
"serverNames": ["www.delfi.lv", "www.lmt.lv", "www.inbox.lv", "e-klase.lv"],
"privateKey": "KJfhenZvJV1kXwv4kDC8NPBtMUY0RR8lFrxsxfXfFmY",
"shortIds": ["48b4c16249ad44ff", "6036d37d12c443c4", ""],
"show": false
},
"xhttpSettings": {
"path": "/xt-6036d37d",
"host": "",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"xmux": {
"maxConcurrency": "16-32",
"maxConnections": 0,
"cMaxReuseTimes": "64-128",
"cMaxLifetimeMs": 0,
"hMaxRequestTimes": "600-900",
"hMaxReusableSecs": "1800-3000"
}
}
}
}
}
```
---
## Local Server Config (full live xray configuration)
Config path: `/usr/local/x-ui/bin/config.json`
DB path: `/etc/x-ui/x-ui.db` (template stored in `xrayTemplateConfig` setting)
### Outbounds
| Tag | Protocol | Destination | Security | Fingerprint | Status |
|-----|----------|-------------|----------|-------------|--------|
| `direct` | freedom | — | — | — | Default |
| `blocked` | blackhole | — | — | — | Block |
| `socks` | socks | 127.0.0.1:1081 | — | — | Local proxy |
| `AMS-Server` | trojan | 45.32.235.202:443 | reality (SNI: www.nvidia.com) | chrome | Active |
| `juris-reality` | vless | share.alogins.net:443 | TCP+reality (SNI: www.delfi.lv) | chrome | Fallback, no routing rules |
| `juris-xhttp` | vless | share.alogins.net:443 | XHTTP+reality (SNI: www.delfi.lv, Host: www.delfi.lv) | chrome | **PRIMARY**, no routing rules |
| `juris-xhttp-firefox` | vless | share.alogins.net:443 | XHTTP+reality (SNI: www.delfi.lv, Host: www.delfi.lv) | firefox | Alternate fingerprint, no routing rules |
| `juris-xhttp-safari` | vless | share.alogins.net:443 | XHTTP+reality (SNI: www.delfi.lv, Host: www.delfi.lv) | safari | Alternate fingerprint, no routing rules |
### Inbounds
| Tag | Protocol | Listen | Port | Notes |
|-----|----------|--------|------|-------|
| `api` | tunnel | 127.0.0.1 | 62789 | Internal API |
| `inbound-127.0.0.1:8445` | vless | 127.0.0.1 | 8445 | XHTTP stream, 4 clients |
| `inbound-56928` | mixed | 0.0.0.0 | 56928 | SOCKS/HTTP proxy, routes → AMS-Server |
### Routing Rules
| # | Match | Outbound |
|---|-------|----------|
| 1 | inboundTag: `inbound-56928` | `AMS-Server` |
| 2 | inboundTag: `api` | `api` |
| 3 | ip: `geoip:private` | `blocked` |
| 4 | protocol: `bittorrent` | `blocked` |
| 5 | domain: `ext:geosite_RU.dat:ru-blocked` + inbound `8445` | `AMS-Server` |
### VLESS Clients (inbound-127.0.0.1:8445)
| Email | UUID | Comment |
|-------|------|---------|
| 36npz8zz | 13a01d7c-9ca7-4ee6-83d8-4c59907b13d1 | ipad pro |
| 4camw53nd | 108fdf6b-f8e4-4e1a-9bbe-b7c1fa9fffa0 | pixel |
| y3673rul | 584cba3d-2d43-464a-9b29-67e02adc092d | iphone promax |
| j5x2285y | f6781d19-cf77-4f8f-9114-0e612aa3081c | AlinaSt |
### juris-reality Outbound (full config)
```json
{
"tag": "juris-reality",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "share.alogins.net",
"port": 443,
"users": [
{
"id": "64522a14-54aa-4b3c-8071-8c8b17aa1f08",
"flow": "xtls-rprx-vision",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"fingerprint": "chrome",
"serverName": "www.delfi.lv",
"publicKey": "58Iqd6LuWXgvjAgo92-7KURhTp0Vj79yGF81l_iuvTw",
"shortId": "48b4c16249ad44ff",
"spiderX": "/"
},
"tcpSettings": {
"header": { "type": "none" }
}
}
}
```
### AMS-Server Outbound (existing)
```json
{
"tag": "AMS-Server",
"protocol": "trojan",
"settings": {
"servers": [
{
"address": "45.32.235.202",
"port": 443,
"password": "z5Y2tMeDs1"
}
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"publicKey": "J37AuaLzZYRTQZ-pxUpbIndo15EpoY1lhnAXZ1rUFnQ",
"fingerprint": "chrome",
"serverName": "www.nvidia.com",
"shortId": "22",
"spiderX": "/"
}
}
}
```
---
## File Paths
### Local server
| Path | Purpose |
|------|---------|
| `/usr/local/x-ui/bin/config.json` | Live xray config (auto-generated by x-ui, do NOT edit directly) |
| `/usr/local/x-ui/bin/xray-linux-amd64` | Xray binary |
| `/usr/local/x-ui/x-ui` | x-ui panel binary |
| `/etc/x-ui/x-ui.db` | x-ui database (settings, inbounds, users) |
| `/home/alvis/ai-xray/venv/` | Python venv with paramiko for SSH |
### Remote server (inside LXD container "xray")
| Path | Purpose |
|------|---------|
| `/usr/local/x-ui/bin/config.json` | Live xray config (auto-generated) |
| `/usr/local/x-ui/bin/xray-linux-amd64` | Xray binary |
| `/etc/x-ui/x-ui.db` | x-ui database |
---
## How to Route Traffic Through juris-reality
Currently `juris-reality` has no routing rules. To use it, add a rule to the xray template in the DB:
**Route specific domains:**
```json
{
"type": "field",
"domain": ["example.com", "example.org"],
"outboundTag": "juris-reality"
}
```
**Route all SOCKS proxy traffic (replaces AMS-Server):**
```json
{
"type": "field",
"inboundTag": ["inbound-56928"],
"outboundTag": "juris-reality"
}
```
**Route Russian-blocked domains through juris-reality instead of AMS-Server:**
Change outboundTag in rule #5 from `AMS-Server` to `juris-reality`.
---
## Updating Xray Config via x-ui REST API (no sudo)
`config.json` is auto-generated by x-ui from the DB template. **Never edit it directly.**
### Step-by-step
**1. Authenticate:**
```bash
curl -s -c /tmp/xui-cookie.txt -X POST \
'http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe/login' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=3ZHPoQdd89&password=1c1QUbKhQP'
```
**2. Read current xray template:**
```bash
curl -s -b /tmp/xui-cookie.txt -X POST \
'http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe/panel/xray/'
```
Returns `{"success":true,"obj":"{ \"xraySetting\": {...}, \"inboundTags\": [...] }"}`.
The `obj` value is a JSON string containing the full xray template under the `xraySetting` key.
**3. Update xray template:**
```bash
curl -s -b /tmp/xui-cookie.txt -X POST \
'http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe/panel/xray/update' \
-d 'xraySetting=<URL-encoded JSON of the full xray template>'
```
The key detail: use **form-urlencoded** with a single field `xraySetting` whose value is the full xray template JSON string. This is the `xraySetting` object from step 2 (the inner JSON, not the wrapper).
**4. Restart xray to apply:**
```bash
curl -s -b /tmp/xui-cookie.txt -X POST \
'http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe/panel/api/server/restartXrayService'
```
### Python example (recommended)
```python
import requests, json
BASE = "http://127.0.0.1:58959/gnYCNq4EbYukS5qtOe"
s = requests.Session()
# Login
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"]
# Modify (example: change SNI)
for ob in xray["outbounds"]:
if ob.get("tag") == "juris-reality":
ob["streamSettings"]["realitySettings"]["serverName"] = "www.delfi.lv"
# Write back (form field, NOT JSON body)
s.post(f"{BASE}/panel/xray/update", data={"xraySetting": json.dumps(xray)})
# Restart xray
s.post(f"{BASE}/panel/api/server/restartXrayService")
```
### API gotchas
| Endpoint | Method | Works? | Notes |
|----------|--------|--------|-------|
| `POST /panel/xray/update` | form-urlencoded `xraySetting=<json>` | **Yes** | Correct way to persist template changes |
| `POST /panel/xray/update` | JSON body `{"xraySetting": ...}` | No | Returns "unexpected end of JSON input" |
| `POST /panel/setting/update` | any | No | Accepts request but silently ignores `xrayTemplateConfig` |
| `GET /panel/setting/getDefaultJsonConfig` | — | — | Returns factory defaults, not actual config |
| `GET /panel/api/server/getConfigJson` | — | Read-only | Returns live running xray config |
| `xray api ado` CLI | — | Ephemeral | Adds outbound to running instance only, lost on restart |
---
## Important Notes
- The `xray api ado` CLI can add outbounds to the running instance (ephemeral, lost on restart). Requires full-config JSON format: `{"outbounds": [{...}]}`.