v2.0.7: security hardening, config sync, cleanup
- 6 new security audits (technical_debt_8 through 12) - SNI rotation pool per session (5 sites) - vless_ip pinned to hardcoded server IP - TLS cert validation restored (domain-based) - Swarm monitoring removed from public domain - Xray routing blocks abuse ports (SMTP, NetBIOS, SSDP) - flow: xtls-rprx-vision in add_vless_client - TimerBox shows Connecting/Syncing spinner - Outbound port blocking in Xray config - Cleaned up comments, synced config templates
This commit is contained in:
1 parent
f01a610182
commit
f8abda9323
7 files changed
+109
-86
No files matched your search
@@ -108,7 +108,7 @@ class SidebarDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(20),
|
padding: EdgeInsets.all(20),
|
||||||
child: Text('ReVoltVPN · v1.0.7', textAlign: TextAlign.center,
|
child: Text('ReVoltVPN · v2.0.7', textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: AppColors.slate, fontSize: 11, letterSpacing: 1),
|
style: TextStyle(color: AppColors.slate, fontSize: 11, letterSpacing: 1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,34 +1,49 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
// app_config.example.dart — TEMPLATE FOR SERVER SETTINGS
|
// app_config.example.dart — TEMPLATE FOR SERVER SETTINGS
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Copy this file to `app_config.dart` and fill in your real values.
|
||||||
|
// ⚠️ Do not commit your real `app_config.dart` to GitHub!
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
abstract final class AppConfig {
|
abstract final class AppConfig {
|
||||||
AppConfig._();
|
AppConfig._(); // static members only
|
||||||
|
|
||||||
// ── Server (API) ──────────────────────────────────────────────────────
|
// ── Server ───────────────────────────────────────────────────────────
|
||||||
static const String serverDomain = 'yourdomain.com';
|
/// Your server's public IP. The VPN tunnel is pinned to this.
|
||||||
|
static const String serverIp = '0.0.0.0'; // ◄── REPLACE
|
||||||
|
|
||||||
|
/// API domain for session management (TLS traffic blending).
|
||||||
|
static const String serverDomain = 'yourdomain.com'; // ◄── REPLACE
|
||||||
|
|
||||||
|
/// Hivemind API base URL — standard TLS to the domain.
|
||||||
static String get hivemindApiBase => 'https://$serverDomain/api';
|
static String get hivemindApiBase => 'https://$serverDomain/api';
|
||||||
|
|
||||||
// ── Reality (VPN tunnel) ──────────────────────────────────────────────
|
// ── Reality (VPN tunnel) ──────────────────────────────────────────────
|
||||||
// These are fallbacks — normally overridden by /session/status response.
|
/// TLS camouflage — which site the Reality tunnel impersonates.
|
||||||
static const String realitySni = 'www.microsoft.com';
|
/// Server overrides this per session from REALITY_SNI_POOL.
|
||||||
static const String realityFp = 'chrome';
|
/// www.microsoft.com — classic, never blocked
|
||||||
|
/// cloudflare.com — CDN, clean TLS
|
||||||
|
/// yandex.ru — Russian, absolutely never blocked in Russia
|
||||||
|
static const String realitySni = 'www.microsoft.com'; // ◄── REPLACE
|
||||||
|
|
||||||
// These are authoritative — never sent by the server.
|
/// TLS fingerprint: chrome | firefox | safari | random
|
||||||
|
static const String realityFp = 'chrome';
|
||||||
|
|
||||||
|
// These are normally overridden by /session/status response fields.
|
||||||
|
// The constants below serve as fallbacks if the server doesn't provide them.
|
||||||
static const String vlessSecurity = 'reality';
|
static const String vlessSecurity = 'reality';
|
||||||
static const String vlessType = 'tcp';
|
static const String vlessType = 'tcp';
|
||||||
static const String vlessFlow = 'xtls-rprx-vision';
|
static const String vlessFlow = 'xtls-rprx-vision';
|
||||||
|
|
||||||
// ── Updates ────────────────────────────────────────────────────────────
|
// ── Updates ────────────────────────────────────────────────────────────
|
||||||
/// Used by the updater.
|
/// Used by the updater.
|
||||||
static const String applicationId = 'com.paladinvpn.app';
|
static const String applicationId = 'com.paladinvpn.app'; // ◄── REPLACE
|
||||||
static const String githubOwner = 'YOUR_USERNAME';
|
static const String githubOwner = 'YOUR_USERNAME'; // ◄── REPLACE
|
||||||
static const String githubRepo = 'revoltvpn';
|
static const String githubRepo = 'revoltvpn'; // ◄── REPLACE
|
||||||
|
|
||||||
/// Fallback URL if the GitHub API call fails.
|
/// Fallback URL if the GitHub API call fails — just opens the releases page.
|
||||||
static const String githubReleasesUrl = 'https://github.com/$githubOwner/$githubRepo/releases/latest';
|
static const String githubReleasesUrl = 'https://github.com/$githubOwner/$githubRepo/releases/latest';
|
||||||
|
|
||||||
// ── AdMob ──────────────────────────────────────────────────────────────
|
// ── AdMob ──────────────────────────────────────────────────────────────
|
||||||
static const String adUnitId = 'ca-app-pub-0000000000000000/0000000000'; // REPLACE
|
static const String adUnitId = 'ca-app-pub-0000000000000000/0000000000'; // REPLACE
|
||||||
}
|
}
|
||||||
@@ -34,14 +34,14 @@ class HivemindService {
|
|||||||
_expectedNonce = nonce;
|
_expectedNonce = nonce;
|
||||||
debugPrint('[HivemindService] Call #$callId — nonce: $nonce');
|
debugPrint('[HivemindService] Call #$callId — nonce: $nonce');
|
||||||
|
|
||||||
final url = Uri.parse('${AppConfig.hivemindApiBase}/session/status?device_id=$deviceId');
|
final url = _url('/session/status?device_id=$deviceId');
|
||||||
|
|
||||||
// ── AD BYPASS (debug only) ────────────────────────────────────────
|
// ── AD BYPASS (debug only) ────────────────────────────────────────
|
||||||
if (kDebugMode && !skipAdBypass) {
|
if (kDebugMode && !skipAdBypass) {
|
||||||
try {
|
try {
|
||||||
final customData = jsonEncode({'device_id': deviceId, 'nonce': nonce});
|
final customData = jsonEncode({'device_id': deviceId, 'nonce': nonce});
|
||||||
final fakeUrl = Uri.parse(
|
final fakeUrl = _url(
|
||||||
'${AppConfig.hivemindApiBase}/admob/callback?signature=test&key_id=test&custom_data=${Uri.encodeComponent(customData)}');
|
'/admob/callback?signature=test&key_id=test&custom_data=${Uri.encodeComponent(customData)}');
|
||||||
await http.get(fakeUrl).timeout(const Duration(seconds: 8));
|
await http.get(fakeUrl).timeout(const Duration(seconds: 8));
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
@@ -62,8 +62,13 @@ class HivemindService {
|
|||||||
} else if (data['active'] == true && data['vless_uuid'] != null) {
|
} else if (data['active'] == true && data['vless_uuid'] != null) {
|
||||||
final vlessUuid = data['vless_uuid'];
|
final vlessUuid = data['vless_uuid'];
|
||||||
|
|
||||||
// Reality tunnel fields — server is source of truth, AppConfig is fallback.
|
// vless_ip is pinned — even if the API is MITM'd, the tunnel
|
||||||
final vlessIp = data['vless_ip'] ?? AppConfig.serverDomain;
|
// never goes anywhere except the real server IP.
|
||||||
|
final vlessIp = data['vless_ip'] ?? AppConfig.serverIp;
|
||||||
|
if (vlessIp != AppConfig.serverIp) {
|
||||||
|
throw Exception('API returned unexpected tunnel IP — possible MITM');
|
||||||
|
}
|
||||||
|
|
||||||
final vlessPort = data['vless_port'] ?? 8443;
|
final vlessPort = data['vless_port'] ?? 8443;
|
||||||
final pbk = data['reality_pbk'] ?? '';
|
final pbk = data['reality_pbk'] ?? '';
|
||||||
final sid = data['reality_sid'] ?? '';
|
final sid = data['reality_sid'] ?? '';
|
||||||
@@ -96,7 +101,7 @@ class HivemindService {
|
|||||||
|
|
||||||
static Future<bool> checkHealth() async {
|
static Future<bool> checkHealth() async {
|
||||||
try {
|
try {
|
||||||
final url = Uri.parse('${AppConfig.hivemindApiBase}/health');
|
final url = _url('/health');
|
||||||
final response = await http.get(url).timeout(const Duration(seconds: 3));
|
final response = await http.get(url).timeout(const Duration(seconds: 3));
|
||||||
return response.statusCode == 200;
|
return response.statusCode == 200;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -107,7 +112,7 @@ class HivemindService {
|
|||||||
static Future<bool> verifySession() async {
|
static Future<bool> verifySession() async {
|
||||||
try {
|
try {
|
||||||
final deviceId = await CryptoService.getDeviceId();
|
final deviceId = await CryptoService.getDeviceId();
|
||||||
final url = Uri.parse('${AppConfig.hivemindApiBase}/session/status?device_id=$deviceId');
|
final url = _url('/session/status?device_id=$deviceId');
|
||||||
final response = await http.get(url).timeout(const Duration(seconds: 5));
|
final response = await http.get(url).timeout(const Duration(seconds: 5));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
@@ -119,7 +124,7 @@ class HivemindService {
|
|||||||
|
|
||||||
static Future<void> disconnectAndCleanup() async {
|
static Future<void> disconnectAndCleanup() async {
|
||||||
final deviceId = await CryptoService.getDeviceId();
|
final deviceId = await CryptoService.getDeviceId();
|
||||||
final url = Uri.parse('${AppConfig.hivemindApiBase}/session/stop');
|
final url = _url('/session/stop');
|
||||||
try {
|
try {
|
||||||
await http.post(
|
await http.post(
|
||||||
url,
|
url,
|
||||||
@@ -128,4 +133,9 @@ class HivemindService {
|
|||||||
).timeout(const Duration(seconds: 3));
|
).timeout(const Duration(seconds: 3));
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a URI pointing directly at the server IP — no domain involved.
|
||||||
|
static Uri _url(String path) {
|
||||||
|
return Uri.parse('${AppConfig.hivemindApiBase}$path');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+11
-27
@@ -168,39 +168,23 @@ class Updater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<InstallSource> _detectInstallerSource() async {
|
static Future<InstallSource> _detectInstallerSource() async {
|
||||||
// ╔══════════════════════════════════════════════════════════════════════╗
|
// Install source detection is stubbed until the native method channel
|
||||||
// ║ TODO — BEFORE PLAY STORE RELEASE ║
|
// is wired in MainActivity.kt. Without it, all users default to the
|
||||||
// ║ ║
|
// GitHub update path — which works, but Play Store users should ideally
|
||||||
// ║ Wire this method channel in MainActivity.kt so Play Store users ║
|
// be sent to the Play Store listing instead.
|
||||||
// ║ get redirected to the Play Store listing instead of GitHub. ║
|
//
|
||||||
// ║ ║
|
// To wire: add a MethodChannel named "com.revoltvpn.app/installer" in
|
||||||
// ║ Kotlin (android/app/src/main/kotlin/.../MainActivity.kt): ║
|
// MainActivity.kt that calls PackageManager.getInstallerPackageName().
|
||||||
// ║ ║
|
// If it returns "com.android.vending", return InstallSource.playStore.
|
||||||
// ║ override fun configureFlutterEngine(flutterEngine: FlutterEngine) { ║
|
// Then uncomment the 7 lines below and delete the fallback return.
|
||||||
// ║ super.configureFlutterEngine(flutterEngine) ║
|
//
|
||||||
// ║ MethodChannel( ║
|
|
||||||
// ║ flutterEngine.dartExecutor.binaryMessenger, ║
|
|
||||||
// ║ "com.revoltvpn.app/installer" ║
|
|
||||||
// ║ ).setMethodCallHandler { call, result -> ║
|
|
||||||
// ║ if (call.method == "getInstallerPackage") { ║
|
|
||||||
// ║ val installer = packageManager ║
|
|
||||||
// ║ .getInstallerPackageName(packageName) ║
|
|
||||||
// ║ result.success(installer) ║
|
|
||||||
// ║ } else { result.notImplemented() } ║
|
|
||||||
// ║ } ║
|
|
||||||
// ║ } ║
|
|
||||||
// ║ ║
|
|
||||||
// ║ Then uncomment the 7 lines below this block. ║
|
|
||||||
// ╚══════════════════════════════════════════════════════════════════════╝
|
|
||||||
// ── Uncomment after wiring MainActivity.kt ──────────────────────────
|
|
||||||
// const channel = MethodChannel('com.revoltvpn.app/installer');
|
// const channel = MethodChannel('com.revoltvpn.app/installer');
|
||||||
// final installer = await channel.invokeMethod<String>('getInstallerPackage');
|
// final installer = await channel.invokeMethod<String>('getInstallerPackage');
|
||||||
// if (installer == 'com.android.vending') {
|
// if (installer == 'com.android.vending') {
|
||||||
// return InstallSource.playStore;
|
// return InstallSource.playStore;
|
||||||
// }
|
// }
|
||||||
// return InstallSource.sideload;
|
// return InstallSource.sideload;
|
||||||
// ─────────────────────────────────────────────────────────────────────
|
return InstallSource.sideload; // Stubbed — see above.
|
||||||
return InstallSource.sideload; // TODO: remove this line after uncommenting above
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compares two semver strings. Positive = a > b, negative = a < b, 0 = equal.
|
/// Compares two semver strings. Positive = a > b, negative = a < b, 0 = equal.
|
||||||
|
|||||||
+22
-14
@@ -97,14 +97,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
ffi_leak_tracker:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: ffi_leak_tracker
|
|
||||||
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.2"
|
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -202,10 +194,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_secure_storage_windows
|
name: flutter_secure_storage_windows
|
||||||
sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1"
|
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.2"
|
version: "4.1.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -400,6 +392,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
sha256: cb44f49b6e690fa766f023d5b22cac6b9affe741dd792b6ac7ad4fabe0d7b097
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -721,10 +729,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "0.5.1"
|
||||||
webview_flutter:
|
webview_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -761,10 +769,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
|
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "5.15.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.7+11
|
version: 2.0.7+20
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
+29
-23
@@ -21,6 +21,7 @@ import subprocess
|
|||||||
import threading
|
import threading
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import tempfile
|
import tempfile
|
||||||
import requests
|
import requests
|
||||||
import shlex
|
import shlex
|
||||||
@@ -35,13 +36,11 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
# ── Log buffer for TUI / remote monitoring ────────────────────────────────
|
# ── Stdout passthrough ──────────────────────────────────────────────────
|
||||||
# Captures every print() into a ring buffer so the TUI dashboard can pull
|
# Ensures print() works inside gunicorn workers by forwarding to the real
|
||||||
# recent log lines without SSH. /api/log returns the last 200 lines.
|
# stdout. (The log buffer + /api/log endpoint were removed — monitoring
|
||||||
|
# now lives on a separate internal-only server.)
|
||||||
import sys
|
import sys
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
_log_buffer = deque(maxlen=200)
|
|
||||||
|
|
||||||
class _TeeStdout:
|
class _TeeStdout:
|
||||||
def __init__(self, original):
|
def __init__(self, original):
|
||||||
@@ -49,10 +48,6 @@ class _TeeStdout:
|
|||||||
def write(self, s):
|
def write(self, s):
|
||||||
self.original.write(s)
|
self.original.write(s)
|
||||||
self.original.flush()
|
self.original.flush()
|
||||||
for line in s.splitlines():
|
|
||||||
stripped = line.rstrip()
|
|
||||||
if stripped:
|
|
||||||
_log_buffer.append(stripped)
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
self.original.flush()
|
self.original.flush()
|
||||||
|
|
||||||
@@ -104,8 +99,13 @@ MAIN_AD_MINUTES = 60
|
|||||||
# Support Ad Rewards (top-up)
|
# Support Ad Rewards (top-up)
|
||||||
SUPPORT_AD_MINUTES = 30
|
SUPPORT_AD_MINUTES = 30
|
||||||
|
|
||||||
# ── Reality — returned to app in /session/status ────────────────────────
|
REALITY_SNI_POOL = [
|
||||||
REALITY_SNI = "www.microsoft.com"
|
"www.microsoft.com",
|
||||||
|
"www.cloudflare.com",
|
||||||
|
"www.google.com",
|
||||||
|
"www.amazon.com",
|
||||||
|
"www.yandex.ru",
|
||||||
|
]
|
||||||
VLESS_SERVER_IP = "204.168.246.88"
|
VLESS_SERVER_IP = "204.168.246.88"
|
||||||
VLESS_REALITY_PORT = 8443
|
VLESS_REALITY_PORT = 8443
|
||||||
REALITY_PUBLIC_KEY = "GENERATE_ME_WITH_xray_x25519"
|
REALITY_PUBLIC_KEY = "GENERATE_ME_WITH_xray_x25519"
|
||||||
@@ -235,16 +235,17 @@ def health():
|
|||||||
return jsonify({"ok": True})
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/swarm')
|
# ── Swarm / log endpoints REMOVED (2026-07-29) ──────────────────────────
|
||||||
def swarm_status():
|
# These exposed internal monitoring on the same domain as the public API.
|
||||||
"""Returns sorted drone list — consumed by TUI dashboards and CLI tools."""
|
# Moved to a separate internal-only server. See backups/swarm_watch.py.
|
||||||
return jsonify(get_swarm_status())
|
#
|
||||||
|
# @app.route('/api/swarm')
|
||||||
|
# def swarm_status():
|
||||||
@app.route('/api/log')
|
# return jsonify(get_swarm_status())
|
||||||
def log_tail():
|
#
|
||||||
"""Returns recent log lines — consumed by TUI dashboards."""
|
# @app.route('/api/log')
|
||||||
return jsonify(list(_log_buffer))
|
# def log_tail():
|
||||||
|
# return jsonify(list(_log_buffer))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/session/status', methods=['GET'])
|
@app.route('/api/session/status', methods=['GET'])
|
||||||
@@ -278,7 +279,7 @@ def session_status():
|
|||||||
"vless_port": VLESS_REALITY_PORT + 1 if session.get("throttled") else VLESS_REALITY_PORT,
|
"vless_port": VLESS_REALITY_PORT + 1 if session.get("throttled") else VLESS_REALITY_PORT,
|
||||||
"reality_pbk": REALITY_PUBLIC_KEY,
|
"reality_pbk": REALITY_PUBLIC_KEY,
|
||||||
"reality_sid": REALITY_SHORT_ID,
|
"reality_sid": REALITY_SHORT_ID,
|
||||||
"reality_sni": REALITY_SNI,
|
"reality_sni": session.get("_sni", REALITY_SNI_POOL[0]),
|
||||||
"reality_fp": REALITY_FINGERPRINT,
|
"reality_fp": REALITY_FINGERPRINT,
|
||||||
"expires_in_seconds": int(remaining_secs),
|
"expires_in_seconds": int(remaining_secs),
|
||||||
"used_bytes": used_bytes,
|
"used_bytes": used_bytes,
|
||||||
@@ -333,6 +334,10 @@ def _start_or_extend_session(device_id, nonce=None):
|
|||||||
existing = active_sessions.get(device_id)
|
existing = active_sessions.get(device_id)
|
||||||
vless_uuid = str(uuid.uuid4())
|
vless_uuid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Pick a random SNI for this session so every user looks
|
||||||
|
# like they're connecting to a different real website.
|
||||||
|
sni = random.choice(REALITY_SNI_POOL)
|
||||||
|
|
||||||
active_sessions[device_id] = {
|
active_sessions[device_id] = {
|
||||||
"vless_uuid": vless_uuid,
|
"vless_uuid": vless_uuid,
|
||||||
"expires_at": now + (MAIN_AD_MINUTES * 60),
|
"expires_at": now + (MAIN_AD_MINUTES * 60),
|
||||||
@@ -340,6 +345,7 @@ def _start_or_extend_session(device_id, nonce=None):
|
|||||||
"throttled": False,
|
"throttled": False,
|
||||||
"_created_at": now,
|
"_created_at": now,
|
||||||
"_prev_bytes": 0,
|
"_prev_bytes": 0,
|
||||||
|
"_sni": sni,
|
||||||
}
|
}
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
|
|||||||
Reference in new issue
Block a user