From 47b97d9a7681be34aae294aa1554f37f2e59c553 Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Fri, 30 Jan 2026 15:04:23 +0300 Subject: [PATCH] feat: Enforce 1 user 1 connection for X-ray and fix active stats speed --- inc/InstallProtocolManager.php | 23 ++++++++ inc/VpnClient.php | 52 ++++++++++++++++++- .../052_add_current_speed_to_clients.sql | 1 + migrations/053_split_speed.sql | 3 ++ run_migration_053.php | 13 +++++ templates/servers/view.twig | 5 +- 6 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 migrations/052_add_current_speed_to_clients.sql create mode 100644 migrations/053_split_speed.sql create mode 100644 run_migration_053.php diff --git a/inc/InstallProtocolManager.php b/inc/InstallProtocolManager.php index f0d4bbe..a39cf69 100644 --- a/inc/InstallProtocolManager.php +++ b/inc/InstallProtocolManager.php @@ -1210,7 +1210,30 @@ class InstallProtocolManager } // 2. Modify config + + // Ensure policy for 1 user 1 connection + if (!isset($config['policy'])) { + $config['policy'] = ['levels' => ['0' => []]]; + } + if (!isset($config['policy']['levels'])) { + $config['policy']['levels'] = ['0' => []]; + } + if (!isset($config['policy']['levels']['0'])) { + $config['policy']['levels']['0'] = []; + } + + // Enforce limitIp: 1 for user level 0 + $config['policy']['levels']['0']['handshake'] = 4; + $config['policy']['levels']['0']['connIdle'] = 300; + $config['policy']['levels']['0']['uplinkOnly'] = 2; + $config['policy']['levels']['0']['downlinkOnly'] = 5; + $config['policy']['levels']['0']['statsUserUplink'] = true; + $config['policy']['levels']['0']['statsUserDownlink'] = true; + $config['policy']['levels']['0']['bufferSize'] = 4; + $config['policy']['levels']['0']['limitIp'] = 1; // Enforce 1 IP per user + // Assuming VLESS structure: inbounds[0] -> settings -> clients + if (!isset($config['inbounds'][0]['settings']['clients'])) { // Might be different structure? But we stick to standard Amnezia XRay config if (!isset($config['inbounds'][0]['settings'])) { diff --git a/inc/VpnClient.php b/inc/VpnClient.php index 77ff66a..1e3614e 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -1529,6 +1529,17 @@ class VpnClient } try { + // Get previous stats for speed calculation + $pdo = DB::conn(); + $stmtPrev = $pdo->prepare('SELECT bytes_sent, bytes_received, last_sync_at, last_handshake FROM vpn_clients WHERE id = ?'); + $stmtPrev->execute([$this->clientId]); + $prev = $stmtPrev->fetch(); + + $prevSent = (int) ($prev['bytes_sent'] ?? 0); + $prevReceived = (int) ($prev['bytes_received'] ?? 0); + $prevSyncAt = $prev['last_sync_at'] ? strtotime($prev['last_sync_at']) : 0; + $prevHandshake = $prev['last_handshake'] ? strtotime($prev['last_handshake']) : 0; + // XRay stats logic $stats = []; @@ -1547,6 +1558,14 @@ class VpnClient if ($identifier) { $stats = self::getXrayStats($serverData, $identifier); + // Infer online status for XRay: if traffic increased, they are online. + // Update last_handshake to NOW() if activity detected. + if ($stats['bytes_sent'] > $prevSent || $stats['bytes_received'] > $prevReceived) { + $stats['last_handshake'] = time(); + } else { + // Keep previous handshake if no new activity + $stats['last_handshake'] = $prevHandshake; + } } } @@ -1555,10 +1574,36 @@ class VpnClient $stats = self::getClientStatsFromServer($serverData, $this->data['public_key']); } - $pdo = DB::conn(); + // Calculate speeds (bytes per second) + $now = time(); + $timeDiff = $now - $prevSyncAt; + $currentSpeed = 0; + $speedUp = 0; + $speedDown = 0; + + if ($timeDiff > 0 && $prevSyncAt > 0) { + // Total speed + $bytesDiff = ($stats['bytes_sent'] + $stats['bytes_received']) - ($prevSent + $prevReceived); + if ($bytesDiff > 0) { + $currentSpeed = (int) ($bytesDiff / $timeDiff); + } + + // Upload speed + $sentDiff = $stats['bytes_sent'] - $prevSent; + if ($sentDiff > 0) { + $speedUp = (int) ($sentDiff / $timeDiff); + } + + // Download speed + $receivedDiff = $stats['bytes_received'] - $prevReceived; + if ($receivedDiff > 0) { + $speedDown = (int) ($receivedDiff / $timeDiff); + } + } + $stmt = $pdo->prepare(' UPDATE vpn_clients - SET bytes_sent = ?, bytes_received = ?, last_handshake = ?, last_sync_at = NOW() + SET bytes_sent = ?, bytes_received = ?, last_handshake = ?, current_speed = ?, speed_up = ?, speed_down = ?, last_sync_at = NOW() WHERE id = ? '); @@ -1570,6 +1615,9 @@ class VpnClient $stats['bytes_sent'], $stats['bytes_received'], $lastHandshake, + $currentSpeed, + $speedUp, + $speedDown, $this->clientId ]); } catch (Exception $e) { diff --git a/migrations/052_add_current_speed_to_clients.sql b/migrations/052_add_current_speed_to_clients.sql new file mode 100644 index 0000000..bc588db --- /dev/null +++ b/migrations/052_add_current_speed_to_clients.sql @@ -0,0 +1 @@ +ALTER TABLE vpn_clients ADD COLUMN current_speed BIGINT DEFAULT 0 AFTER traffic_limit; diff --git a/migrations/053_split_speed.sql b/migrations/053_split_speed.sql new file mode 100644 index 0000000..de69365 --- /dev/null +++ b/migrations/053_split_speed.sql @@ -0,0 +1,3 @@ +ALTER TABLE vpn_clients ADD COLUMN speed_up BIGINT DEFAULT 0 AFTER current_speed; +ALTER TABLE vpn_clients ADD COLUMN speed_down BIGINT DEFAULT 0 AFTER speed_up; +-- We can drop current_speed later or keep it as total diff --git a/run_migration_053.php b/run_migration_053.php new file mode 100644 index 0000000..134423c --- /dev/null +++ b/run_migration_053.php @@ -0,0 +1,13 @@ +exec($sql); + echo "Migration 053 applied successfully.\n"; +} catch (Exception $e) { + echo "Migration failed: " . $e->getMessage() . "\n"; +} diff --git a/templates/servers/view.twig b/templates/servers/view.twig index 8150940..918d52e 100644 --- a/templates/servers/view.twig +++ b/templates/servers/view.twig @@ -335,7 +335,10 @@ {% endif %} -
-
+
+ ↑ {{ (client.speed_up|default(0) / 1024)|number_format(1) }} KB/s
+ ↓ {{ (client.speed_down|default(0) / 1024)|number_format(1) }} KB/s +
{% if client.last_handshake %}