mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-03 10:30:51 +08:00
feat: Trojan Reality support and protocol distribution optimizations
This commit is contained in:
@@ -80,7 +80,8 @@ class ClientController extends Controller
|
|||||||
'user' => $user,
|
'user' => $user,
|
||||||
'servers' => $serversFiltered,
|
'servers' => $serversFiltered,
|
||||||
'clientName' => $clientInfo['name'] ?? null,
|
'clientName' => $clientInfo['name'] ?? null,
|
||||||
'clientVersion' => $clientInfo['version'] ?? null
|
'clientVersion' => $clientInfo['version'] ?? null,
|
||||||
|
'userAgent' => $clientInfo['flag'] ?? null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $protocolInstance->handle();
|
return $protocolInstance->handle();
|
||||||
|
|||||||
@@ -42,10 +42,17 @@ class ServerSave extends FormRequest
|
|||||||
'tls_settings.allow_insecure' => 'nullable|boolean',
|
'tls_settings.allow_insecure' => 'nullable|boolean',
|
||||||
],
|
],
|
||||||
'trojan' => [
|
'trojan' => [
|
||||||
|
'tls' => 'nullable|integer',
|
||||||
'network' => 'required|string',
|
'network' => 'required|string',
|
||||||
'network_settings' => 'nullable|array',
|
'network_settings' => 'nullable|array',
|
||||||
'server_name' => 'nullable|string',
|
'server_name' => 'nullable|string',
|
||||||
'allow_insecure' => 'nullable|boolean',
|
'allow_insecure' => 'nullable|boolean',
|
||||||
|
'reality_settings.allow_insecure' => 'nullable|boolean',
|
||||||
|
'reality_settings.server_name' => 'nullable|string',
|
||||||
|
'reality_settings.server_port' => 'nullable|integer',
|
||||||
|
'reality_settings.public_key' => 'nullable|string',
|
||||||
|
'reality_settings.private_key' => 'nullable|string',
|
||||||
|
'reality_settings.short_id' => 'nullable|string',
|
||||||
],
|
],
|
||||||
'hysteria' => [
|
'hysteria' => [
|
||||||
'version' => 'required|integer',
|
'version' => 'required|integer',
|
||||||
|
|||||||
@@ -148,6 +148,20 @@ class Server extends Model
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const REALITY_CONFIGURATION = [
|
||||||
|
'reality_settings' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'fields' => [
|
||||||
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
|
'server_port' => ['type' => 'string', 'default' => null],
|
||||||
|
'public_key' => ['type' => 'string', 'default' => null],
|
||||||
|
'private_key' => ['type' => 'string', 'default' => null],
|
||||||
|
'short_id' => ['type' => 'string', 'default' => null],
|
||||||
|
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
private const UTLS_CONFIGURATION = [
|
private const UTLS_CONFIGURATION = [
|
||||||
'utls' => [
|
'utls' => [
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
@@ -160,10 +174,12 @@ class Server extends Model
|
|||||||
|
|
||||||
private const PROTOCOL_CONFIGURATIONS = [
|
private const PROTOCOL_CONFIGURATIONS = [
|
||||||
self::TYPE_TROJAN => [
|
self::TYPE_TROJAN => [
|
||||||
|
'tls' => ['type' => 'integer', 'default' => 1],
|
||||||
'network' => ['type' => 'string', 'default' => null],
|
'network' => ['type' => 'string', 'default' => null],
|
||||||
'network_settings' => ['type' => 'array', 'default' => null],
|
'network_settings' => ['type' => 'array', 'default' => null],
|
||||||
'server_name' => ['type' => 'string', 'default' => null],
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||||
|
...self::REALITY_CONFIGURATION,
|
||||||
...self::MULTIPLEX_CONFIGURATION,
|
...self::MULTIPLEX_CONFIGURATION,
|
||||||
...self::UTLS_CONFIGURATION
|
...self::UTLS_CONFIGURATION
|
||||||
],
|
],
|
||||||
@@ -182,17 +198,7 @@ class Server extends Model
|
|||||||
'flow' => ['type' => 'string', 'default' => null],
|
'flow' => ['type' => 'string', 'default' => null],
|
||||||
'network' => ['type' => 'string', 'default' => null],
|
'network' => ['type' => 'string', 'default' => null],
|
||||||
'network_settings' => ['type' => 'array', 'default' => null],
|
'network_settings' => ['type' => 'array', 'default' => null],
|
||||||
'reality_settings' => [
|
...self::REALITY_CONFIGURATION,
|
||||||
'type' => 'object',
|
|
||||||
'fields' => [
|
|
||||||
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
|
||||||
'server_port' => ['type' => 'string', 'default' => null],
|
|
||||||
'server_name' => ['type' => 'string', 'default' => null],
|
|
||||||
'public_key' => ['type' => 'string', 'default' => null],
|
|
||||||
'private_key' => ['type' => 'string', 'default' => null],
|
|
||||||
'short_id' => ['type' => 'string', 'default' => null]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
...self::MULTIPLEX_CONFIGURATION,
|
...self::MULTIPLEX_CONFIGURATION,
|
||||||
...self::UTLS_CONFIGURATION
|
...self::UTLS_CONFIGURATION
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -199,8 +199,9 @@ class Clash extends AbstractProtocol
|
|||||||
|
|
||||||
switch (data_get($protocol_settings, 'network')) {
|
switch (data_get($protocol_settings, 'network')) {
|
||||||
case 'tcp':
|
case 'tcp':
|
||||||
$array['network'] = data_get($protocol_settings, 'network_settings.header.type');
|
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||||
if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
|
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||||
|
if ($headerType === 'http') {
|
||||||
if ($httpOpts = array_filter([
|
if ($httpOpts = array_filter([
|
||||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||||
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
||||||
|
|||||||
@@ -271,8 +271,9 @@ class ClashMeta extends AbstractProtocol
|
|||||||
|
|
||||||
switch (data_get($protocol_settings, 'network')) {
|
switch (data_get($protocol_settings, 'network')) {
|
||||||
case 'tcp':
|
case 'tcp':
|
||||||
$array['network'] = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||||
if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
|
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||||
|
if ($headerType === 'http') {
|
||||||
if (
|
if (
|
||||||
$httpOpts = array_filter([
|
$httpOpts = array_filter([
|
||||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||||
@@ -420,10 +421,26 @@ class ClashMeta extends AbstractProtocol
|
|||||||
'port' => $server['port'],
|
'port' => $server['port'],
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'udp' => true,
|
'udp' => true,
|
||||||
'skip-cert-verify' => (bool) data_get($protocol_settings, 'allow_insecure', false)
|
|
||||||
];
|
];
|
||||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
|
||||||
$array['sni'] = $serverName;
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
|
switch ($tlsMode) {
|
||||||
|
case 2: // Reality
|
||||||
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false);
|
||||||
|
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
||||||
|
$array['sni'] = $serverName;
|
||||||
|
}
|
||||||
|
$array['reality-opts'] = [
|
||||||
|
'public-key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
||||||
|
'short-id' => data_get($protocol_settings, 'reality_settings.short_id'),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'allow_insecure', false);
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$array['sni'] = $serverName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self::appendUtls($array, $protocol_settings);
|
self::appendUtls($array, $protocol_settings);
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ class General extends AbstractProtocol
|
|||||||
default => '',
|
default => '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return response(base64_encode($uri))->header('content-type', 'text/plain');
|
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']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildShadowsocks($password, $server)
|
public static function buildShadowsocks($password, $server)
|
||||||
@@ -66,7 +68,7 @@ class General extends AbstractProtocol
|
|||||||
$plugin_opts = data_get($protocol_settings, 'plugin_opts');
|
$plugin_opts = data_get($protocol_settings, 'plugin_opts');
|
||||||
$url = "ss://{$str}@{$addr}:{$server['port']}";
|
$url = "ss://{$str}@{$addr}:{$server['port']}";
|
||||||
if ($plugin && $plugin_opts) {
|
if ($plugin && $plugin_opts) {
|
||||||
$url .= '/?' . 'plugin=' . $plugin . ';' . rawurlencode($plugin_opts);
|
$url .= '/?' . 'plugin=' . rawurlencode($plugin . ';' . $plugin_opts);
|
||||||
}
|
}
|
||||||
$url .= "#{$name}\r\n";
|
$url .= "#{$name}\r\n";
|
||||||
return $url;
|
return $url;
|
||||||
@@ -91,6 +93,9 @@ class General extends AbstractProtocol
|
|||||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||||
$config['sni'] = $serverName;
|
$config['sni'] = $serverName;
|
||||||
}
|
}
|
||||||
|
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||||
|
$config['fp'] = $fp;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($protocol_settings['network']) {
|
switch ($protocol_settings['network']) {
|
||||||
case 'tcp':
|
case 'tcp':
|
||||||
@@ -148,7 +153,7 @@ class General extends AbstractProtocol
|
|||||||
'security' => '', //传输层安全 tls/reality
|
'security' => '', //传输层安全 tls/reality
|
||||||
'encryption' => 'none', //加密方式
|
'encryption' => 'none', //加密方式
|
||||||
'type' => $server['protocol_settings']['network'], //传输协议
|
'type' => $server['protocol_settings']['network'], //传输协议
|
||||||
'flow' => $protocol_settings['flow'] ? $protocol_settings['flow'] : null,
|
'flow' => data_get($protocol_settings, 'flow'),
|
||||||
];
|
];
|
||||||
// 处理TLS
|
// 处理TLS
|
||||||
switch ($server['protocol_settings']['tls']) {
|
switch ($server['protocol_settings']['tls']) {
|
||||||
@@ -160,6 +165,9 @@ class General extends AbstractProtocol
|
|||||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||||
$config['sni'] = $serverName;
|
$config['sni'] = $serverName;
|
||||||
}
|
}
|
||||||
|
if (data_get($protocol_settings, 'tls_settings.allow_insecure')) {
|
||||||
|
$config['allowInsecure'] = '1';
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2: //reality
|
case 2: //reality
|
||||||
$config['security'] = "reality";
|
$config['security'] = "reality";
|
||||||
@@ -224,11 +232,30 @@ class General extends AbstractProtocol
|
|||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$name = rawurlencode($server['name']);
|
$name = rawurlencode($server['name']);
|
||||||
$array = [];
|
$array = [];
|
||||||
$array['allowInsecure'] = $protocol_settings['allow_insecure'];
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
|
||||||
$array['peer'] = $serverName;
|
switch ($tlsMode) {
|
||||||
$array['sni'] = $serverName;
|
case 2: // Reality
|
||||||
|
$array['security'] = 'reality';
|
||||||
|
$array['pbk'] = data_get($protocol_settings, 'reality_settings.public_key');
|
||||||
|
$array['sid'] = data_get($protocol_settings, 'reality_settings.short_id');
|
||||||
|
$array['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||||
|
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||||
|
$array['fp'] = $fp;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
$array['allowInsecure'] = data_get($protocol_settings, 'allow_insecure', false);
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$array['peer'] = $serverName;
|
||||||
|
$array['sni'] = $serverName;
|
||||||
|
}
|
||||||
|
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||||
|
$array['fp'] = $fp;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($server['protocol_settings']['network']) {
|
switch ($server['protocol_settings']['network']) {
|
||||||
case 'ws':
|
case 'ws':
|
||||||
$array['type'] = 'ws';
|
$array['type'] = 'ws';
|
||||||
@@ -299,8 +326,10 @@ class General extends AbstractProtocol
|
|||||||
$params['upmbps'] = $upMbps;
|
$params['upmbps'] = $upMbps;
|
||||||
if ($downMbps = data_get($protocol_settings, 'bandwidth.down'))
|
if ($downMbps = data_get($protocol_settings, 'bandwidth.down'))
|
||||||
$params['downmbps'] = $downMbps;
|
$params['downmbps'] = $downMbps;
|
||||||
if ($obfsPassword = data_get($protocol_settings, 'obfs.password'))
|
if (data_get($protocol_settings, 'obfs.open') && ($obfsPassword = data_get($protocol_settings, 'obfs.password'))) {
|
||||||
|
$params['obfs'] = 'xplus';
|
||||||
$params['obfsParam'] = $obfsPassword;
|
$params['obfsParam'] = $obfsPassword;
|
||||||
|
}
|
||||||
|
|
||||||
$query = http_build_query($params);
|
$query = http_build_query($params);
|
||||||
$uri = "hysteria://{$addr}:{$server['port']}?{$query}#{$name}";
|
$uri = "hysteria://{$addr}:{$server['port']}?{$query}#{$name}";
|
||||||
@@ -309,8 +338,8 @@ class General extends AbstractProtocol
|
|||||||
|
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function buildTuic($password, $server)
|
public static function buildTuic($password, $server)
|
||||||
{
|
{
|
||||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||||
@@ -344,6 +373,10 @@ class General extends AbstractProtocol
|
|||||||
$udpRelay = data_get($protocol_settings, 'udp_relay_mode', 'native');
|
$udpRelay = data_get($protocol_settings, 'udp_relay_mode', 'native');
|
||||||
$queryParams['udp-relay-mode'] = $udpRelay;
|
$queryParams['udp-relay-mode'] = $udpRelay;
|
||||||
|
|
||||||
|
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||||
|
$queryParams['insecure'] = '1';
|
||||||
|
}
|
||||||
|
|
||||||
$query = http_build_query($queryParams);
|
$query = http_build_query($queryParams);
|
||||||
|
|
||||||
// 构造完整URI,格式:
|
// 构造完整URI,格式:
|
||||||
@@ -361,7 +394,7 @@ class General extends AbstractProtocol
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static function buildAnyTLS($password, $server)
|
public static function buildAnyTLS($password, $server)
|
||||||
{
|
{
|
||||||
@@ -372,16 +405,18 @@ class General extends AbstractProtocol
|
|||||||
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
||||||
];
|
];
|
||||||
$query = http_build_query($params);
|
$query = http_build_query($params);
|
||||||
$uri = "anytls://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$uri = "anytls://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||||
$uri .= "\r\n";
|
$uri .= "\r\n";
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildSocks($password, $server)
|
public static function buildSocks($password, $server)
|
||||||
{
|
{
|
||||||
$name = rawurlencode($server['name']);
|
$name = rawurlencode($server['name']);
|
||||||
$credentials = base64_encode("{$password}:{$password}");
|
$credentials = base64_encode("{$password}:{$password}");
|
||||||
return "socks://{$credentials}@{$server['host']}:{$server['port']}#{$name}\r\n";
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
return "socks://{$credentials}@{$addr}:{$server['port']}#{$name}\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildHttp($password, $server)
|
public static function buildHttp($password, $server)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Loon extends AbstractProtocol
|
|||||||
|
|
||||||
protected $protocolRequirements = [
|
protected $protocolRequirements = [
|
||||||
'loon.hysteria.protocol_settings.version' => [2 => '637'],
|
'loon.hysteria.protocol_settings.version' => [2 => '637'],
|
||||||
|
'loon.trojan.protocol_settings.tls' => [0 => '3.2.1', 1 => '3.2.1',2 => '999.9.9'],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@@ -115,11 +116,10 @@ class Loon extends AbstractProtocol
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (data_get($protocol_settings, 'tls')) {
|
if (data_get($protocol_settings, 'tls')) {
|
||||||
if (data_get($protocol_settings, 'network') === 'tcp')
|
$config[] = 'over-tls=true';
|
||||||
$config[] = 'over-tls=true';
|
|
||||||
if (data_get($protocol_settings, 'tls_settings')) {
|
if (data_get($protocol_settings, 'tls_settings')) {
|
||||||
$tls_settings = data_get($protocol_settings, 'tls_settings');
|
$tls_settings = data_get($protocol_settings, 'tls_settings');
|
||||||
$config[] = 'skip-cert-verify=' . ($tls_settings['allow_insecure'] ? 'true' : 'false');
|
$config[] = 'skip-cert-verify=' . (data_get($tls_settings, 'allow_insecure') ? 'true' : 'false');
|
||||||
if (data_get($tls_settings, 'server_name'))
|
if (data_get($tls_settings, 'server_name'))
|
||||||
$config[] = "tls-name={$tls_settings['server_name']}";
|
$config[] = "tls-name={$tls_settings['server_name']}";
|
||||||
}
|
}
|
||||||
@@ -150,8 +150,25 @@ class Loon extends AbstractProtocol
|
|||||||
if (data_get($wsSettings, key: 'headers.Host'))
|
if (data_get($wsSettings, key: 'headers.Host'))
|
||||||
$config[] = "host={$wsSettings['headers']['Host']}";
|
$config[] = "host={$wsSettings['headers']['Host']}";
|
||||||
break;
|
break;
|
||||||
|
case 'grpc':
|
||||||
|
$config[] = 'transport=grpc';
|
||||||
|
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||||
|
$config[] = "grpc-service-name={$serviceName}";
|
||||||
|
break;
|
||||||
|
case 'h2':
|
||||||
|
$config[] = 'transport=h2';
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||||
|
$config[] = "path={$path}";
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||||
|
$config[] = "host=" . (is_array($host) ? $host[0] : $host);
|
||||||
|
break;
|
||||||
|
case 'httpupgrade':
|
||||||
|
$config[] = 'transport=httpupgrade';
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||||
|
$config[] = "path={$path}";
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||||
|
$config[] = "host={$host}";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = implode(',', $config);
|
$uri = implode(',', $config);
|
||||||
@@ -167,13 +184,45 @@ class Loon extends AbstractProtocol
|
|||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"{$password}",
|
"{$password}",
|
||||||
data_get($protocol_settings, 'server_name') ? "tls-name={$protocol_settings['server_name']}" : "",
|
|
||||||
'fast-open=false',
|
|
||||||
'udp=true'
|
|
||||||
];
|
];
|
||||||
if (!empty($protocol_settings['allow_insecure'])) {
|
|
||||||
$config[] = data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false';
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
|
switch ($tlsMode) {
|
||||||
|
case 2: // Reality
|
||||||
|
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
||||||
|
$config[] = "tls-name={$serverName}";
|
||||||
|
}
|
||||||
|
if ($pubkey = data_get($protocol_settings, 'reality_settings.public_key')) {
|
||||||
|
$config[] = "public-key={$pubkey}";
|
||||||
|
}
|
||||||
|
if ($shortid = data_get($protocol_settings, 'reality_settings.short_id')) {
|
||||||
|
$config[] = "short-id={$shortid}";
|
||||||
|
}
|
||||||
|
$config[] = 'skip-cert-verify=' . (data_get($protocol_settings, 'reality_settings.allow_insecure', false) ? 'true' : 'false');
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$config[] = "tls-name={$serverName}";
|
||||||
|
}
|
||||||
|
$config[] = 'skip-cert-verify=' . (data_get($protocol_settings, 'allow_insecure') ? 'true' : 'false');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (data_get($protocol_settings, 'network', 'tcp')) {
|
||||||
|
case 'ws':
|
||||||
|
$config[] = 'transport=ws';
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||||
|
$config[] = "path={$path}";
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||||
|
$config[] = "host={$host}";
|
||||||
|
break;
|
||||||
|
case 'grpc':
|
||||||
|
$config[] = 'transport=grpc';
|
||||||
|
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||||
|
$config[] = "grpc-service-name={$serviceName}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$config = array_filter($config);
|
$config = array_filter($config);
|
||||||
$uri = implode(',', $config);
|
$uri = implode(',', $config);
|
||||||
$uri .= "\r\n";
|
$uri .= "\r\n";
|
||||||
@@ -267,7 +316,9 @@ class Loon extends AbstractProtocol
|
|||||||
];
|
];
|
||||||
if (data_get($protocol_settings, 'tls.allow_insecure'))
|
if (data_get($protocol_settings, 'tls.allow_insecure'))
|
||||||
$config[] = "skip-cert-verify=true";
|
$config[] = "skip-cert-verify=true";
|
||||||
$config[] = "download-bandwidth=" . data_get($protocol_settings, 'bandwidth.download_bandwidth');
|
if ($down = data_get($protocol_settings, 'bandwidth.down')) {
|
||||||
|
$config[] = "download-bandwidth={$down}";
|
||||||
|
}
|
||||||
$config[] = "udp=true";
|
$config[] = "udp=true";
|
||||||
$config = array_filter($config);
|
$config = array_filter($config);
|
||||||
$uri = implode(',', $config);
|
$uri = implode(',', $config);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Protocols;
|
namespace App\Protocols;
|
||||||
|
|
||||||
|
use App\Utils\Helper;
|
||||||
use App\Support\AbstractProtocol;
|
use App\Support\AbstractProtocol;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
|
||||||
@@ -11,7 +12,10 @@ class QuantumultX extends AbstractProtocol
|
|||||||
public $allowedProtocols = [
|
public $allowedProtocols = [
|
||||||
Server::TYPE_SHADOWSOCKS,
|
Server::TYPE_SHADOWSOCKS,
|
||||||
Server::TYPE_VMESS,
|
Server::TYPE_VMESS,
|
||||||
|
Server::TYPE_VLESS,
|
||||||
Server::TYPE_TROJAN,
|
Server::TYPE_TROJAN,
|
||||||
|
Server::TYPE_SOCKS,
|
||||||
|
Server::TYPE_HTTP,
|
||||||
];
|
];
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@@ -20,15 +24,15 @@ class QuantumultX extends AbstractProtocol
|
|||||||
$user = $this->user;
|
$user = $this->user;
|
||||||
$uri = '';
|
$uri = '';
|
||||||
foreach ($servers as $item) {
|
foreach ($servers as $item) {
|
||||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
$uri .= match ($item['type']) {
|
||||||
$uri .= self::buildShadowsocks($item['password'], $item);
|
Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item),
|
||||||
}
|
Server::TYPE_VMESS => self::buildVmess($item['password'], $item),
|
||||||
if ($item['type'] === Server::TYPE_VMESS) {
|
Server::TYPE_VLESS => self::buildVless($item['password'], $item),
|
||||||
$uri .= self::buildVmess($item['password'], $item);
|
Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item),
|
||||||
}
|
Server::TYPE_SOCKS => self::buildSocks5($item['password'], $item),
|
||||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
Server::TYPE_HTTP => self::buildHttp($item['password'], $item),
|
||||||
$uri .= self::buildTrojan($item['password'], $item);
|
default => ''
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return response(base64_encode($uri))
|
return response(base64_encode($uri))
|
||||||
->header('content-type', 'text/plain')
|
->header('content-type', 'text/plain')
|
||||||
@@ -39,18 +43,16 @@ class QuantumultX extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$password = data_get($server, 'password', $password);
|
$password = data_get($server, 'password', $password);
|
||||||
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
$config = [
|
$config = [
|
||||||
"shadowsocks={$server['host']}:{$server['port']}",
|
"shadowsocks={$addr}:{$server['port']}",
|
||||||
"method={$protocol_settings['cipher']}",
|
"method={$protocol_settings['cipher']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
'fast-open=true',
|
|
||||||
'udp-relay=true',
|
|
||||||
"tag={$server['name']}"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||||
$plugin = data_get($protocol_settings, 'plugin');
|
$plugin = data_get($protocol_settings, 'plugin');
|
||||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||||
// 解析插件选项
|
|
||||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||||
->filter()
|
->filter()
|
||||||
->mapWithKeys(function ($pair) {
|
->mapWithKeys(function ($pair) {
|
||||||
@@ -61,83 +63,170 @@ class QuantumultX extends AbstractProtocol
|
|||||||
return [trim($key) => trim($value)];
|
return [trim($key) => trim($value)];
|
||||||
})
|
})
|
||||||
->all();
|
->all();
|
||||||
switch ($plugin) {
|
if ($plugin === 'obfs') {
|
||||||
case 'obfs':
|
if (isset($parsedOpts['obfs'])) {
|
||||||
$config[] = "obfs={$parsedOpts['obfs']}";
|
$config[] = "obfs={$parsedOpts['obfs']}";
|
||||||
if (isset($parsedOpts['obfs-host'])) {
|
}
|
||||||
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
if (isset($parsedOpts['obfs-host'])) {
|
||||||
}
|
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
||||||
if (isset($parsedOpts['path'])) {
|
}
|
||||||
$config[] = "obfs-uri={$parsedOpts['path']}";
|
if (isset($parsedOpts['path'])) {
|
||||||
}
|
$config[] = "obfs-uri={$parsedOpts['path']}";
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$uri = implode(',', $config);
|
|
||||||
$uri .= "\r\n";
|
self::applyCommonSettings($config, $server);
|
||||||
return $uri;
|
|
||||||
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildVmess($uuid, $server)
|
public static function buildVmess($uuid, $server)
|
||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
$config = [
|
$config = [
|
||||||
"vmess={$server['host']}:{$server['port']}",
|
"vmess={$addr}:{$server['port']}",
|
||||||
'method=chacha20-poly1305',
|
"method=" . data_get($protocol_settings, 'cipher', 'auto'),
|
||||||
"password={$uuid}",
|
"password={$uuid}",
|
||||||
'fast-open=true',
|
|
||||||
'udp-relay=true',
|
|
||||||
"tag={$server['name']}"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (data_get($protocol_settings, 'tls')) {
|
self::applyTransportSettings($config, $protocol_settings);
|
||||||
if (data_get($protocol_settings, 'network') === 'tcp')
|
self::applyCommonSettings($config, $server);
|
||||||
array_push($config, 'obfs=over-tls');
|
|
||||||
if (data_get($protocol_settings, 'tls_settings')) {
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
if (data_get($protocol_settings, 'tls_settings.allow_insecure'))
|
}
|
||||||
array_push($config, 'tls-verification=' . ($protocol_settings['tls_settings']['allow_insecure'] ? 'false' : 'true'));
|
|
||||||
if (data_get($protocol_settings, 'tls_settings.server_name'))
|
public static function buildVless($uuid, $server)
|
||||||
$host = data_get($protocol_settings, 'tls_settings.server_name');
|
{
|
||||||
}
|
$protocol_settings = $server['protocol_settings'];
|
||||||
}
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
if (data_get($protocol_settings, 'network') === 'ws') {
|
$config = [
|
||||||
if (data_get($protocol_settings, 'tls'))
|
"vless={$addr}:{$server['port']}",
|
||||||
array_push($config, 'obfs=wss');
|
'method=none',
|
||||||
else
|
"password={$uuid}",
|
||||||
array_push($config, 'obfs=ws');
|
];
|
||||||
if (data_get($protocol_settings, 'network_settings')) {
|
|
||||||
if (data_get($protocol_settings, 'network_settings.path'))
|
self::applyTransportSettings($config, $protocol_settings);
|
||||||
array_push($config, "obfs-uri={$protocol_settings['network_settings']['path']}");
|
|
||||||
if (data_get($protocol_settings, 'network_settings.headers.Host') && !isset($host))
|
if ($flow = data_get($protocol_settings, 'flow')) {
|
||||||
$host = data_get($protocol_settings, 'network_settings.headers.Host');
|
$config[] = "vless-flow={$flow}";
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isset($host)) {
|
|
||||||
array_push($config, "obfs-host={$host}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$uri = implode(',', $config);
|
self::applyCommonSettings($config, $server);
|
||||||
$uri .= "\r\n";
|
|
||||||
return $uri;
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function applyTransportSettings(&$config, $settings, bool $nativeTls = false, ?array $tlsData = null)
|
||||||
|
{
|
||||||
|
$tlsMode = (int) data_get($settings, 'tls', 0);
|
||||||
|
$network = data_get($settings, 'network', 'tcp');
|
||||||
|
$host = null;
|
||||||
|
$isWs = $network === 'ws';
|
||||||
|
|
||||||
|
switch ($network) {
|
||||||
|
case 'ws':
|
||||||
|
$config[] = $tlsMode ? 'obfs=wss' : 'obfs=ws';
|
||||||
|
if ($path = data_get($settings, 'network_settings.path')) {
|
||||||
|
$config[] = "obfs-uri={$path}";
|
||||||
|
}
|
||||||
|
$host = data_get($settings, 'network_settings.headers.Host');
|
||||||
|
break;
|
||||||
|
case 'tcp':
|
||||||
|
$headerType = data_get($settings, 'network_settings.header.type', 'tcp');
|
||||||
|
if ($headerType === 'http') {
|
||||||
|
$config[] = 'obfs=http';
|
||||||
|
$paths = data_get($settings, 'network_settings.header.request.path', ['/']);
|
||||||
|
$config[] = 'obfs-uri=' . (is_array($paths) ? ($paths[0] ?? '/') : $paths);
|
||||||
|
$hostVal = data_get($settings, 'network_settings.header.request.headers.Host');
|
||||||
|
$host = is_array($hostVal) ? ($hostVal[0] ?? null) : $hostVal;
|
||||||
|
} elseif ($tlsMode) {
|
||||||
|
$config[] = $nativeTls ? 'over-tls=true' : 'obfs=over-tls';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($tlsMode) {
|
||||||
|
case 2: // Reality
|
||||||
|
$host = $host ?? data_get($settings, 'reality_settings.server_name');
|
||||||
|
if ($pubKey = data_get($settings, 'reality_settings.public_key')) {
|
||||||
|
$config[] = "reality-base64-pubkey={$pubKey}";
|
||||||
|
}
|
||||||
|
if ($shortId = data_get($settings, 'reality_settings.short_id')) {
|
||||||
|
$config[] = "reality-hex-shortid={$shortId}";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: // TLS
|
||||||
|
$resolved = $tlsData ?? (array) data_get($settings, 'tls_settings', []);
|
||||||
|
$allowInsecure = (bool) ($resolved['allow_insecure'] ?? false);
|
||||||
|
$config[] = 'tls-verification=' . ($allowInsecure ? 'false' : 'true');
|
||||||
|
$host = $host ?? ($resolved['server_name'] ?? null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($host) {
|
||||||
|
$config[] = ($nativeTls && !$isWs) ? "tls-host={$host}" : "obfs-host={$host}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function applyCommonSettings(&$config, $server)
|
||||||
|
{
|
||||||
|
$config[] = 'fast-open=true';
|
||||||
|
if ($server['type'] !== Server::TYPE_HTTP) {
|
||||||
|
$config[] = 'udp-relay=true';
|
||||||
|
}
|
||||||
|
$config[] = "tag={$server['name']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildTrojan($password, $server)
|
public static function buildTrojan($password, $server)
|
||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
$config = [
|
$config = [
|
||||||
"trojan={$server['host']}:{$server['port']}",
|
"trojan={$addr}:{$server['port']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
'over-tls=true',
|
|
||||||
$protocol_settings['server_name'] ? "tls-host={$protocol_settings['server_name']}" : "",
|
|
||||||
// Tips: allowInsecure=false = tls-verification=true
|
|
||||||
$protocol_settings['allow_insecure'] ? 'tls-verification=false' : 'tls-verification=true',
|
|
||||||
'fast-open=true',
|
|
||||||
'udp-relay=true',
|
|
||||||
"tag={$server['name']}"
|
|
||||||
];
|
];
|
||||||
$config = array_filter($config);
|
|
||||||
$uri = implode(',', $config);
|
$tlsData = [
|
||||||
$uri .= "\r\n";
|
'allow_insecure' => data_get($protocol_settings, 'allow_insecure', false),
|
||||||
return $uri;
|
'server_name' => data_get($protocol_settings, 'server_name'),
|
||||||
|
];
|
||||||
|
self::applyTransportSettings($config, $protocol_settings, true, $tlsData);
|
||||||
|
self::applyCommonSettings($config, $server);
|
||||||
|
|
||||||
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildSocks5($password, $server)
|
||||||
|
{
|
||||||
|
$protocol_settings = $server['protocol_settings'];
|
||||||
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$config = [
|
||||||
|
"socks5={$addr}:{$server['port']}",
|
||||||
|
"username={$password}",
|
||||||
|
"password={$password}",
|
||||||
|
];
|
||||||
|
|
||||||
|
self::applyTransportSettings($config, $protocol_settings, true);
|
||||||
|
self::applyCommonSettings($config, $server);
|
||||||
|
|
||||||
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildHttp($password, $server)
|
||||||
|
{
|
||||||
|
$protocol_settings = $server['protocol_settings'];
|
||||||
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$config = [
|
||||||
|
"http={$addr}:{$server['port']}",
|
||||||
|
"username={$password}",
|
||||||
|
"password={$password}",
|
||||||
|
];
|
||||||
|
|
||||||
|
self::applyTransportSettings($config, $protocol_settings, true);
|
||||||
|
self::applyCommonSettings($config, $server);
|
||||||
|
|
||||||
|
return implode(',', array_filter($config)) . "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
|
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
|
||||||
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
|
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
|
||||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||||
$expiredDate = date('Y-m-d', $user['expired_at']);
|
$expiredDate = $user['expired_at'] === null ? 'N/A' : date('Y-m-d', $user['expired_at']);
|
||||||
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
|
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
|
||||||
foreach ($servers as $item) {
|
foreach ($servers as $item) {
|
||||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
||||||
@@ -128,6 +128,25 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
$config['path'] = data_get($protocol_settings, 'network_settings.serviceName');
|
$config['path'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||||
$config['host'] = data_get($protocol_settings, 'tls_settings.server_name') ?? $server['host'];
|
$config['host'] = data_get($protocol_settings, 'tls_settings.server_name') ?? $server['host'];
|
||||||
break;
|
break;
|
||||||
|
case 'httpupgrade':
|
||||||
|
$config['obfs'] = "httpupgrade";
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path')) {
|
||||||
|
$config['path'] = $path;
|
||||||
|
}
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host'])) {
|
||||||
|
$config['obfsParam'] = $host;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h2':
|
||||||
|
$config['obfs'] = "h2";
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path')) {
|
||||||
|
$config['path'] = $path;
|
||||||
|
}
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host')) {
|
||||||
|
$config['obfsParam'] = $host[0] ?? $server['host'];
|
||||||
|
$config['peer'] = $host [0] ?? $server['host'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
|
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
|
||||||
$uri = "vmess://{$userinfo}?{$query}";
|
$uri = "vmess://{$userinfo}?{$query}";
|
||||||
@@ -157,7 +176,6 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
$config['xtls'] = $xtlsMap[data_get($protocol_settings, 'flow')];
|
$config['xtls'] = $xtlsMap[data_get($protocol_settings, 'flow')];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data_get($protocol_settings, 'tls')) {
|
switch (data_get($protocol_settings, 'tls')) {
|
||||||
case 1:
|
case 1:
|
||||||
$config['tls'] = 1;
|
$config['tls'] = 1;
|
||||||
@@ -211,6 +229,15 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
}
|
}
|
||||||
$config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
$config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||||
break;
|
break;
|
||||||
|
case 'h2':
|
||||||
|
$config['obfs'] = "h2";
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path')) {
|
||||||
|
$config['path'] = $path;
|
||||||
|
}
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host', $server['host'])) {
|
||||||
|
$config['obfsParam'] = $host;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'httpupgrade':
|
case 'httpupgrade':
|
||||||
$config['obfs'] = "httpupgrade";
|
$config['obfs'] = "httpupgrade";
|
||||||
if ($path = data_get($protocol_settings, 'network_settings.path')) {
|
if ($path = data_get($protocol_settings, 'network_settings.path')) {
|
||||||
@@ -244,10 +271,24 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$name = rawurlencode($server['name']);
|
$name = rawurlencode($server['name']);
|
||||||
$params['allowInsecure'] = data_get($protocol_settings, 'allow_insecure');
|
$params = [];
|
||||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
$params['peer'] = $serverName;
|
|
||||||
|
switch ($tlsMode) {
|
||||||
|
case 2: // Reality
|
||||||
|
$params['security'] = 'reality';
|
||||||
|
$params['pbk'] = data_get($protocol_settings, 'reality_settings.public_key');
|
||||||
|
$params['sid'] = data_get($protocol_settings, 'reality_settings.short_id');
|
||||||
|
$params['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
$params['allowInsecure'] = data_get($protocol_settings, 'allow_insecure');
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$params['peer'] = $serverName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data_get($protocol_settings, 'network')) {
|
switch (data_get($protocol_settings, 'network')) {
|
||||||
case 'grpc':
|
case 'grpc':
|
||||||
$params['obfs'] = 'grpc';
|
$params['obfs'] = 'grpc';
|
||||||
@@ -286,7 +327,7 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
}
|
}
|
||||||
if (data_get($protocol_settings, 'obfs.open')) {
|
if (data_get($protocol_settings, 'obfs.open')) {
|
||||||
$params["obfs"] = "xplus";
|
$params["obfs"] = "xplus";
|
||||||
$params["obfsParam"] = data_get($protocol_settings, 'obfs_settings.password');
|
$params["obfsParam"] = 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($server['ports']))
|
||||||
@@ -341,7 +382,8 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
$params['password'] = $password;
|
$params['password'] = $password;
|
||||||
}
|
}
|
||||||
$query = http_build_query($params);
|
$query = http_build_query($params);
|
||||||
$uri = "tuic://{$server['host']}:{$server['port']}?{$query}#{$name}";
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$uri = "tuic://{$addr}:{$server['port']}?{$query}#{$name}";
|
||||||
$uri .= "\r\n";
|
$uri .= "\r\n";
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
@@ -355,7 +397,8 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
||||||
];
|
];
|
||||||
$query = http_build_query($params);
|
$query = http_build_query($params);
|
||||||
$uri = "anytls://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$uri = "anytls://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||||
$uri .= "\r\n";
|
$uri .= "\r\n";
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
@@ -364,7 +407,8 @@ class Shadowrocket extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$name = rawurlencode($server['name']);
|
$name = rawurlencode($server['name']);
|
||||||
$uri = "socks://" . base64_encode("{$password}:{$password}@{$server['host']}:{$server['port']}") . "?method=auto#{$name}";
|
$addr = Helper::wrapIPv6($server['host']);
|
||||||
|
$uri = 'socks://' . base64_encode("{$password}:{$password}@{$addr}:{$server['port']}") . "?method=auto#{$name}";
|
||||||
$uri .= "\r\n";
|
$uri .= "\r\n";
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use App\Utils\Helper;
|
|||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use App\Support\AbstractProtocol;
|
use App\Support\AbstractProtocol;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Log;
|
||||||
|
|
||||||
class SingBox extends AbstractProtocol
|
class SingBox extends AbstractProtocol
|
||||||
{
|
{
|
||||||
@@ -59,9 +60,6 @@ class SingBox extends AbstractProtocol
|
|||||||
'anytls' => [
|
'anytls' => [
|
||||||
'base_version' => '1.12.0'
|
'base_version' => '1.12.0'
|
||||||
],
|
],
|
||||||
'mieru' => [
|
|
||||||
'base_version' => '1.12.0'
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -133,10 +131,6 @@ class SingBox extends AbstractProtocol
|
|||||||
$httpConfig = $this->buildHttp($this->user['uuid'], $item);
|
$httpConfig = $this->buildHttp($this->user['uuid'], $item);
|
||||||
$proxies[] = $httpConfig;
|
$proxies[] = $httpConfig;
|
||||||
}
|
}
|
||||||
if ($item['type'] === Server::TYPE_MIERU) {
|
|
||||||
$mieruConfig = $this->buildMieru($this->user['uuid'], $item);
|
|
||||||
$proxies[] = $mieruConfig;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach ($outbounds as &$outbound) {
|
foreach ($outbounds as &$outbound) {
|
||||||
if (in_array($outbound['type'], ['urltest', 'selector'])) {
|
if (in_array($outbound['type'], ['urltest', 'selector'])) {
|
||||||
@@ -155,23 +149,12 @@ class SingBox extends AbstractProtocol
|
|||||||
protected function buildRule()
|
protected function buildRule()
|
||||||
{
|
{
|
||||||
$rules = $this->config['route']['rules'];
|
$rules = $this->config['route']['rules'];
|
||||||
// Force the nodes ip to be a direct rule
|
|
||||||
// array_unshift($rules, [
|
|
||||||
// 'ip_cidr' => collect($this->servers)->pluck('host')->map(function ($host) {
|
|
||||||
// return filter_var($host, FILTER_VALIDATE_IP) ? [$host] : Helper::getIpByDomainName($host);
|
|
||||||
// })->flatten()->unique()->values(),
|
|
||||||
// 'outbound' => 'direct',
|
|
||||||
// ]);
|
|
||||||
$this->config['route']['rules'] = $rules;
|
$this->config['route']['rules'] = $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据客户端版本自适应配置格式
|
* 根据客户端版本自适应配置格式
|
||||||
*
|
* 模板基准格式: 1.13.0+ (最新)
|
||||||
* sing-box 版本断点:
|
|
||||||
* - 1.8.0: rule_set 替代 geoip/geosite db, cache_file 替代 clash_api.cache_file
|
|
||||||
* - 1.10.0: address 数组替代 inet4_address/inet6_address
|
|
||||||
* - 1.11.0: 移除 endpoint_independent_nat, sniff_override_destination
|
|
||||||
*/
|
*/
|
||||||
protected function adaptConfigForVersion(): void
|
protected function adaptConfigForVersion(): void
|
||||||
{
|
{
|
||||||
@@ -180,57 +163,190 @@ class SingBox extends AbstractProtocol
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// >= 1.11.0: 移除已废弃字段,避免 "配置已过时" 警告
|
// >= 1.13.0: 移除已删除的 block/dns 出站
|
||||||
if (version_compare($coreVersion, '1.11.0', '>=')) {
|
if (version_compare($coreVersion, '1.13.0', '>=')) {
|
||||||
$this->removeDeprecatedFieldsV111();
|
$this->upgradeSpecialOutboundsToActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// < 1.10.0: address 数组 → inet4_address/inet6_address
|
// < 1.11.0: rule action 降级为旧出站; 恢复废弃字段
|
||||||
|
if (version_compare($coreVersion, '1.11.0', '<')) {
|
||||||
|
$this->downgradeActionsToSpecialOutbounds();
|
||||||
|
$this->restoreDeprecatedInboundFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1.12.0: DNS type+server → 旧 address 格式
|
||||||
|
if (version_compare($coreVersion, '1.12.0', '<')) {
|
||||||
|
$this->convertDnsServersToLegacy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1.10.0: tun address 数组 → inet4_address/inet6_address
|
||||||
if (version_compare($coreVersion, '1.10.0', '<')) {
|
if (version_compare($coreVersion, '1.10.0', '<')) {
|
||||||
$this->convertAddressToLegacy();
|
$this->convertTunAddressToLegacy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取实际 sing-box 核心版本
|
* 获取核心版本 (Hiddify/SFM 等映射到内核版本)
|
||||||
*
|
|
||||||
* sing-box 客户端直接报核心版本,hiddify/sfm 等 wrapper 客户端
|
|
||||||
* 报的是 app 版本,需要映射到对应的 sing-box 核心版本
|
|
||||||
*/
|
*/
|
||||||
private function getSingBoxCoreVersion(): ?string
|
private function getSingBoxCoreVersion(): ?string
|
||||||
{
|
{
|
||||||
|
// 优先从 UA 提取核心版本
|
||||||
|
if (!empty($this->userAgent)) {
|
||||||
|
if (preg_match('/sing-box\s+v?(\d+(?:\.\d+){0,2})/i', $this->userAgent, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($this->clientVersion)) {
|
if (empty($this->clientVersion)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sing-box 原生客户端,版本即核心版本
|
|
||||||
if ($this->clientName === 'sing-box') {
|
if ($this->clientName === 'sing-box') {
|
||||||
return $this->clientVersion;
|
return $this->clientVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hiddify/SFM 等 wrapper 默认内置较新的 sing-box 核心
|
return '1.13.0';
|
||||||
// 保守策略: 直接按最新格式输出(移除废弃字段),因为这些客户端普遍内置 >= 1.11 的核心
|
|
||||||
return '1.11.0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sing-box >= 1.11.0: 移除废弃字段
|
* sing-box >= 1.13.0: block/dns 出站升级为 action
|
||||||
*/
|
*/
|
||||||
private function removeDeprecatedFieldsV111(): void
|
private function upgradeSpecialOutboundsToActions(): void
|
||||||
|
{
|
||||||
|
$removedTags = [];
|
||||||
|
$this->config['outbounds'] = array_values(array_filter(
|
||||||
|
$this->config['outbounds'] ?? [],
|
||||||
|
function ($outbound) use (&$removedTags) {
|
||||||
|
if (in_array($outbound['type'] ?? '', ['block', 'dns'])) {
|
||||||
|
$removedTags[$outbound['tag']] = $outbound['type'];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
if (empty($removedTags)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->config['route']['rules'])) {
|
||||||
|
foreach ($this->config['route']['rules'] as &$rule) {
|
||||||
|
if (!isset($rule['outbound']) || !isset($removedTags[$rule['outbound']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$type = $removedTags[$rule['outbound']];
|
||||||
|
unset($rule['outbound']);
|
||||||
|
$rule['action'] = $type === 'dns' ? 'hijack-dns' : 'reject';
|
||||||
|
}
|
||||||
|
unset($rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sing-box < 1.11.0: rule action 降级为旧 block/dns 出站
|
||||||
|
*/
|
||||||
|
private function downgradeActionsToSpecialOutbounds(): void
|
||||||
|
{
|
||||||
|
$needsDnsOutbound = false;
|
||||||
|
$needsBlockOutbound = false;
|
||||||
|
|
||||||
|
if (isset($this->config['route']['rules'])) {
|
||||||
|
foreach ($this->config['route']['rules'] as &$rule) {
|
||||||
|
if (!isset($rule['action'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch ($rule['action']) {
|
||||||
|
case 'hijack-dns':
|
||||||
|
unset($rule['action']);
|
||||||
|
$rule['outbound'] = 'dns-out';
|
||||||
|
$needsDnsOutbound = true;
|
||||||
|
break;
|
||||||
|
case 'reject':
|
||||||
|
unset($rule['action']);
|
||||||
|
$rule['outbound'] = 'block';
|
||||||
|
$needsBlockOutbound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needsBlockOutbound) {
|
||||||
|
$this->config['outbounds'][] = ['type' => 'block', 'tag' => 'block'];
|
||||||
|
}
|
||||||
|
if ($needsDnsOutbound) {
|
||||||
|
$this->config['outbounds'][] = ['type' => 'dns', 'tag' => 'dns-out'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sing-box < 1.11.0: 恢复废弃的入站字段
|
||||||
|
*/
|
||||||
|
private function restoreDeprecatedInboundFields(): void
|
||||||
{
|
{
|
||||||
if (!isset($this->config['inbounds'])) {
|
if (!isset($this->config['inbounds'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($this->config['inbounds'] as &$inbound) {
|
foreach ($this->config['inbounds'] as &$inbound) {
|
||||||
unset($inbound['endpoint_independent_nat']);
|
if ($inbound['type'] === 'tun') {
|
||||||
unset($inbound['sniff_override_destination']);
|
$inbound['endpoint_independent_nat'] = true;
|
||||||
|
}
|
||||||
|
if (!empty($inbound['sniff'])) {
|
||||||
|
$inbound['sniff_override_destination'] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sing-box < 1.12.0: 将新 DNS server type+server 格式转换为旧 address 格式
|
||||||
|
*/
|
||||||
|
private function convertDnsServersToLegacy(): void
|
||||||
|
{
|
||||||
|
if (!isset($this->config['dns']['servers'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($this->config['dns']['servers'] as &$server) {
|
||||||
|
if (!isset($server['type'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$type = $server['type'];
|
||||||
|
$host = $server['server'] ?? null;
|
||||||
|
switch ($type) {
|
||||||
|
case 'https':
|
||||||
|
$server['address'] = "https://{$host}/dns-query";
|
||||||
|
break;
|
||||||
|
case 'tls':
|
||||||
|
$server['address'] = "tls://{$host}";
|
||||||
|
break;
|
||||||
|
case 'tcp':
|
||||||
|
$server['address'] = "tcp://{$host}";
|
||||||
|
break;
|
||||||
|
case 'quic':
|
||||||
|
$server['address'] = "quic://{$host}";
|
||||||
|
break;
|
||||||
|
case 'udp':
|
||||||
|
$server['address'] = $host;
|
||||||
|
break;
|
||||||
|
case 'block':
|
||||||
|
$server['address'] = 'rcode://refused';
|
||||||
|
break;
|
||||||
|
case 'rcode':
|
||||||
|
$server['address'] = 'rcode://' . ($server['rcode'] ?? 'success');
|
||||||
|
unset($server['rcode']);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$server['address'] = $host;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
unset($server['type'], $server['server']);
|
||||||
|
}
|
||||||
|
unset($server);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sing-box < 1.10.0: 将 tun address 数组转换为 inet4_address/inet6_address
|
* sing-box < 1.10.0: 将 tun address 数组转换为 inet4_address/inet6_address
|
||||||
*/
|
*/
|
||||||
private function convertAddressToLegacy(): void
|
private function convertTunAddressToLegacy(): void
|
||||||
{
|
{
|
||||||
if (!isset($this->config['inbounds'])) {
|
if (!isset($this->config['inbounds'])) {
|
||||||
return;
|
return;
|
||||||
@@ -297,42 +413,8 @@ class SingBox extends AbstractProtocol
|
|||||||
|
|
||||||
$this->appendMultiplex($array, $protocol_settings);
|
$this->appendMultiplex($array, $protocol_settings);
|
||||||
|
|
||||||
$transport = match ($protocol_settings['network']) {
|
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||||
'tcp' => data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none' ? [
|
$array['transport'] = $transport;
|
||||||
'type' => 'http',
|
|
||||||
'path' => Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])),
|
|
||||||
'host' => data_get($protocol_settings, 'network_settings.header.request.headers.Host', [])
|
|
||||||
] : null,
|
|
||||||
'ws' => array_filter([
|
|
||||||
'type' => 'ws',
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
||||||
'headers' => ($host = data_get($protocol_settings, 'network_settings.headers.Host')) ? ['Host' => $host] : null,
|
|
||||||
'max_early_data' => 2048,
|
|
||||||
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
|
||||||
]),
|
|
||||||
'grpc' => [
|
|
||||||
'type' => 'grpc',
|
|
||||||
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
||||||
],
|
|
||||||
'h2' => [
|
|
||||||
'type' => 'http',
|
|
||||||
'host' => data_get($protocol_settings, 'network_settings.host'),
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path')
|
|
||||||
],
|
|
||||||
'httpupgrade' => [
|
|
||||||
'type' => 'httpupgrade',
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
||||||
'host' => data_get($protocol_settings, 'network_settings.host', $server['host']),
|
|
||||||
'headers' => data_get($protocol_settings, 'network_settings.headers')
|
|
||||||
],
|
|
||||||
'quic' => [
|
|
||||||
'type' => 'quic'
|
|
||||||
],
|
|
||||||
default => null
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($transport) {
|
|
||||||
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
|
||||||
}
|
}
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
@@ -347,18 +429,23 @@ class SingBox extends AbstractProtocol
|
|||||||
"server_port" => $server['port'],
|
"server_port" => $server['port'],
|
||||||
"uuid" => $password,
|
"uuid" => $password,
|
||||||
"packet_encoding" => "xudp",
|
"packet_encoding" => "xudp",
|
||||||
'flow' => data_get($protocol_settings, 'flow', ''),
|
|
||||||
];
|
];
|
||||||
|
if ($flow = data_get($protocol_settings, 'flow')) {
|
||||||
|
$array['flow'] = $flow;
|
||||||
|
}
|
||||||
|
|
||||||
if ($protocol_settings['tls']) {
|
if ($protocol_settings['tls']) {
|
||||||
|
$tlsMode = (int) $protocol_settings['tls'];
|
||||||
$tlsConfig = [
|
$tlsConfig = [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'),
|
'insecure' => $tlsMode === 2
|
||||||
|
? (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false)
|
||||||
|
: (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->appendUtls($tlsConfig, $protocol_settings);
|
$this->appendUtls($tlsConfig, $protocol_settings);
|
||||||
|
|
||||||
switch ($protocol_settings['tls']) {
|
switch ($tlsMode) {
|
||||||
case 1:
|
case 1:
|
||||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||||
$tlsConfig['server_name'] = $serverName;
|
$tlsConfig['server_name'] = $serverName;
|
||||||
@@ -379,41 +466,8 @@ class SingBox extends AbstractProtocol
|
|||||||
|
|
||||||
$this->appendMultiplex($array, $protocol_settings);
|
$this->appendMultiplex($array, $protocol_settings);
|
||||||
|
|
||||||
$transport = match ($protocol_settings['network']) {
|
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||||
'tcp' => data_get($protocol_settings, 'network_settings.header.type') == 'http' ? [
|
$array['transport'] = $transport;
|
||||||
'type' => 'http',
|
|
||||||
'path' => Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']))
|
|
||||||
] : null,
|
|
||||||
'ws' => array_filter([
|
|
||||||
'type' => 'ws',
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
||||||
'headers' => ($host = data_get($protocol_settings, 'network_settings.headers.Host')) ? ['Host' => $host] : null,
|
|
||||||
'max_early_data' => 2048,
|
|
||||||
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
|
||||||
], fn($value) => !is_null($value)),
|
|
||||||
'grpc' => [
|
|
||||||
'type' => 'grpc',
|
|
||||||
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
||||||
],
|
|
||||||
'h2' => [
|
|
||||||
'type' => 'http',
|
|
||||||
'host' => data_get($protocol_settings, 'network_settings.host'),
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path')
|
|
||||||
],
|
|
||||||
'httpupgrade' => [
|
|
||||||
'type' => 'httpupgrade',
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
||||||
'host' => data_get($protocol_settings, 'network_settings.host', $server['host']),
|
|
||||||
'headers' => data_get($protocol_settings, 'network_settings.headers')
|
|
||||||
],
|
|
||||||
'quic' => [
|
|
||||||
'type' => 'quic'
|
|
||||||
],
|
|
||||||
default => null
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($transport) {
|
|
||||||
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
@@ -428,36 +482,36 @@ class SingBox extends AbstractProtocol
|
|||||||
'server' => $server['host'],
|
'server' => $server['host'],
|
||||||
'server_port' => $server['port'],
|
'server_port' => $server['port'],
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'tls' => [
|
|
||||||
'enabled' => true,
|
|
||||||
'insecure' => (bool) data_get($protocol_settings, 'allow_insecure', false),
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->appendUtls($array['tls'], $protocol_settings);
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
|
$tlsConfig = ['enabled' => true];
|
||||||
|
|
||||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
switch ($tlsMode) {
|
||||||
$array['tls']['server_name'] = $serverName;
|
case 2: // Reality
|
||||||
|
$tlsConfig['insecure'] = (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false);
|
||||||
|
$tlsConfig['server_name'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||||
|
$tlsConfig['reality'] = [
|
||||||
|
'enabled' => true,
|
||||||
|
'public_key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
||||||
|
'short_id' => data_get($protocol_settings, 'reality_settings.short_id'),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
$tlsConfig['insecure'] = (bool) data_get($protocol_settings, 'allow_insecure', false);
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$tlsConfig['server_name'] = $serverName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->appendUtls($tlsConfig, $protocol_settings);
|
||||||
|
$array['tls'] = $tlsConfig;
|
||||||
|
|
||||||
$this->appendMultiplex($array, $protocol_settings);
|
$this->appendMultiplex($array, $protocol_settings);
|
||||||
|
|
||||||
$transport = match (data_get($protocol_settings, 'network')) {
|
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||||
'grpc' => [
|
$array['transport'] = $transport;
|
||||||
'type' => 'grpc',
|
|
||||||
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
||||||
],
|
|
||||||
'ws' => array_filter([
|
|
||||||
'type' => 'ws',
|
|
||||||
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
||||||
'headers' => data_get($protocol_settings, 'network_settings.headers.Host') ? ['Host' => [data_get($protocol_settings, 'network_settings.headers.Host')]] : null,
|
|
||||||
'max_early_data' => 2048,
|
|
||||||
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
|
||||||
]),
|
|
||||||
default => null
|
|
||||||
};
|
|
||||||
if ($transport) {
|
|
||||||
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
|
||||||
}
|
}
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
@@ -508,10 +562,9 @@ class SingBox extends AbstractProtocol
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
return array_merge(
|
return array_filter(
|
||||||
$baseConfig,
|
array_merge($baseConfig, $speedConfig, $versionConfig),
|
||||||
$speedConfig,
|
fn($v) => !is_null($v)
|
||||||
$versionConfig
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,6 +678,47 @@ class SingBox extends AbstractProtocol
|
|||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function buildTransport(array $protocol_settings, array $server): ?array
|
||||||
|
{
|
||||||
|
$transport = match (data_get($protocol_settings, 'network')) {
|
||||||
|
'tcp' => data_get($protocol_settings, 'network_settings.header.type') === 'http' ? [
|
||||||
|
'type' => 'http',
|
||||||
|
'path' => Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])),
|
||||||
|
'host' => data_get($protocol_settings, 'network_settings.header.request.headers.Host', [])
|
||||||
|
] : null,
|
||||||
|
'ws' => [
|
||||||
|
'type' => 'ws',
|
||||||
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
||||||
|
'headers' => ($host = data_get($protocol_settings, 'network_settings.headers.Host')) ? ['Host' => $host] : null,
|
||||||
|
'max_early_data' => 0,
|
||||||
|
// 'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
||||||
|
],
|
||||||
|
'grpc' => [
|
||||||
|
'type' => 'grpc',
|
||||||
|
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
||||||
|
],
|
||||||
|
'h2' => [
|
||||||
|
'type' => 'http',
|
||||||
|
'host' => data_get($protocol_settings, 'network_settings.host'),
|
||||||
|
'path' => data_get($protocol_settings, 'network_settings.path')
|
||||||
|
],
|
||||||
|
'httpupgrade' => [
|
||||||
|
'type' => 'httpupgrade',
|
||||||
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
||||||
|
'host' => data_get($protocol_settings, 'network_settings.host', $server['host']),
|
||||||
|
'headers' => data_get($protocol_settings, 'network_settings.headers')
|
||||||
|
],
|
||||||
|
'quic' => ['type' => 'quic'],
|
||||||
|
default => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!$transport) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_filter($transport, fn($v) => !is_null($v));
|
||||||
|
}
|
||||||
|
|
||||||
protected function appendMultiplex(&$array, $protocol_settings)
|
protected function appendMultiplex(&$array, $protocol_settings)
|
||||||
{
|
{
|
||||||
if ($multiplex = data_get($protocol_settings, 'multiplex')) {
|
if ($multiplex = data_get($protocol_settings, 'multiplex')) {
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ class Stash extends AbstractProtocol
|
|||||||
Server::TYPE_HTTP,
|
Server::TYPE_HTTP,
|
||||||
];
|
];
|
||||||
protected $protocolRequirements = [
|
protected $protocolRequirements = [
|
||||||
|
// Global rules applied regardless of client version (features Stash never supports)
|
||||||
|
'*' => [
|
||||||
|
'trojan' => [
|
||||||
|
'protocol_settings.tls' => [
|
||||||
|
'2' => '9999.0.0', // Trojan Reality not supported in Stash
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'vmess' => [
|
||||||
|
'protocol_settings.network' => [
|
||||||
|
'httpupgrade' => '9999.0.0', // httpupgrade not supported in Stash
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
'stash' => [
|
'stash' => [
|
||||||
'anytls' => [
|
'anytls' => [
|
||||||
'base_version' => '3.3.0' // AnyTLS 协议在3.3.0版本中添加
|
'base_version' => '3.3.0' // AnyTLS 协议在3.3.0版本中添加
|
||||||
@@ -110,10 +123,10 @@ class Stash extends AbstractProtocol
|
|||||||
array_push($proxy, self::buildTuic($item['password'], $item));
|
array_push($proxy, self::buildTuic($item['password'], $item));
|
||||||
array_push($proxies, $item['name']);
|
array_push($proxies, $item['name']);
|
||||||
}
|
}
|
||||||
// if ($item['type'] === 'anytls') {
|
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||||
// array_push($proxy, self::buildAnyTLS($item['password'], $item));
|
array_push($proxy, self::buildAnyTLS($item['password'], $item));
|
||||||
// array_push($proxies, $item['name']);
|
array_push($proxies, $item['name']);
|
||||||
// }
|
}
|
||||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||||
array_push($proxy, self::buildSocks5($item['password'], $item));
|
array_push($proxy, self::buildSocks5($item['password'], $item));
|
||||||
array_push($proxies, $item['name']);
|
array_push($proxies, $item['name']);
|
||||||
@@ -237,8 +250,8 @@ class Stash extends AbstractProtocol
|
|||||||
$array['cipher'] = 'auto';
|
$array['cipher'] = 'auto';
|
||||||
$array['udp'] = true;
|
$array['udp'] = true;
|
||||||
|
|
||||||
$array['tls'] = data_get($protocol_settings, 'tls');
|
$array['tls'] = (bool) data_get($protocol_settings, 'tls');
|
||||||
$array['skip-cert-verify'] = data_get($protocol_settings, 'tls_settings.allow_insecure');
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||||
$array['servername'] = $serverName;
|
$array['servername'] = $serverName;
|
||||||
}
|
}
|
||||||
@@ -266,6 +279,15 @@ class Stash extends AbstractProtocol
|
|||||||
$array['grpc-opts'] = [];
|
$array['grpc-opts'] = [];
|
||||||
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||||
break;
|
break;
|
||||||
|
case 'h2':
|
||||||
|
$array['network'] = 'h2';
|
||||||
|
$array['tls'] = true;
|
||||||
|
$array['h2-opts'] = [];
|
||||||
|
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||||
|
$array['h2-opts']['path'] = $path;
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||||
|
$array['h2-opts']['host'] = is_array($host) ? $host : [$host];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -297,6 +319,7 @@ class Stash extends AbstractProtocol
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
$array['tls'] = true;
|
$array['tls'] = true;
|
||||||
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false);
|
||||||
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
||||||
$array['servername'] = $serverName;
|
$array['servername'] = $serverName;
|
||||||
$array['sni'] = $serverName;
|
$array['sni'] = $serverName;
|
||||||
@@ -335,11 +358,14 @@ class Stash extends AbstractProtocol
|
|||||||
$array['network'] = 'grpc';
|
$array['network'] = 'grpc';
|
||||||
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||||
break;
|
break;
|
||||||
// case 'h2':
|
case 'h2':
|
||||||
// $array['network'] = 'h2';
|
$array['network'] = 'h2';
|
||||||
// $array['h2-opts']['host'] = data_get($protocol_settings, 'network_settings.host');
|
$array['h2-opts'] = [];
|
||||||
// $array['h2-opts']['path'] = data_get($protocol_settings, 'network_settings.path');
|
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||||
// break;
|
$array['h2-opts']['path'] = $path;
|
||||||
|
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||||
|
$array['h2-opts']['host'] = is_array($host) ? $host : [$host];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
@@ -348,13 +374,36 @@ class Stash extends AbstractProtocol
|
|||||||
public static function buildTrojan($password, $server)
|
public static function buildTrojan($password, $server)
|
||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$array = [];
|
$array = [
|
||||||
$array['name'] = $server['name'];
|
'name' => $server['name'],
|
||||||
$array['type'] = 'trojan';
|
'type' => 'trojan',
|
||||||
$array['server'] = $server['host'];
|
'server' => $server['host'],
|
||||||
$array['port'] = $server['port'];
|
'port' => $server['port'],
|
||||||
$array['password'] = $password;
|
'password' => $password,
|
||||||
$array['udp'] = true;
|
'udp' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||||
|
switch ($tlsMode) {
|
||||||
|
case 2: // Reality
|
||||||
|
$array['tls'] = true;
|
||||||
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false);
|
||||||
|
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
||||||
|
$array['sni'] = $serverName;
|
||||||
|
}
|
||||||
|
$array['reality-opts'] = [
|
||||||
|
'public-key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
||||||
|
'short-id' => data_get($protocol_settings, 'reality_settings.short_id'),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default: // Standard TLS
|
||||||
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||||
|
$array['sni'] = $serverName;
|
||||||
|
}
|
||||||
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'allow_insecure', false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (data_get($protocol_settings, 'network')) {
|
switch (data_get($protocol_settings, 'network')) {
|
||||||
case 'tcp':
|
case 'tcp':
|
||||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
||||||
@@ -370,11 +419,13 @@ class Stash extends AbstractProtocol
|
|||||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'grpc':
|
||||||
|
$array['network'] = 'grpc';
|
||||||
|
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||||
|
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
|
||||||
$array['sni'] = $serverName;
|
|
||||||
}
|
|
||||||
$array['skip-cert-verify'] = data_get($protocol_settings, 'allow_insecure');
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,12 +449,18 @@ class Stash extends AbstractProtocol
|
|||||||
$array['type'] = 'hysteria';
|
$array['type'] = 'hysteria';
|
||||||
$array['auth-str'] = $password;
|
$array['auth-str'] = $password;
|
||||||
$array['protocol'] = 'udp';
|
$array['protocol'] = 'udp';
|
||||||
$array['obfs'] = data_get($protocol_settings, 'obfs.open') ? data_get($protocol_settings, 'obfs.type') : null;
|
if (data_get($protocol_settings, 'obfs.open')) {
|
||||||
|
$array['obfs'] = data_get($protocol_settings, 'obfs.password');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
$array['type'] = 'hysteria2';
|
$array['type'] = 'hysteria2';
|
||||||
$array['auth'] = $password;
|
$array['auth'] = $password;
|
||||||
$array['fast-open'] = true;
|
$array['fast-open'] = true;
|
||||||
|
if (data_get($protocol_settings, 'obfs.open')) {
|
||||||
|
$array['obfs'] = data_get($protocol_settings, 'obfs.type', 'salamander');
|
||||||
|
$array['obfs-password'] = data_get($protocol_settings, 'obfs.password');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return $array;
|
return $array;
|
||||||
@@ -417,8 +474,6 @@ class Stash extends AbstractProtocol
|
|||||||
'type' => 'tuic',
|
'type' => 'tuic',
|
||||||
'server' => $server['host'],
|
'server' => $server['host'],
|
||||||
'port' => $server['port'],
|
'port' => $server['port'],
|
||||||
'uuid' => $password,
|
|
||||||
'password' => $password,
|
|
||||||
'congestion-controller' => data_get($protocol_settings, 'congestion_control', 'cubic'),
|
'congestion-controller' => data_get($protocol_settings, 'congestion_control', 'cubic'),
|
||||||
'udp-relay-mode' => data_get($protocol_settings, 'udp_relay_mode', 'native'),
|
'udp-relay-mode' => data_get($protocol_settings, 'udp_relay_mode', 'native'),
|
||||||
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
||||||
@@ -430,6 +485,13 @@ class Stash extends AbstractProtocol
|
|||||||
'version' => data_get($protocol_settings, 'version', 5),
|
'version' => data_get($protocol_settings, 'version', 5),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (data_get($protocol_settings, 'version') === 4) {
|
||||||
|
$array['token'] = $password;
|
||||||
|
} else {
|
||||||
|
$array['uuid'] = $password;
|
||||||
|
$array['password'] = $password;
|
||||||
|
}
|
||||||
|
|
||||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls.allow_insecure', false);
|
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls.allow_insecure', false);
|
||||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||||
$array['sni'] = $serverName;
|
$array['sni'] = $serverName;
|
||||||
@@ -440,15 +502,15 @@ class Stash extends AbstractProtocol
|
|||||||
|
|
||||||
public static function buildAnyTLS($password, $server)
|
public static function buildAnyTLS($password, $server)
|
||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||||
$array = [
|
$array = [
|
||||||
'name' => $server['name'],
|
'name' => $server['name'],
|
||||||
'type' => 'anytls',
|
'type' => 'anytls',
|
||||||
'server' => $server['host'],
|
'server' => $server['host'],
|
||||||
'port' => $server['port'],
|
'port' => $server['port'],
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'sni' => data_get($protocol_settings, 'tls_settings.server_name'),
|
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||||
'skip-cert-verify' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false),
|
'skip-cert-verify' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||||
'udp' => true,
|
'udp' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class Surfboard extends AbstractProtocol
|
|||||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||||
$unusedTraffic = $totalTraffic - $useTraffic;
|
$unusedTraffic = $totalTraffic - $useTraffic;
|
||||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||||
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量: { $unusedTraffic }GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量:{$unusedTraffic}GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
||||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||||
|
|
||||||
return response($config, 200)
|
return response($config, 200)
|
||||||
@@ -146,10 +146,12 @@ class Surfboard extends AbstractProtocol
|
|||||||
array_push($config, 'tls=true');
|
array_push($config, 'tls=true');
|
||||||
if (data_get($protocol_settings, 'tls_settings')) {
|
if (data_get($protocol_settings, 'tls_settings')) {
|
||||||
$tlsSettings = data_get($protocol_settings, 'tls_settings');
|
$tlsSettings = data_get($protocol_settings, 'tls_settings');
|
||||||
if (!!data_get($tlsSettings, 'allowInsecure'))
|
if (data_get($tlsSettings, 'allow_insecure')) {
|
||||||
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
|
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allow_insecure'] ? 'true' : 'false'));
|
||||||
if (!!data_get($tlsSettings, 'serverName'))
|
}
|
||||||
array_push($config, "sni={$tlsSettings['serverName']}");
|
if ($sni = data_get($tlsSettings, 'server_name')) {
|
||||||
|
array_push($config, "sni={$sni}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data_get($protocol_settings, 'network') === 'ws') {
|
if (data_get($protocol_settings, 'network') === 'ws') {
|
||||||
@@ -176,7 +178,7 @@ class Surfboard extends AbstractProtocol
|
|||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
$protocol_settings['server_name'] ? "sni={$protocol_settings['server_name']}" : "",
|
data_get($protocol_settings, 'server_name') ? "sni=" . data_get($protocol_settings, 'server_name') : "",
|
||||||
'tfo=true',
|
'tfo=true',
|
||||||
'udp-relay=true'
|
'udp-relay=true'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Surge extends AbstractProtocol
|
|||||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||||
$unusedTraffic = $totalTraffic - $useTraffic;
|
$unusedTraffic = $totalTraffic - $useTraffic;
|
||||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||||
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量:{ $unusedTraffic }GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量:{$unusedTraffic}GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
||||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||||
|
|
||||||
return response($config, 200)
|
return response($config, 200)
|
||||||
@@ -108,7 +108,7 @@ class Surge extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}=ss",
|
"{$server['name']} = ss",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"encrypt-method={$protocol_settings['cipher']}",
|
"encrypt-method={$protocol_settings['cipher']}",
|
||||||
@@ -152,7 +152,7 @@ class Surge extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}=vmess",
|
"{$server['name']} = vmess",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"username={$uuid}",
|
"username={$uuid}",
|
||||||
@@ -191,11 +191,11 @@ class Surge extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = $server['protocol_settings'];
|
$protocol_settings = $server['protocol_settings'];
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}=trojan",
|
"{$server['name']} = trojan",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
$protocol_settings['server_name'] ? "sni={$protocol_settings['server_name']}" : "",
|
data_get($protocol_settings, 'server_name') ? "sni=" . data_get($protocol_settings, 'server_name') : "",
|
||||||
'tfo=true',
|
'tfo=true',
|
||||||
'udp-relay=true'
|
'udp-relay=true'
|
||||||
];
|
];
|
||||||
@@ -213,7 +213,7 @@ class Surge extends AbstractProtocol
|
|||||||
{
|
{
|
||||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}=anytls",
|
"{$server['name']} = anytls",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
@@ -237,7 +237,7 @@ class Surge extends AbstractProtocol
|
|||||||
if ($protocol_settings['version'] != 2)
|
if ($protocol_settings['version'] != 2)
|
||||||
return '';
|
return '';
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}=hysteria2",
|
"{$server['name']} = hysteria2",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"password={$password}",
|
"password={$password}",
|
||||||
@@ -266,7 +266,7 @@ class Surge extends AbstractProtocol
|
|||||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||||
$type = data_get($protocol_settings, 'tls') ? 'socks5-tls' : 'socks5';
|
$type = data_get($protocol_settings, 'tls') ? 'socks5-tls' : 'socks5';
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}={$type}",
|
"{$server['name']} = {$type}",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"{$password}",
|
"{$password}",
|
||||||
@@ -295,7 +295,7 @@ class Surge extends AbstractProtocol
|
|||||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||||
$type = data_get($protocol_settings, 'tls') ? 'https' : 'http';
|
$type = data_get($protocol_settings, 'tls') ? 'https' : 'http';
|
||||||
$config = [
|
$config = [
|
||||||
"{$server['name']}={$type}",
|
"{$server['name']} = {$type}",
|
||||||
"{$server['host']}",
|
"{$server['host']}",
|
||||||
"{$server['port']}",
|
"{$server['port']}",
|
||||||
"{$password}",
|
"{$password}",
|
||||||
|
|||||||
@@ -168,6 +168,11 @@ class ServerService
|
|||||||
'host' => $host,
|
'host' => $host,
|
||||||
'server_name' => $protocolSettings['server_name'],
|
'server_name' => $protocolSettings['server_name'],
|
||||||
'multiplex' => data_get($protocolSettings, 'multiplex'),
|
'multiplex' => data_get($protocolSettings, 'multiplex'),
|
||||||
|
'tls' => (int) $protocolSettings['tls'],
|
||||||
|
'tls_settings' => match ((int) $protocolSettings['tls']) {
|
||||||
|
2 => $protocolSettings['reality_settings'],
|
||||||
|
default => null,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'vless' => [
|
'vless' => [
|
||||||
...$baseConfig,
|
...$baseConfig,
|
||||||
@@ -256,7 +261,7 @@ class ServerService
|
|||||||
$response['custom_routes'] = $node['custom_routes'];
|
$response['custom_routes'] = $node['custom_routes'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($node['cert_config'])) {
|
if (!empty($node['cert_config']) && data_get($node['cert_config'],'cert_mode') !== 'none' ) {
|
||||||
$response['cert_config'] = $node['cert_config'];
|
$response['cert_config'] = $node['cert_config'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ abstract class AbstractProtocol
|
|||||||
*/
|
*/
|
||||||
protected $clientVersion;
|
protected $clientVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null 原始 User-Agent
|
||||||
|
*/
|
||||||
|
protected $userAgent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array 协议标识
|
* @var array 协议标识
|
||||||
*/
|
*/
|
||||||
@@ -48,13 +53,15 @@ abstract class AbstractProtocol
|
|||||||
* @param array $servers 服务器信息
|
* @param array $servers 服务器信息
|
||||||
* @param string|null $clientName 客户端名称
|
* @param string|null $clientName 客户端名称
|
||||||
* @param string|null $clientVersion 客户端版本
|
* @param string|null $clientVersion 客户端版本
|
||||||
|
* @param string|null $userAgent 原始 User-Agent
|
||||||
*/
|
*/
|
||||||
public function __construct($user, $servers, $clientName = null, $clientVersion = null)
|
public function __construct($user, $servers, $clientName = null, $clientVersion = null, $userAgent = null)
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
$this->servers = $servers;
|
$this->servers = $servers;
|
||||||
$this->clientName = $clientName;
|
$this->clientName = $clientName;
|
||||||
$this->clientVersion = $clientVersion;
|
$this->clientVersion = $clientVersion;
|
||||||
|
$this->userAgent = $userAgent;
|
||||||
$this->protocolRequirements = $this->normalizeProtocolRequirements($this->protocolRequirements);
|
$this->protocolRequirements = $this->normalizeProtocolRequirements($this->protocolRequirements);
|
||||||
$this->servers = HookManager::filter('protocol.servers.filtered', $this->filterServersByVersion());
|
$this->servers = HookManager::filter('protocol.servers.filtered', $this->filterServersByVersion());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user