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.
This commit is contained in:
@@ -40,6 +40,7 @@ class ServerSave extends FormRequest
|
|||||||
'tls.allow_insecure' => 'nullable|boolean',
|
'tls.allow_insecure' => 'nullable|boolean',
|
||||||
'bandwidth.up' => 'nullable|integer',
|
'bandwidth.up' => 'nullable|integer',
|
||||||
'bandwidth.down' => 'nullable|integer',
|
'bandwidth.down' => 'nullable|integer',
|
||||||
|
'hop_interval' => 'integer|nullable',
|
||||||
],
|
],
|
||||||
'vless' => [
|
'vless' => [
|
||||||
'tls' => 'required|integer',
|
'tls' => 'required|integer',
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ class Server extends Model
|
|||||||
'server_name' => ['type' => 'string', 'default' => null],
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
'allow_insecure' => ['type' => 'boolean', 'default' => false]
|
'allow_insecure' => ['type' => 'boolean', 'default' => false]
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
'hop_interval' => ['type' => 'integer', 'default' => null]
|
||||||
],
|
],
|
||||||
self::TYPE_TUIC => [
|
self::TYPE_TUIC => [
|
||||||
'version' => ['type' => 'integer', 'default' => 5],
|
'version' => ['type' => 'integer', 'default' => 5],
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ class Clash extends AbstractProtocol
|
|||||||
|
|
||||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
$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('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||||
->header('profile-update-interval', '24')
|
->header('profile-update-interval', '24')
|
||||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName))
|
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName))
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ class ClashMeta extends AbstractProtocol
|
|||||||
|
|
||||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
$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('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||||
->header('profile-update-interval', '24')
|
->header('profile-update-interval', '24')
|
||||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class General extends AbstractProtocol
|
|||||||
$uri .= self::buildSocks($user['uuid'], $item);
|
$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)
|
public static function buildShadowsocks($password, $server)
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class Loon extends AbstractProtocol
|
|||||||
$uri .= self::buildHysteria($user['uuid'], $item, $user);
|
$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']}");
|
->header('Subscription-Userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class QuantumultX extends AbstractProtocol
|
|||||||
$uri .= self::buildTrojan($user['uuid'], $item);
|
$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']}");
|
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
'2' => '1993'
|
'2' => '1993'
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'anytls' => [
|
||||||
|
'base_version' => '2592'
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -54,7 +57,8 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
$uri .= self::buildAnyTLS($user['uuid'], $item);
|
$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['obfs-password'] = data_get($protocol_settings, 'obfs.password');
|
||||||
}
|
}
|
||||||
$params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure');
|
$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'];
|
$params['mport'] = $server['ports'];
|
||||||
|
}
|
||||||
$query = http_build_query($params);
|
$query = http_build_query($params);
|
||||||
$addr = Helper::wrapIPv6($server['host']);
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class Shadowsocks extends AbstractProtocol
|
|||||||
$subs['bytes_remaining'] = $bytesRemaining;
|
$subs['bytes_remaining'] = $bytesRemaining;
|
||||||
$subs['servers'] = array_merge($subs['servers'], $configs);
|
$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)
|
public static function SIP008($server, $user)
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class SingBox extends AbstractProtocol
|
|||||||
],
|
],
|
||||||
'wireguard' => [
|
'wireguard' => [
|
||||||
'base_version' => '1.5.0'
|
'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'],
|
'insecure' => (bool) $protocol_settings['tls']['allow_insecure'],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
Log::info($this->clientName);
|
// 支持 1.11.0 版本及以上 `server_ports` 和 `hop_interval` 配置
|
||||||
Log::info($this->clientVersion);
|
if ($this->supportsFeature('sing-box', '1.11.0')) {
|
||||||
// if (
|
if (isset($server['ports'])) {
|
||||||
// isset($server['ports'])
|
$baseConfig['server_ports'] = [str_replace('-', ':', $server['ports'])];
|
||||||
// && $this->clientName == 'sfm'
|
}
|
||||||
// && version_compare($this->clientVersion, '1.11.0', '>=')
|
if (isset($protocol_settings['hop_interval'])) {
|
||||||
// ) {
|
$baseConfig['hop_interval'] = "{$protocol_settings['hop_interval']}s";
|
||||||
// $baseConfig['server_ports'][] = str_replace('-', ':', $server['ports']);
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||||
$baseConfig['tls']['server_name'] = $serverName;
|
$baseConfig['tls']['server_name'] = $serverName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ class Stash extends AbstractProtocol
|
|||||||
|
|
||||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
$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('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||||
->header('profile-update-interval', '24')
|
->header('profile-update-interval', '24')
|
||||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ abstract class AbstractProtocol
|
|||||||
* @var array 协议标识
|
* @var array 协议标识
|
||||||
*/
|
*/
|
||||||
public $flags = [];
|
public $flags = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array 协议需求配置
|
* @var array 协议需求配置
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +48,7 @@ abstract class AbstractProtocol
|
|||||||
$this->servers = $servers;
|
$this->servers = $servers;
|
||||||
$this->clientName = $clientName;
|
$this->clientName = $clientName;
|
||||||
$this->clientVersion = $clientVersion;
|
$this->clientVersion = $clientVersion;
|
||||||
|
|
||||||
// 服务器过滤逻辑
|
// 服务器过滤逻辑
|
||||||
$this->servers = $this->filterServersByVersion();
|
$this->servers = $this->filterServersByVersion();
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ abstract class AbstractProtocol
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
abstract public function handle();
|
abstract public function handle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据客户端版本过滤不兼容的服务器
|
* 根据客户端版本过滤不兼容的服务器
|
||||||
*
|
*
|
||||||
@@ -103,20 +103,23 @@ abstract class AbstractProtocol
|
|||||||
$serverType = $server['type'] ?? null;
|
$serverType = $server['type'] ?? null;
|
||||||
// 如果该协议没有特定要求,则认为兼容
|
// 如果该协议没有特定要求,则认为兼容
|
||||||
if (!isset($this->protocolRequirements[$this->clientName][$serverType])) {
|
if (!isset($this->protocolRequirements[$this->clientName][$serverType])) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$requirements = $this->protocolRequirements[$this->clientName][$serverType];
|
$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) {
|
foreach ($requirements as $path => $valueRequirements) {
|
||||||
$actualValue = data_get($server, $path);
|
$actualValue = data_get($server, $path);
|
||||||
|
|
||||||
if ($actualValue === null) {
|
if ($actualValue === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($valueRequirements[$actualValue])) {
|
if (isset($valueRequirements[$actualValue])) {
|
||||||
$requiredVersion = $valueRequirements[$actualValue];
|
$requiredVersion = $valueRequirements[$actualValue];
|
||||||
if (version_compare($this->clientVersion, $requiredVersion, '<')) {
|
if (version_compare($this->clientVersion, $requiredVersion, '<')) {
|
||||||
@@ -124,7 +127,37 @@ abstract class AbstractProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 检查当前客户端是否支持特定功能
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user