From bf3a9112f28492e9225d8ae18642de3fe33f8800 Mon Sep 17 00:00:00 2001 From: xboard Date: Sat, 24 May 2025 13:45:32 +0800 Subject: [PATCH] fix: improve node filtering by client base_version, set subscription content-type, and add hop_interval support for hysteria2 - Refactor node filtering logic to correctly handle client base_version requirements. - Set appropriate Content-Type header for subscription responses. - Add support for hop_interval configuration in hysteria2 node delivery. --- app/Http/Requests/Admin/ServerSave.php | 1 + app/Models/Server.php | 3 +- app/Protocols/Clash.php | 3 +- app/Protocols/ClashMeta.php | 3 +- app/Protocols/General.php | 2 +- app/Protocols/Loon.php | 3 +- app/Protocols/QuantumultX.php | 3 +- app/Protocols/Shadowrocket.php | 12 +++++- app/Protocols/Shadowsocks.php | 3 +- app/Protocols/SingBox.php | 22 ++++++----- app/Protocols/Stash.php | 3 +- app/Support/AbstractProtocol.php | 53 +++++++++++++++++++++----- 12 files changed, 82 insertions(+), 29 deletions(-) diff --git a/app/Http/Requests/Admin/ServerSave.php b/app/Http/Requests/Admin/ServerSave.php index 08a5d7c..868a65c 100644 --- a/app/Http/Requests/Admin/ServerSave.php +++ b/app/Http/Requests/Admin/ServerSave.php @@ -40,6 +40,7 @@ class ServerSave extends FormRequest 'tls.allow_insecure' => 'nullable|boolean', 'bandwidth.up' => 'nullable|integer', 'bandwidth.down' => 'nullable|integer', + 'hop_interval' => 'integer|nullable', ], 'vless' => [ 'tls' => 'required|integer', diff --git a/app/Models/Server.php b/app/Models/Server.php index 84ddfcf..d18088e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -178,7 +178,8 @@ class Server extends Model 'server_name' => ['type' => 'string', 'default' => null], 'allow_insecure' => ['type' => 'boolean', 'default' => false] ] - ] + ], + 'hop_interval' => ['type' => 'integer', 'default' => null] ], self::TYPE_TUIC => [ 'version' => ['type' => 'integer', 'default' => 5], diff --git a/app/Protocols/Clash.php b/app/Protocols/Clash.php index 1fd23fc..3c5df34 100644 --- a/app/Protocols/Clash.php +++ b/app/Protocols/Clash.php @@ -91,7 +91,8 @@ class Clash extends AbstractProtocol $yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); $yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml); - return response($yaml, 200) + return response($yaml) + ->header('content-type', 'text/yaml') ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}") ->header('profile-update-interval', '24') ->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName)) diff --git a/app/Protocols/ClashMeta.php b/app/Protocols/ClashMeta.php index 96661a5..b08ba66 100644 --- a/app/Protocols/ClashMeta.php +++ b/app/Protocols/ClashMeta.php @@ -154,7 +154,8 @@ class ClashMeta extends AbstractProtocol $yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); $yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml); - return response($yaml, 200) + return response($yaml) + ->header('content-type', 'text/yaml') ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}") ->header('profile-update-interval', '24') ->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName)); diff --git a/app/Protocols/General.php b/app/Protocols/General.php index 74f5864..4077a52 100644 --- a/app/Protocols/General.php +++ b/app/Protocols/General.php @@ -53,7 +53,7 @@ class General extends AbstractProtocol $uri .= self::buildSocks($user['uuid'], $item); } } - return base64_encode($uri); + return response(base64_encode($uri))->header('content-type', 'text/plain'); } public static function buildShadowsocks($password, $server) diff --git a/app/Protocols/Loon.php b/app/Protocols/Loon.php index 53f1f93..a8fd35d 100644 --- a/app/Protocols/Loon.php +++ b/app/Protocols/Loon.php @@ -41,7 +41,8 @@ class Loon extends AbstractProtocol $uri .= self::buildHysteria($user['uuid'], $item, $user); } } - return response($uri, 200) + return response($uri) + ->header('content-type', 'text/plain') ->header('Subscription-Userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); } diff --git a/app/Protocols/QuantumultX.php b/app/Protocols/QuantumultX.php index fc38e18..7af4679 100644 --- a/app/Protocols/QuantumultX.php +++ b/app/Protocols/QuantumultX.php @@ -24,7 +24,8 @@ class QuantumultX extends AbstractProtocol $uri .= self::buildTrojan($user['uuid'], $item); } } - return response(base64_encode($uri), 200) + return response(base64_encode($uri)) + ->header('content-type', 'text/plain') ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); } diff --git a/app/Protocols/Shadowrocket.php b/app/Protocols/Shadowrocket.php index db6463c..d889368 100644 --- a/app/Protocols/Shadowrocket.php +++ b/app/Protocols/Shadowrocket.php @@ -16,6 +16,9 @@ class Shadowrocket extends AbstractProtocol '2' => '1993' ], ], + 'anytls' => [ + 'base_version' => '2592' + ], ], ]; @@ -54,7 +57,8 @@ class Shadowrocket extends AbstractProtocol $uri .= self::buildAnyTLS($user['uuid'], $item); } } - return base64_encode($uri); + return response(base64_encode($uri)) + ->header('content-type', 'text/plain'); } @@ -267,8 +271,12 @@ class Shadowrocket extends AbstractProtocol $params['obfs-password'] = data_get($protocol_settings, 'obfs.password'); } $params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure'); - if (isset($server['ports'])) + if (isset($protocol_settings['hop_interval'])) { + $params['keepalive'] = $protocol_settings['hop_interval']; + } + if (isset($server['ports'])) { $params['mport'] = $server['ports']; + } $query = http_build_query($params); $addr = Helper::wrapIPv6($server['host']); diff --git a/app/Protocols/Shadowsocks.php b/app/Protocols/Shadowsocks.php index 16cb1a3..4f14796 100644 --- a/app/Protocols/Shadowsocks.php +++ b/app/Protocols/Shadowsocks.php @@ -36,7 +36,8 @@ class Shadowsocks extends AbstractProtocol $subs['bytes_remaining'] = $bytesRemaining; $subs['servers'] = array_merge($subs['servers'], $configs); - return json_encode($subs, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + return response()->json($subs) + ->header('content-type', 'application/json'); } public static function SIP008($server, $user) diff --git a/app/Protocols/SingBox.php b/app/Protocols/SingBox.php index 0c09e37..12cbaf8 100644 --- a/app/Protocols/SingBox.php +++ b/app/Protocols/SingBox.php @@ -48,6 +48,9 @@ class SingBox extends AbstractProtocol ], 'wireguard' => [ 'base_version' => '1.5.0' + ], + 'anytls' => [ + 'base_version' => '1.12.0' ] ] ]; @@ -340,15 +343,16 @@ class SingBox extends AbstractProtocol 'insecure' => (bool) $protocol_settings['tls']['allow_insecure'], ] ]; - Log::info($this->clientName); - Log::info($this->clientVersion); - // if ( - // isset($server['ports']) - // && $this->clientName == 'sfm' - // && version_compare($this->clientVersion, '1.11.0', '>=') - // ) { - // $baseConfig['server_ports'][] = str_replace('-', ':', $server['ports']); - // } + // 支持 1.11.0 版本及以上 `server_ports` 和 `hop_interval` 配置 + if ($this->supportsFeature('sing-box', '1.11.0')) { + if (isset($server['ports'])) { + $baseConfig['server_ports'] = [str_replace('-', ':', $server['ports'])]; + } + if (isset($protocol_settings['hop_interval'])) { + $baseConfig['hop_interval'] = "{$protocol_settings['hop_interval']}s"; + } + } + if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) { $baseConfig['tls']['server_name'] = $serverName; } diff --git a/app/Protocols/Stash.php b/app/Protocols/Stash.php index 4a6ddcb..d911aad 100644 --- a/app/Protocols/Stash.php +++ b/app/Protocols/Stash.php @@ -149,7 +149,8 @@ class Stash extends AbstractProtocol $yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); $yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml); - return response($yaml, 200) + return response($yaml) + ->header('content-type', 'text/yaml') ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}") ->header('profile-update-interval', '24') ->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName)); diff --git a/app/Support/AbstractProtocol.php b/app/Support/AbstractProtocol.php index 8e86827..a93d1e5 100644 --- a/app/Support/AbstractProtocol.php +++ b/app/Support/AbstractProtocol.php @@ -28,7 +28,7 @@ abstract class AbstractProtocol * @var array 协议标识 */ public $flags = []; - + /** * @var array 协议需求配置 */ @@ -48,7 +48,7 @@ abstract class AbstractProtocol $this->servers = $servers; $this->clientName = $clientName; $this->clientVersion = $clientVersion; - + // 服务器过滤逻辑 $this->servers = $this->filterServersByVersion(); } @@ -69,7 +69,7 @@ abstract class AbstractProtocol * @return mixed */ abstract public function handle(); - + /** * 根据客户端版本过滤不兼容的服务器 * @@ -103,20 +103,23 @@ abstract class AbstractProtocol $serverType = $server['type'] ?? null; // 如果该协议没有特定要求,则认为兼容 if (!isset($this->protocolRequirements[$this->clientName][$serverType])) { - return true; } - + $requirements = $this->protocolRequirements[$this->clientName][$serverType]; - + + if (isset($requirements['base_version']) && version_compare($this->clientVersion, $requirements['base_version'], '<')) { + return false; + } + // 检查每个路径的版本要求 foreach ($requirements as $path => $valueRequirements) { $actualValue = data_get($server, $path); - + if ($actualValue === null) { continue; } - + if (isset($valueRequirements[$actualValue])) { $requiredVersion = $valueRequirements[$actualValue]; if (version_compare($this->clientVersion, $requiredVersion, '<')) { @@ -124,7 +127,37 @@ abstract class AbstractProtocol } } } - + return true; } -} \ No newline at end of file + + /** + * 检查当前客户端是否支持特定功能 + * + * @param string $clientName 客户端名称 + * @param string $minVersion 最低版本要求 + * @param array $additionalConditions 额外条件检查 + * @return bool + */ + protected function supportsFeature(string $clientName, string $minVersion, array $additionalConditions = []): bool + { + // 检查客户端名称 + if ($this->clientName !== $clientName) { + return false; + } + + // 检查版本号 + if (empty($this->clientVersion) || version_compare($this->clientVersion, $minVersion, '<')) { + return false; + } + + // 检查额外条件 + foreach ($additionalConditions as $condition) { + if (!$condition) { + return false; + } + } + + return true; + } +} \ No newline at end of file