first commit
This commit is contained in:
332
Xboard/app/Protocols/Clash.php
Normal file
332
Xboard/app/Protocols/Clash.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use App\Support\AbstractProtocol;
|
||||
|
||||
class Clash extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['clash'];
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.clash.yaml';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.clash.yaml';
|
||||
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
|
||||
// 优先从数据库配置中获取模板
|
||||
$template = subscribe_template('clash');
|
||||
|
||||
$config = Yaml::parse($template);
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
foreach ($servers as $item) {
|
||||
|
||||
if (
|
||||
$item['type'] === Server::TYPE_SHADOWSOCKS
|
||||
&& in_array(data_get($item['protocol_settings'], 'cipher'), [
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305'
|
||||
])
|
||||
) {
|
||||
array_push($proxy, self::buildShadowsocks($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
array_push($proxy, self::buildVmess($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
array_push($proxy, self::buildTrojan($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
array_push($proxy, self::buildSocks5($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HTTP) {
|
||||
array_push($proxy, self::buildHttp($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
}
|
||||
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
if (!is_array($config['proxy-groups'][$k]['proxies']))
|
||||
$config['proxy-groups'][$k]['proxies'] = [];
|
||||
$isFilter = false;
|
||||
foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
|
||||
foreach ($proxies as $dst) {
|
||||
if (!$this->isRegex($src))
|
||||
continue;
|
||||
$isFilter = true;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
|
||||
if ($this->isMatch($src, $dst)) {
|
||||
array_push($config['proxy-groups'][$k]['proxies'], $dst);
|
||||
}
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
|
||||
$config['proxy-groups'] = array_filter($config['proxy-groups'], function ($group) {
|
||||
return $group['proxies'];
|
||||
});
|
||||
$config['proxy-groups'] = array_values($config['proxy-groups']);
|
||||
|
||||
$config = $this->buildRules($config);
|
||||
|
||||
|
||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
||||
return response($yaml)
|
||||
->header('content-type', 'text/yaml')
|
||||
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||
->header('profile-update-interval', '24')
|
||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName))
|
||||
->header('profile-web-page-url', admin_setting('app_url'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the rules for Clash.
|
||||
*/
|
||||
public function buildRules($config)
|
||||
{
|
||||
// Force the current subscription domain to be a direct rule
|
||||
$subsDomain = request()->header('Host');
|
||||
if ($subsDomain) {
|
||||
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public static function buildShadowsocks($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'ss';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['cipher'] = data_get($protocol_settings, 'cipher');
|
||||
$array['password'] = $uuid;
|
||||
$array['udp'] = true;
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
$array['plugin'] = $plugin;
|
||||
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
|
||||
// 根据插件类型进行字段映射
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
$array['plugin-opts'] = [
|
||||
'mode' => $parsedOpts['obfs'] ?? data_get($protocol_settings, 'obfs', 'http'),
|
||||
'host' => $parsedOpts['obfs-host'] ?? data_get($protocol_settings, 'obfs_settings.host', ''),
|
||||
];
|
||||
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$array['plugin-opts']['path'] = $parsedOpts['path'];
|
||||
}
|
||||
break;
|
||||
case 'v2ray-plugin':
|
||||
$array['plugin-opts'] = [
|
||||
'mode' => $parsedOpts['mode'] ?? 'websocket',
|
||||
'tls' => isset($parsedOpts['tls']) && $parsedOpts['tls'] == 'true',
|
||||
'host' => $parsedOpts['host'] ?? '',
|
||||
'path' => $parsedOpts['path'] ?? '/',
|
||||
];
|
||||
break;
|
||||
default:
|
||||
// 对于其他插件,直接使用解析出的键值对
|
||||
$array['plugin-opts'] = $parsedOpts;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'vmess';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['uuid'] = $uuid;
|
||||
$array['alterId'] = 0;
|
||||
$array['cipher'] = 'auto';
|
||||
$array['udp'] = true;
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure');
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['servername'] = $serverName;
|
||||
}
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||
if ($headerType === 'http') {
|
||||
if ($httpOpts = array_filter([
|
||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
||||
])) {
|
||||
$array['http-opts'] = $httpOpts;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'trojan';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['password'] = $password;
|
||||
$array['udp'] = true;
|
||||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'allow_insecure');
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$array['network'] = 'tcp';
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
default:
|
||||
$array['network'] = 'tcp';
|
||||
break;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildSocks5($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'socks5';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['udp'] = true;
|
||||
|
||||
$array['username'] = $password;
|
||||
$array['password'] = $password;
|
||||
|
||||
// TLS 配置
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildHttp($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'http';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
|
||||
$array['username'] = $password;
|
||||
$array['password'] = $password;
|
||||
|
||||
// TLS 配置
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function isMatch($exp, $str)
|
||||
{
|
||||
try {
|
||||
return preg_match($exp, $str) === 1;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function isRegex($exp)
|
||||
{
|
||||
if (empty($exp)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return preg_match($exp, '') !== false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
708
Xboard/app/Protocols/ClashMeta.php
Normal file
708
Xboard/app/Protocols/ClashMeta.php
Normal file
@@ -0,0 +1,708 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use App\Support\AbstractProtocol;
|
||||
|
||||
class ClashMeta extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['meta', 'verge', 'flclash', 'nekobox', 'clashmetaforandroid'];
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.clashmeta.yaml';
|
||||
const CUSTOM_CLASH_TEMPLATE_FILE = 'resources/rules/custom.clash.yaml';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.clash.yaml';
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_TUIC,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
Server::TYPE_MIERU,
|
||||
];
|
||||
|
||||
protected $protocolRequirements = [
|
||||
'*.vless.protocol_settings.network' => [
|
||||
'whitelist' => [
|
||||
'tcp' => '0.0.0',
|
||||
'ws' => '0.0.0',
|
||||
'grpc' => '0.0.0',
|
||||
'http' => '0.0.0',
|
||||
'h2' => '0.0.0',
|
||||
'httpupgrade' => '0.0.0',
|
||||
],
|
||||
'strict' => true,
|
||||
],
|
||||
'nekobox.hysteria.protocol_settings.version' => [
|
||||
1 => '0.0.0',
|
||||
2 => '1.2.7',
|
||||
],
|
||||
'clashmetaforandroid.hysteria.protocol_settings.version' => [
|
||||
2 => '2.9.0',
|
||||
],
|
||||
'nekoray.hysteria.protocol_settings.version' => [
|
||||
2 => '3.24',
|
||||
],
|
||||
'verge.hysteria.protocol_settings.version' => [
|
||||
2 => '1.3.8',
|
||||
],
|
||||
'ClashX Meta.hysteria.protocol_settings.version' => [
|
||||
2 => '1.3.5',
|
||||
],
|
||||
'flclash.hysteria.protocol_settings.version' => [
|
||||
2 => '0.8.0',
|
||||
],
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
|
||||
$template = subscribe_template('clashmeta');
|
||||
|
||||
$config = Yaml::parse($template);
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
||||
array_push($proxy, self::buildShadowsocks($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
array_push($proxy, self::buildVmess($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
array_push($proxy, self::buildTrojan($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VLESS) {
|
||||
array_push($proxy, self::buildVless($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
array_push($proxy, self::buildHysteria($item['password'], $item, $user));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TUIC) {
|
||||
array_push($proxy, self::buildTuic($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
array_push($proxy, self::buildAnyTLS($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
array_push($proxy, self::buildSocks5($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HTTP) {
|
||||
array_push($proxy, self::buildHttp($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_MIERU) {
|
||||
array_push($proxy, self::buildMieru($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
}
|
||||
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
if (!is_array($config['proxy-groups'][$k]['proxies']))
|
||||
$config['proxy-groups'][$k]['proxies'] = [];
|
||||
$isFilter = false;
|
||||
foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
|
||||
foreach ($proxies as $dst) {
|
||||
if (!$this->isRegex($src))
|
||||
continue;
|
||||
$isFilter = true;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
|
||||
if ($this->isMatch($src, $dst)) {
|
||||
array_push($config['proxy-groups'][$k]['proxies'], $dst);
|
||||
}
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
$config['proxy-groups'] = array_filter($config['proxy-groups'], function ($group) {
|
||||
return $group['proxies'];
|
||||
});
|
||||
$config['proxy-groups'] = array_values($config['proxy-groups']);
|
||||
$config = $this->buildRules($config);
|
||||
|
||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
||||
return response($yaml)
|
||||
->header('content-type', 'text/yaml')
|
||||
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||
->header('profile-update-interval', '24')
|
||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the rules for Clash.
|
||||
*/
|
||||
public function buildRules($config)
|
||||
{
|
||||
// Force the current subscription domain to be a direct rule
|
||||
$subsDomain = request()->header('Host');
|
||||
if ($subsDomain) {
|
||||
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
|
||||
}
|
||||
// // Force the nodes ip to be a direct rule
|
||||
// collect($this->servers)->pluck('host')->map(function ($host) {
|
||||
// $host = trim($host);
|
||||
// return filter_var($host, FILTER_VALIDATE_IP) ? [$host] : Helper::getIpByDomainName($host);
|
||||
// })->flatten()->unique()->each(function ($nodeIP) use (&$config) {
|
||||
// array_unshift($config['rules'], "IP-CIDR,{$nodeIP}/32,DIRECT,no-resolve");
|
||||
// });
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'ss';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['cipher'] = data_get($server['protocol_settings'], 'cipher');
|
||||
$array['password'] = data_get($server, 'password', $password);
|
||||
$array['udp'] = true;
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
$array['plugin'] = $plugin;
|
||||
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [trim($pair) => true];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
|
||||
// 根据插件类型进行字段映射
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
case 'obfs-local':
|
||||
$array['plugin'] = 'obfs';
|
||||
$array['plugin-opts'] = array_filter([
|
||||
'mode' => $parsedOpts['obfs'] ?? ($parsedOpts['mode'] ?? 'http'),
|
||||
'host' => $parsedOpts['obfs-host'] ?? ($parsedOpts['host'] ?? 'www.bing.com'),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'v2ray-plugin':
|
||||
$array['plugin-opts'] = array_filter([
|
||||
'mode' => $parsedOpts['mode'] ?? 'websocket',
|
||||
'tls' => isset($parsedOpts['tls']) || isset($parsedOpts['server']),
|
||||
'host' => $parsedOpts['host'] ?? null,
|
||||
'path' => $parsedOpts['path'] ?? '/',
|
||||
'mux' => isset($parsedOpts['mux']) ? true : null,
|
||||
'headers' => isset($parsedOpts['host']) ? ['Host' => $parsedOpts['host']] : null
|
||||
], fn($v) => $v !== null);
|
||||
break;
|
||||
|
||||
case 'shadow-tls':
|
||||
$array['plugin-opts'] = array_filter([
|
||||
'host' => $parsedOpts['host'] ?? null,
|
||||
'password' => $parsedOpts['password'] ?? null,
|
||||
'version' => isset($parsedOpts['version']) ? (int) $parsedOpts['version'] : 2
|
||||
], fn($v) => $v !== null);
|
||||
break;
|
||||
|
||||
case 'restls':
|
||||
$array['plugin-opts'] = array_filter([
|
||||
'host' => $parsedOpts['host'] ?? null,
|
||||
'password' => $parsedOpts['password'] ?? null,
|
||||
'restls-script' => $parsedOpts['restls-script'] ?? '123'
|
||||
], fn($v) => $v !== null);
|
||||
break;
|
||||
|
||||
default:
|
||||
$array['plugin-opts'] = $parsedOpts;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'vmess',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'uuid' => $uuid,
|
||||
'alterId' => 0,
|
||||
'cipher' => 'auto',
|
||||
'udp' => true
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = (bool) data_get($protocol_settings, 'tls');
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
$array['servername'] = data_get($protocol_settings, 'tls_settings.server_name');
|
||||
}
|
||||
|
||||
self::appendUtls($array, $protocol_settings);
|
||||
self::appendMultiplex($array, $protocol_settings);
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||
if ($headerType === 'http') {
|
||||
if (
|
||||
$httpOpts = array_filter([
|
||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
||||
])
|
||||
) {
|
||||
$array['http-opts'] = $httpOpts;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
case 'h2':
|
||||
$array['network'] = 'h2';
|
||||
$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;
|
||||
case 'httpupgrade':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts'] = ['v2ray-http-upgrade' => true];
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildVless($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'vless',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'uuid' => $password,
|
||||
'alterId' => 0,
|
||||
'cipher' => 'auto',
|
||||
'udp' => true,
|
||||
'flow' => data_get($protocol_settings, 'flow'),
|
||||
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
|
||||
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
|
||||
default => 'none'
|
||||
},
|
||||
'tls' => false
|
||||
];
|
||||
|
||||
switch (data_get($protocol_settings, 'tls')) {
|
||||
case 1:
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['servername'] = $serverName;
|
||||
}
|
||||
self::appendUtls($array, $protocol_settings);
|
||||
break;
|
||||
case 2:
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'reality_settings.allow_insecure', false);
|
||||
$array['servername'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||
$array['reality-opts'] = [
|
||||
'public-key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
||||
'short-id' => data_get($protocol_settings, 'reality_settings.short_id')
|
||||
];
|
||||
self::appendUtls($array, $protocol_settings);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$array['network'] = 'tcp';
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||
if ($headerType === 'http') {
|
||||
$array['network'] = 'http';
|
||||
if (
|
||||
$httpOpts = array_filter([
|
||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
||||
])
|
||||
) {
|
||||
$array['http-opts'] = $httpOpts;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
case 'h2':
|
||||
$array['network'] = 'h2';
|
||||
$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;
|
||||
case 'httpupgrade':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts'] = ['v2ray-http-upgrade' => true];
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
self::appendMultiplex($array, $protocol_settings);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'trojan',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'password' => $password,
|
||||
'udp' => true,
|
||||
];
|
||||
|
||||
$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::appendMultiplex($array, $protocol_settings);
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$array['network'] = 'tcp';
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
case 'h2':
|
||||
$array['network'] = 'h2';
|
||||
$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;
|
||||
case 'httpupgrade':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts'] = ['v2ray-http-upgrade' => true];
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['ws-opts']['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
break;
|
||||
default:
|
||||
$array['network'] = 'tcp';
|
||||
break;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildHysteria($password, $server, $user)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||
'up' => data_get($protocol_settings, 'bandwidth.up'),
|
||||
'down' => data_get($protocol_settings, 'bandwidth.down'),
|
||||
'skip-cert-verify' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||
];
|
||||
if (isset($server['ports'])) {
|
||||
$array['ports'] = $server['ports'];
|
||||
}
|
||||
if ($hopInterval = data_get($protocol_settings, 'hop_interval')) {
|
||||
$array['hop-interval'] = (int) $hopInterval;
|
||||
}
|
||||
switch (data_get($protocol_settings, 'version')) {
|
||||
case 1:
|
||||
$array['type'] = 'hysteria';
|
||||
$array['auth_str'] = $password;
|
||||
$array['protocol'] = 'udp'; // 支持 udp/wechat-video/faketcp
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$array['obfs'] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
$array['fast-open'] = true;
|
||||
$array['disable_mtu_discovery'] = true;
|
||||
break;
|
||||
case 2:
|
||||
$array['type'] = 'hysteria2';
|
||||
$array['password'] = $password;
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$array['obfs'] = data_get($protocol_settings, 'obfs.type');
|
||||
$array['obfs-password'] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTuic($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'tuic',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'udp' => true,
|
||||
];
|
||||
|
||||
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);
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
|
||||
if ($alpn = data_get($protocol_settings, 'alpn')) {
|
||||
$array['alpn'] = $alpn;
|
||||
}
|
||||
|
||||
$array['congestion-controller'] = data_get($protocol_settings, 'congestion_control', 'cubic');
|
||||
$array['udp-relay-mode'] = data_get($protocol_settings, 'udp_relay_mode', 'native');
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'anytls',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'password' => $password,
|
||||
'udp' => true,
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
if ($allowInsecure = data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$array['skip-cert-verify'] = (bool) $allowInsecure;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildMieru($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'mieru',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'username' => $password,
|
||||
'password' => $password,
|
||||
'transport' => strtoupper(data_get($protocol_settings, 'transport', 'TCP'))
|
||||
];
|
||||
|
||||
// 如果配置了端口范围
|
||||
if (isset($server['ports'])) {
|
||||
$array['port-range'] = $server['ports'];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildSocks5($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'socks5';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['udp'] = true;
|
||||
|
||||
$array['username'] = $password;
|
||||
$array['password'] = $password;
|
||||
|
||||
// TLS 配置
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildHttp($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'http';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
|
||||
$array['username'] = $password;
|
||||
$array['password'] = $password;
|
||||
|
||||
// TLS 配置
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function isMatch($exp, $str)
|
||||
{
|
||||
try {
|
||||
return preg_match($exp, $str) === 1;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function isRegex($exp)
|
||||
{
|
||||
if (empty($exp)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return preg_match($exp, '') !== false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function appendMultiplex(&$array, $protocol_settings)
|
||||
{
|
||||
if ($multiplex = data_get($protocol_settings, 'multiplex')) {
|
||||
if (data_get($multiplex, 'enabled')) {
|
||||
$array['smux'] = array_filter([
|
||||
'enabled' => true,
|
||||
'protocol' => data_get($multiplex, 'protocol', 'yamux'),
|
||||
'max-connections' => data_get($multiplex, 'max_connections'),
|
||||
// 'min-streams' => data_get($multiplex, 'min_streams'),
|
||||
// 'max-streams' => data_get($multiplex, 'max_streams'),
|
||||
'padding' => data_get($multiplex, 'padding') ? true : null,
|
||||
]);
|
||||
|
||||
if (data_get($multiplex, 'brutal.enabled')) {
|
||||
$array['smux']['brutal-opts'] = [
|
||||
'enabled' => true,
|
||||
'up' => data_get($multiplex, 'brutal.up_mbps'),
|
||||
'down' => data_get($multiplex, 'brutal.down_mbps'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function appendUtls(&$array, $protocol_settings)
|
||||
{
|
||||
if ($utls = data_get($protocol_settings, 'utls')) {
|
||||
if (data_get($utls, 'enabled')) {
|
||||
$array['client-fingerprint'] = Helper::getTlsFingerprint($utls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
448
Xboard/app/Protocols/General.php
Normal file
448
Xboard/app/Protocols/General.php
Normal file
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Support\AbstractProtocol;
|
||||
|
||||
class General extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['general', 'v2rayn', 'v2rayng', 'passwall', 'ssrplus', 'sagernet'];
|
||||
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_TUIC,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
|
||||
protected $protocolRequirements = [
|
||||
'v2rayng.hysteria.protocol_settings.version' => [2 => '1.9.5'],
|
||||
'v2rayn.hysteria.protocol_settings.version' => [2 => '6.31'],
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$uri = '';
|
||||
|
||||
foreach ($servers as $item) {
|
||||
$uri .= match ($item['type']) {
|
||||
Server::TYPE_VMESS => self::buildVmess($item['password'], $item),
|
||||
Server::TYPE_VLESS => self::buildVless($item['password'], $item),
|
||||
Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item),
|
||||
Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item),
|
||||
Server::TYPE_HYSTERIA => self::buildHysteria($item['password'], $item),
|
||||
Server::TYPE_ANYTLS => self::buildAnyTLS($item['password'], $item),
|
||||
Server::TYPE_SOCKS => self::buildSocks($item['password'], $item),
|
||||
Server::TYPE_TUIC => self::buildTuic($item['password'], $item),
|
||||
Server::TYPE_HTTP => self::buildHttp($item['password'], $item),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
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)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$password = data_get($server, 'password', $password);
|
||||
$str = str_replace(
|
||||
['+', '/', '='],
|
||||
['-', '_', ''],
|
||||
base64_encode(data_get($protocol_settings, 'cipher') . ":{$password}")
|
||||
);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$plugin_opts = data_get($protocol_settings, 'plugin_opts');
|
||||
$url = "ss://{$str}@{$addr}:{$server['port']}";
|
||||
if ($plugin && $plugin_opts) {
|
||||
$url .= '/?' . 'plugin=' . rawurlencode($plugin . ';' . $plugin_opts);
|
||||
}
|
||||
$url .= "#{$name}\r\n";
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"v" => "2",
|
||||
"ps" => $server['name'],
|
||||
"add" => $server['host'],
|
||||
"port" => (string) $server['port'],
|
||||
"id" => $uuid,
|
||||
"aid" => '0',
|
||||
"net" => data_get($server, 'protocol_settings.network'),
|
||||
"type" => "none",
|
||||
"host" => "",
|
||||
"path" => "",
|
||||
"tls" => data_get($protocol_settings, 'tls') ? "tls" : "",
|
||||
];
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config['sni'] = $serverName;
|
||||
}
|
||||
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$config['fp'] = $fp;
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
|
||||
$config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'http');
|
||||
$config['path'] = Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']));
|
||||
$config['host'] =
|
||||
data_get($protocol_settings, 'network_settings.header.request.headers.Host')
|
||||
? Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['/']), )
|
||||
: null;
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$config['type'] = '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['type'] = 'grpc';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$config['path'] = $path;
|
||||
break;
|
||||
case 'h2':
|
||||
$config['net'] = 'h2';
|
||||
$config['type'] = '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) ? implode(',', $host) : $host;
|
||||
break;
|
||||
case 'httpupgrade':
|
||||
$config['net'] = 'httpupgrade';
|
||||
$config['type'] = 'httpupgrade';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$config['path'] = $path;
|
||||
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
|
||||
}
|
||||
|
||||
public static function buildVless($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$host = $server['host']; //节点地址
|
||||
$port = $server['port']; //节点端口
|
||||
$name = $server['name']; //节点名称
|
||||
|
||||
$config = [
|
||||
'mode' => 'multi', //grpc传输模式
|
||||
'security' => '', //传输层安全 tls/reality
|
||||
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
|
||||
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
|
||||
default => 'none'
|
||||
},
|
||||
'type' => data_get($server, 'protocol_settings.network'), //传输协议
|
||||
'flow' => data_get($protocol_settings, 'flow'),
|
||||
];
|
||||
// 处理TLS
|
||||
switch (data_get($server, 'protocol_settings.tls')) {
|
||||
case 1:
|
||||
$config['security'] = "tls";
|
||||
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$config['fp'] = $fp;
|
||||
}
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config['sni'] = $serverName;
|
||||
}
|
||||
if (data_get($protocol_settings, 'tls_settings.allow_insecure')) {
|
||||
$config['allowInsecure'] = '1';
|
||||
}
|
||||
break;
|
||||
case 2: //reality
|
||||
$config['security'] = "reality";
|
||||
$config['pbk'] = data_get($protocol_settings, 'reality_settings.public_key');
|
||||
$config['sid'] = data_get($protocol_settings, 'reality_settings.short_id');
|
||||
$config['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||
$config['servername'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||
$config['spx'] = "/";
|
||||
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$config['fp'] = $fp;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// 处理传输协议
|
||||
switch (data_get($server, 'protocol_settings.network')) {
|
||||
case 'ws':
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$config['path'] = $path;
|
||||
if ($wsHost = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$config['host'] = $wsHost;
|
||||
break;
|
||||
case 'grpc':
|
||||
if ($path = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$config['serviceName'] = $path;
|
||||
break;
|
||||
case 'h2':
|
||||
$config['type'] = 'http';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$config['path'] = $path;
|
||||
if ($h2Host = data_get($protocol_settings, 'network_settings.host'))
|
||||
$config['host'] = is_array($h2Host) ? implode(',', $h2Host) : $h2Host;
|
||||
break;
|
||||
case 'kcp':
|
||||
if ($path = data_get($protocol_settings, 'network_settings.seed'))
|
||||
$config['path'] = $path;
|
||||
$config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||
break;
|
||||
case 'httpupgrade':
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$config['path'] = $path;
|
||||
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
|
||||
break;
|
||||
case 'xhttp':
|
||||
$config['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
$config['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
|
||||
$config['mode'] = data_get($protocol_settings, 'network_settings.mode', 'auto');
|
||||
$config['extra'] = json_encode(data_get($protocol_settings, 'network_settings.extra'));
|
||||
break;
|
||||
}
|
||||
|
||||
$user = $uuid . '@' . Helper::wrapIPv6($host) . ':' . $port;
|
||||
$query = http_build_query($config);
|
||||
$fragment = urlencode($name);
|
||||
$link = sprintf("vless://%s?%s#%s\r\n", $user, $query, $fragment);
|
||||
return $link;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$array = [];
|
||||
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||
|
||||
switch ($tlsMode) {
|
||||
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 (data_get($server, 'protocol_settings.network')) {
|
||||
case 'ws':
|
||||
$array['type'] = 'ws';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host'))
|
||||
$array['host'] = $host;
|
||||
break;
|
||||
case 'grpc':
|
||||
// Follow V2rayN family standards
|
||||
$array['type'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['serviceName'] = $serviceName;
|
||||
break;
|
||||
case 'h2':
|
||||
$array['type'] = 'http';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['path'] = $path;
|
||||
if ($host = data_get($protocol_settings, 'network_settings.host'))
|
||||
$array['host'] = is_array($host) ? implode(',', $host) : $host;
|
||||
break;
|
||||
case 'httpupgrade':
|
||||
$array['type'] = 'httpupgrade';
|
||||
if ($path = data_get($protocol_settings, 'network_settings.path'))
|
||||
$array['path'] = $path;
|
||||
$array['host'] = data_get($protocol_settings, 'network_settings.host', $server['host']);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$query = http_build_query($array);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
$uri = "trojan://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildHysteria($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$params = [];
|
||||
$version = data_get($protocol_settings, 'version', 2);
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$params['sni'] = $serverName;
|
||||
}
|
||||
$params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure') ? '1' : '0';
|
||||
|
||||
$name = rawurlencode($server['name']);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
if ($version === 2) {
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$params['obfs'] = 'salamander';
|
||||
$params['obfs-password'] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
if (isset($server['ports'])) {
|
||||
$params['mport'] = $server['ports'];
|
||||
}
|
||||
|
||||
$query = http_build_query($params);
|
||||
$uri = "hysteria2://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
} else {
|
||||
$params['protocol'] = 'udp';
|
||||
$params['auth'] = $password;
|
||||
if ($upMbps = data_get($protocol_settings, 'bandwidth.up'))
|
||||
$params['upmbps'] = $upMbps;
|
||||
if ($downMbps = data_get($protocol_settings, 'bandwidth.down'))
|
||||
$params['downmbps'] = $downMbps;
|
||||
if (data_get($protocol_settings, 'obfs.open') && ($obfsPassword = data_get($protocol_settings, 'obfs.password'))) {
|
||||
$params['obfs'] = 'xplus';
|
||||
$params['obfsParam'] = $obfsPassword;
|
||||
}
|
||||
|
||||
$query = http_build_query($params);
|
||||
$uri = "hysteria://{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
}
|
||||
$uri .= "\r\n";
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
|
||||
public static function buildTuic($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$name = rawurlencode($server['name']);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$port = $server['port'];
|
||||
$uuid = $password; // v2rayN格式里,uuid和password都是密码部分
|
||||
$pass = $password;
|
||||
|
||||
$queryParams = [];
|
||||
|
||||
// 填充sni参数
|
||||
if ($sni = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$queryParams['sni'] = $sni;
|
||||
}
|
||||
|
||||
// alpn参数,支持多值时用逗号连接
|
||||
if ($alpn = data_get($protocol_settings, 'alpn')) {
|
||||
if (is_array($alpn)) {
|
||||
$queryParams['alpn'] = implode(',', $alpn);
|
||||
} else {
|
||||
$queryParams['alpn'] = $alpn;
|
||||
}
|
||||
}
|
||||
|
||||
// congestion_controller参数,默认cubic
|
||||
$congestion = data_get($protocol_settings, 'congestion_control', 'cubic');
|
||||
$queryParams['congestion_control'] = $congestion;
|
||||
|
||||
// udp_relay_mode参数,默认native
|
||||
$udpRelay = data_get($protocol_settings, 'udp_relay_mode', 'native');
|
||||
$queryParams['udp-relay-mode'] = $udpRelay;
|
||||
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$queryParams['insecure'] = '1';
|
||||
}
|
||||
|
||||
$query = http_build_query($queryParams);
|
||||
|
||||
// 构造完整URI,格式:
|
||||
// Tuic://uuid:password@host:port?sni=xxx&alpn=xxx&congestion_controller=xxx&udp_relay_mode=xxx#别名
|
||||
$uri = "tuic://{$uuid}:{$pass}@{$addr}:{$port}";
|
||||
|
||||
if (!empty($query)) {
|
||||
$uri .= "?{$query}";
|
||||
}
|
||||
|
||||
$uri .= "#{$name}\r\n";
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$params = [
|
||||
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
||||
];
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$uri = "anytls://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildSocks($password, $server)
|
||||
{
|
||||
$name = rawurlencode($server['name']);
|
||||
$credentials = base64_encode("{$password}:{$password}");
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
return "socks://{$credentials}@{$addr}:{$server['port']}#{$name}\r\n";
|
||||
}
|
||||
|
||||
public static function buildHttp($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$name = rawurlencode($server['name']);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$credentials = base64_encode("{$password}:{$password}");
|
||||
|
||||
$params = [];
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$params['security'] = 'tls';
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$params['sni'] = $serverName;
|
||||
}
|
||||
$params['allowInsecure'] = data_get($protocol_settings, 'tls_settings.allow_insecure') ? '1' : '0';
|
||||
}
|
||||
|
||||
$uri = "http://{$credentials}@{$addr}:{$server['port']}";
|
||||
if (!empty($params)) {
|
||||
$uri .= '?' . http_build_query($params);
|
||||
}
|
||||
$uri .= "#{$name}\r\n";
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
357
Xboard/app/Protocols/Loon.php
Normal file
357
Xboard/app/Protocols/Loon.php
Normal file
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Loon extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['loon'];
|
||||
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_ANYTLS,
|
||||
];
|
||||
|
||||
protected $protocolRequirements = [
|
||||
'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()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
|
||||
$uri = '';
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if (
|
||||
$item['type'] === Server::TYPE_SHADOWSOCKS
|
||||
) {
|
||||
$uri .= self::buildShadowsocks($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
$uri .= self::buildVmess($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
$uri .= self::buildTrojan($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
$uri .= self::buildHysteria($item['password'], $item, $user);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VLESS) {
|
||||
$uri .= self::buildVless($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$uri .= self::buildAnyTLS($item['password'], $item);
|
||||
}
|
||||
}
|
||||
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']}");
|
||||
}
|
||||
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$cipher = data_get($protocol_settings, 'cipher');
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=Shadowsocks",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$cipher}",
|
||||
"{$password}",
|
||||
'fast-open=false',
|
||||
'udp=true'
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
$config[] = "obfs-name={$parsedOpts['obfs']}";
|
||||
if (isset($parsedOpts['obfs-host'])) {
|
||||
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
||||
}
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$config[] = "obfs-uri={$parsedOpts['path']}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config) . "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']}=vmess",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
'auto',
|
||||
"{$uuid}",
|
||||
'fast-open=false',
|
||||
'udp=true',
|
||||
"alterId=0"
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$config[] = 'over-tls=true';
|
||||
if (data_get($protocol_settings, 'tls_settings')) {
|
||||
$tls_settings = data_get($protocol_settings, 'tls_settings');
|
||||
$config[] = 'skip-cert-verify=' . (data_get($tls_settings, 'allow_insecure') ? 'true' : 'false');
|
||||
if (data_get($tls_settings, 'server_name'))
|
||||
$config[] = "tls-name={$tls_settings['server_name']}";
|
||||
}
|
||||
}
|
||||
|
||||
switch (data_get($server['protocol_settings'], 'network')) {
|
||||
case 'tcp':
|
||||
$config[] = 'transport=tcp';
|
||||
$tcpSettings = data_get($protocol_settings, 'network_settings');
|
||||
if (data_get($tcpSettings, 'header.type'))
|
||||
$config = str_replace('transport=tcp', "transport={$tcpSettings['header']['type']}", $config);
|
||||
if (data_get($tcpSettings, key: 'header.request.path')) {
|
||||
$paths = data_get($tcpSettings, key: 'header.request.path');
|
||||
$path = $paths[array_rand($paths)];
|
||||
$config[] = "path={$path}";
|
||||
}
|
||||
if (data_get($tcpSettings, key: 'header.request.headers.Host')) {
|
||||
$hosts = data_get($tcpSettings, key: 'header.request.headers.Host');
|
||||
$host = $hosts[array_rand($hosts)];
|
||||
$config[] = "host={$host}";
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$config[] = 'transport=ws';
|
||||
$wsSettings = data_get($protocol_settings, 'network_settings');
|
||||
if (data_get($wsSettings, key: 'path'))
|
||||
$config[] = "path={$wsSettings['path']}";
|
||||
if (data_get($wsSettings, key: 'headers.Host'))
|
||||
$config[] = "host={$wsSettings['headers']['Host']}";
|
||||
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 .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']}=trojan",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
];
|
||||
|
||||
$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);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVless($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=VLESS",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
"alterId=0",
|
||||
"udp=true"
|
||||
];
|
||||
|
||||
// flow
|
||||
if ($flow = data_get($protocol_settings, 'flow')) {
|
||||
$config[] = "flow={$flow}";
|
||||
}
|
||||
|
||||
// TLS/Reality
|
||||
switch (data_get($protocol_settings, 'tls')) {
|
||||
case 1:
|
||||
$config[] = "over-tls=true";
|
||||
$config[] = "skip-cert-verify=" . (data_get($protocol_settings, 'tls_settings.allow_insecure', false) ? "true" : "false");
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$config[] = "over-tls=true";
|
||||
$config[] = "skip-cert-verify=" . (data_get($protocol_settings, 'reality_settings.allow_insecure', false) ? "true" : "false");
|
||||
if ($serverName = data_get($protocol_settings, 'reality_settings.server_name')) {
|
||||
$config[] = "sni={$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}";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$config[] = "over-tls=false";
|
||||
break;
|
||||
}
|
||||
|
||||
// network
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
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;
|
||||
default:
|
||||
$config[] = "transport=tcp";
|
||||
break;
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config) . "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildHysteria($password, $server, $user)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
if ($protocol_settings['version'] != 2) {
|
||||
return;
|
||||
}
|
||||
$config = [
|
||||
"{$server['name']}=Hysteria2",
|
||||
$server['host'],
|
||||
$server['port'],
|
||||
$password,
|
||||
$protocol_settings['tls']['server_name'] ? "sni={$protocol_settings['tls']['server_name']}" : "(null)"
|
||||
];
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure'))
|
||||
$config[] = "skip-cert-verify=true";
|
||||
if ($down = data_get($protocol_settings, 'bandwidth.down')) {
|
||||
$config[] = "download-bandwidth={$down}";
|
||||
}
|
||||
$config[] = "udp=true";
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=anytls",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
"udp=true"
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
// ✅ 跳过证书校验
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = 'skip-cert-verify=true';
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
|
||||
return implode(',', $config) . "\r\n";
|
||||
}
|
||||
}
|
||||
232
Xboard/app/Protocols/QuantumultX.php
Normal file
232
Xboard/app/Protocols/QuantumultX.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class QuantumultX extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['quantumult%20x', 'quantumult-x'];
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$uri = '';
|
||||
foreach ($servers as $item) {
|
||||
$uri .= match ($item['type']) {
|
||||
Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item),
|
||||
Server::TYPE_VMESS => self::buildVmess($item['password'], $item),
|
||||
Server::TYPE_VLESS => self::buildVless($item['password'], $item),
|
||||
Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item),
|
||||
Server::TYPE_SOCKS => self::buildSocks5($item['password'], $item),
|
||||
Server::TYPE_HTTP => self::buildHttp($item['password'], $item),
|
||||
default => ''
|
||||
};
|
||||
}
|
||||
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)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$password = data_get($server, 'password', $password);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$config = [
|
||||
"shadowsocks={$addr}:{$server['port']}",
|
||||
"method=" . data_get($protocol_settings, 'cipher'),
|
||||
"password={$password}",
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
if ($plugin === 'obfs') {
|
||||
if (isset($parsedOpts['obfs'])) {
|
||||
$config[] = "obfs={$parsedOpts['obfs']}";
|
||||
}
|
||||
if (isset($parsedOpts['obfs-host'])) {
|
||||
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
||||
}
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$config[] = "obfs-uri={$parsedOpts['path']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::applyCommonSettings($config, $server);
|
||||
|
||||
return implode(',', array_filter($config)) . "\r\n";
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$config = [
|
||||
"vmess={$addr}:{$server['port']}",
|
||||
"method=" . data_get($protocol_settings, 'cipher', 'auto'),
|
||||
"password={$uuid}",
|
||||
];
|
||||
|
||||
self::applyTransportSettings($config, $protocol_settings);
|
||||
self::applyCommonSettings($config, $server);
|
||||
|
||||
return implode(',', array_filter($config)) . "\r\n";
|
||||
}
|
||||
|
||||
public static function buildVless($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$config = [
|
||||
"vless={$addr}:{$server['port']}",
|
||||
'method=none',
|
||||
"password={$uuid}",
|
||||
];
|
||||
|
||||
self::applyTransportSettings($config, $protocol_settings);
|
||||
|
||||
if ($flow = data_get($protocol_settings, 'flow')) {
|
||||
$config[] = "vless-flow={$flow}";
|
||||
}
|
||||
|
||||
self::applyCommonSettings($config, $server);
|
||||
|
||||
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)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$config = [
|
||||
"trojan={$addr}:{$server['port']}",
|
||||
"password={$password}",
|
||||
];
|
||||
|
||||
$tlsData = [
|
||||
'allow_insecure' => data_get($protocol_settings, 'allow_insecure', false),
|
||||
'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";
|
||||
}
|
||||
}
|
||||
415
Xboard/app/Protocols/Shadowrocket.php
Normal file
415
Xboard/app/Protocols/Shadowrocket.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Shadowrocket extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['shadowrocket'];
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_TUIC,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
];
|
||||
|
||||
protected $protocolRequirements = [
|
||||
'shadowrocket.hysteria.protocol_settings.version' => [2 => '1993'],
|
||||
'shadowrocket.anytls.base_version' => '2592',
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
|
||||
$uri = '';
|
||||
//display remaining traffic and expire date
|
||||
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
|
||||
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||
$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";
|
||||
foreach ($servers as $item) {
|
||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
||||
$uri .= self::buildShadowsocks($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
$uri .= self::buildVmess($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VLESS) {
|
||||
$uri .= self::buildVless($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
$uri .= self::buildTrojan($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
$uri .= self::buildHysteria($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TUIC) {
|
||||
$uri .= self::buildTuic($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$uri .= self::buildAnyTLS($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
$uri .= self::buildSocks($item['password'], $item);
|
||||
}
|
||||
}
|
||||
return response(base64_encode($uri))
|
||||
->header('content-type', 'text/plain');
|
||||
}
|
||||
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$password = data_get($server, 'password', $password);
|
||||
$str = str_replace(
|
||||
['+', '/', '='],
|
||||
['-', '_', ''],
|
||||
base64_encode(data_get($protocol_settings, 'cipher') . ":{$password}")
|
||||
);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
$uri = "ss://{$str}@{$addr}:{$server['port']}";
|
||||
$plugin = data_get($protocol_settings, 'plugin') == 'obfs' ? 'obfs-local' : data_get($protocol_settings, 'plugin');
|
||||
$plugin_opts = data_get($protocol_settings, 'plugin_opts');
|
||||
if ($plugin && $plugin_opts) {
|
||||
$uri .= '/?' . 'plugin=' . $plugin . ';' . rawurlencode($plugin_opts);
|
||||
}
|
||||
return $uri . "#{$name}\r\n";
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$userinfo = base64_encode('auto:' . $uuid . '@' . Helper::wrapIPv6($server['host']) . ':' . $server['port']);
|
||||
$config = [
|
||||
'tfo' => 1,
|
||||
'remark' => $server['name'],
|
||||
'alterId' => 0
|
||||
];
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$config['tls'] = 1;
|
||||
if (data_get($protocol_settings, 'tls_settings')) {
|
||||
if (!!data_get($protocol_settings, 'tls_settings.allow_insecure'))
|
||||
$config['allowInsecure'] = (int) data_get($protocol_settings, 'tls_settings.allow_insecure');
|
||||
if (!!data_get($protocol_settings, 'tls_settings.server_name'))
|
||||
$config['peer'] = data_get($protocol_settings, 'tls_settings.server_name');
|
||||
}
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
|
||||
$config['obfs'] = data_get($protocol_settings, 'network_settings.header.type');
|
||||
$config['path'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']));
|
||||
$config['obfsParam'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com']));
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$config['obfs'] = "websocket";
|
||||
$config['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host')) {
|
||||
$config['obfsParam'] = $host;
|
||||
}
|
||||
break;
|
||||
case 'grpc':
|
||||
$config['obfs'] = "grpc";
|
||||
$config['path'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||
$config['host'] = data_get($protocol_settings, 'tls_settings.server_name') ?? $server['host'];
|
||||
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);
|
||||
$uri = "vmess://{$userinfo}?{$query}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVless($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$userinfo = base64_encode('auto:' . $uuid . '@' . Helper::wrapIPv6($server['host']) . ':' . $server['port']);
|
||||
$config = [
|
||||
'tfo' => 1,
|
||||
'remark' => $server['name'],
|
||||
'alterId' => 0
|
||||
];
|
||||
|
||||
// 判断是否开启xtls
|
||||
if (data_get($protocol_settings, 'flow')) {
|
||||
$xtlsMap = [
|
||||
'none' => 0,
|
||||
'xtls-rprx-direct' => 1,
|
||||
'xtls-rprx-vision' => 2
|
||||
];
|
||||
if (array_key_exists(data_get($protocol_settings, 'flow'), $xtlsMap)) {
|
||||
$config['tls'] = 1;
|
||||
$config['xtls'] = $xtlsMap[data_get($protocol_settings, 'flow')];
|
||||
}
|
||||
}
|
||||
switch (data_get($protocol_settings, 'tls')) {
|
||||
case 1:
|
||||
$config['tls'] = 1;
|
||||
$config['allowInsecure'] = (int) data_get($protocol_settings, 'tls_settings.allow_insecure');
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config['peer'] = $serverName;
|
||||
}
|
||||
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$config['fp'] = $fp;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$config['tls'] = 1;
|
||||
$config['sni'] = data_get($protocol_settings, 'reality_settings.server_name');
|
||||
$config['pbk'] = data_get($protocol_settings, 'reality_settings.public_key');
|
||||
$config['sid'] = data_get($protocol_settings, 'reality_settings.short_id');
|
||||
if ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$config['fp'] = $fp;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
if (data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none') {
|
||||
$config['obfs'] = data_get($protocol_settings, 'network_settings.header.type');
|
||||
$config['path'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']));
|
||||
$config['obfsParam'] = \Illuminate\Support\Arr::random(data_get($protocol_settings, 'network_settings.header.request.headers.Host', ['www.example.com']));
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$config['obfs'] = "websocket";
|
||||
if (data_get($protocol_settings, 'network_settings.path')) {
|
||||
$config['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
}
|
||||
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host')) {
|
||||
$config['obfsParam'] = $host;
|
||||
}
|
||||
break;
|
||||
case 'grpc':
|
||||
$config['obfs'] = "grpc";
|
||||
$config['path'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||
$config['host'] = data_get($protocol_settings, 'tls_settings.server_name') ?? $server['host'];
|
||||
break;
|
||||
case 'kcp':
|
||||
$config['obfs'] = "kcp";
|
||||
if ($seed = data_get($protocol_settings, 'network_settings.seed')) {
|
||||
$config['path'] = $seed;
|
||||
}
|
||||
$config['type'] = data_get($protocol_settings, 'network_settings.header.type', 'none');
|
||||
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':
|
||||
$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 'xhttp':
|
||||
$config['obfs'] = "xhttp";
|
||||
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;
|
||||
}
|
||||
if ($mode = data_get($protocol_settings, 'network_settings.mode', 'auto')) {
|
||||
$config['mode'] = $mode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
|
||||
$uri = "vless" . "://{$userinfo}?{$query}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$params = [];
|
||||
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||
|
||||
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')) {
|
||||
case 'grpc':
|
||||
$params['obfs'] = 'grpc';
|
||||
$params['path'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||
break;
|
||||
case 'ws':
|
||||
$host = data_get($protocol_settings, 'network_settings.headers.Host');
|
||||
$path = data_get($protocol_settings, 'network_settings.path');
|
||||
$params['plugin'] = "obfs-local;obfs=websocket;obfs-host={$host};obfs-uri={$path}";
|
||||
break;
|
||||
}
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
$uri = "trojan://{$password}@{$addr}:{$server['port']}?{$query}&tfo=1#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildHysteria($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$uri = ''; // 初始化变量
|
||||
|
||||
switch (data_get($protocol_settings, 'version')) {
|
||||
case 1:
|
||||
$params = [
|
||||
"auth" => $password,
|
||||
"upmbps" => data_get($protocol_settings, 'bandwidth.up'),
|
||||
"downmbps" => data_get($protocol_settings, 'bandwidth.down'),
|
||||
"protocol" => 'udp',
|
||||
"fastopen" => 1,
|
||||
];
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$params['peer'] = $serverName;
|
||||
}
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$params["obfs"] = "xplus";
|
||||
$params["obfsParam"] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
$params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure');
|
||||
if (isset($server['ports']))
|
||||
$params['mport'] = $server['ports'];
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
$uri = "hysteria://{$addr}:{$server['port']}?{$query}#{$server['name']}";
|
||||
$uri .= "\r\n";
|
||||
break;
|
||||
case 2:
|
||||
$params = [
|
||||
"obfs" => 'none',
|
||||
"fastopen" => 1
|
||||
];
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$params['peer'] = $serverName;
|
||||
}
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$params['obfs'] = data_get($protocol_settings, 'obfs.type');
|
||||
$params['obfs-password'] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
$params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure');
|
||||
if (isset($protocol_settings['hop_interval'])) {
|
||||
$params['keepalive'] = data_get($protocol_settings, 'hop_interval');
|
||||
}
|
||||
if (isset($server['ports'])) {
|
||||
$params['mport'] = $server['ports'];
|
||||
}
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
|
||||
$uri = "hysteria2://{$password}@{$addr}:{$server['port']}?{$query}#{$server['name']}";
|
||||
$uri .= "\r\n";
|
||||
break;
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
public static function buildTuic($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$params = [
|
||||
'alpn' => data_get($protocol_settings, 'alpn'),
|
||||
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
||||
];
|
||||
if (data_get($protocol_settings, 'version') === 4) {
|
||||
$params['token'] = $password;
|
||||
} else {
|
||||
$params['uuid'] = $password;
|
||||
$params['password'] = $password;
|
||||
}
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$uri = "tuic://{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$params = [
|
||||
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||
'insecure' => data_get($protocol_settings, 'tls.allow_insecure')
|
||||
];
|
||||
$query = http_build_query($params);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$uri = "anytls://{$password}@{$addr}:{$server['port']}?{$query}#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildSocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$addr = Helper::wrapIPv6($server['host']);
|
||||
$uri = 'socks://' . base64_encode("{$password}:{$password}@{$addr}:{$server['port']}") . "?method=auto#{$name}";
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
60
Xboard/app/Protocols/Shadowsocks.php
Normal file
60
Xboard/app/Protocols/Shadowsocks.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Shadowsocks extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['shadowsocks'];
|
||||
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
|
||||
$configs = [];
|
||||
$subs = [];
|
||||
$subs['servers'] = [];
|
||||
$subs['bytes_used'] = '';
|
||||
$subs['bytes_remaining'] = '';
|
||||
|
||||
$bytesUsed = $user['u'] + $user['d'];
|
||||
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if (
|
||||
$item['type'] === 'shadowsocks'
|
||||
&& in_array(data_get($item, 'protocol_settings.cipher'), ['aes-128-gcm', 'aes-256-gcm', 'aes-192-gcm', 'chacha20-ietf-poly1305'])
|
||||
) {
|
||||
array_push($configs, self::SIP008($item, $user));
|
||||
}
|
||||
}
|
||||
|
||||
$subs['version'] = 1;
|
||||
$subs['bytes_used'] = $bytesUsed;
|
||||
$subs['bytes_remaining'] = $bytesRemaining;
|
||||
$subs['servers'] = array_merge($subs['servers'], $configs);
|
||||
|
||||
return response()->json($subs)
|
||||
->header('content-type', 'application/json');
|
||||
}
|
||||
|
||||
public static function SIP008($server, $user)
|
||||
{
|
||||
$config = [
|
||||
"id" => $server['id'],
|
||||
"remarks" => $server['name'],
|
||||
"server" => $server['host'],
|
||||
"server_port" => $server['port'],
|
||||
"password" => $server['password'],
|
||||
"method" => data_get($server, 'protocol_settings.cipher')
|
||||
];
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
757
Xboard/app/Protocols/SingBox.php
Normal file
757
Xboard/app/Protocols/SingBox.php
Normal file
@@ -0,0 +1,757 @@
|
||||
<?php
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
use Log;
|
||||
|
||||
class SingBox extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['sing-box', 'hiddify', 'sfm'];
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_TUIC,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
private $config;
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.sing-box.json';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.sing-box.json';
|
||||
|
||||
/**
|
||||
* 多客户端协议支持配置
|
||||
*/
|
||||
protected $protocolRequirements = [
|
||||
'sing-box' => [
|
||||
'vless' => [
|
||||
'base_version' => '1.5.0',
|
||||
'protocol_settings.flow' => [
|
||||
'xtls-rprx-vision' => '1.5.0'
|
||||
],
|
||||
'protocol_settings.tls' => [
|
||||
'2' => '1.6.0' // Reality
|
||||
]
|
||||
],
|
||||
'hysteria' => [
|
||||
'base_version' => '1.5.0',
|
||||
'protocol_settings.version' => [
|
||||
'2' => '1.5.0' // Hysteria 2
|
||||
]
|
||||
],
|
||||
'tuic' => [
|
||||
'base_version' => '1.5.0'
|
||||
],
|
||||
'ssh' => [
|
||||
'base_version' => '1.8.0'
|
||||
],
|
||||
'juicity' => [
|
||||
'base_version' => '1.7.0'
|
||||
],
|
||||
'wireguard' => [
|
||||
'base_version' => '1.5.0'
|
||||
],
|
||||
'anytls' => [
|
||||
'base_version' => '1.12.0'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
$this->config = $this->loadConfig();
|
||||
$this->buildOutbounds();
|
||||
$this->buildRule();
|
||||
$this->adaptConfigForVersion();
|
||||
$user = $this->user;
|
||||
|
||||
return response()
|
||||
->json($this->config)
|
||||
->header('profile-title', 'base64:' . base64_encode($appName))
|
||||
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||
->header('profile-update-interval', '24');
|
||||
}
|
||||
|
||||
protected function loadConfig()
|
||||
{
|
||||
$jsonData = subscribe_template('singbox');
|
||||
|
||||
return is_array($jsonData) ? $jsonData : json_decode($jsonData, true);
|
||||
}
|
||||
|
||||
protected function buildOutbounds()
|
||||
{
|
||||
$outbounds = $this->config['outbounds'];
|
||||
$proxies = [];
|
||||
foreach ($this->servers as $item) {
|
||||
$protocol_settings = $item['protocol_settings'];
|
||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
||||
$ssConfig = $this->buildShadowsocks($item['password'], $item);
|
||||
$proxies[] = $ssConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
$trojanConfig = $this->buildTrojan($this->user['uuid'], $item);
|
||||
$proxies[] = $trojanConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
$vmessConfig = $this->buildVmess($this->user['uuid'], $item);
|
||||
$proxies[] = $vmessConfig;
|
||||
}
|
||||
if (
|
||||
$item['type'] === Server::TYPE_VLESS
|
||||
&& in_array(data_get($protocol_settings, 'network'), ['tcp', 'ws', 'grpc', 'http', 'quic', 'httpupgrade'])
|
||||
) {
|
||||
$vlessConfig = $this->buildVless($this->user['uuid'], $item);
|
||||
$proxies[] = $vlessConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
$hysteriaConfig = $this->buildHysteria($this->user['uuid'], $item);
|
||||
$proxies[] = $hysteriaConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TUIC) {
|
||||
$tuicConfig = $this->buildTuic($this->user['uuid'], $item);
|
||||
$proxies[] = $tuicConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$anytlsConfig = $this->buildAnyTLS($this->user['uuid'], $item);
|
||||
$proxies[] = $anytlsConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
$socksConfig = $this->buildSocks($this->user['uuid'], $item);
|
||||
$proxies[] = $socksConfig;
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HTTP) {
|
||||
$httpConfig = $this->buildHttp($this->user['uuid'], $item);
|
||||
$proxies[] = $httpConfig;
|
||||
}
|
||||
}
|
||||
foreach ($outbounds as &$outbound) {
|
||||
if (in_array($outbound['type'], ['urltest', 'selector'])) {
|
||||
array_push($outbound['outbounds'], ...array_column($proxies, 'tag'));
|
||||
}
|
||||
}
|
||||
|
||||
$outbounds = array_merge($outbounds, $proxies);
|
||||
$this->config['outbounds'] = $outbounds;
|
||||
return $outbounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build rule
|
||||
*/
|
||||
protected function buildRule()
|
||||
{
|
||||
$rules = $this->config['route']['rules'];
|
||||
$this->config['route']['rules'] = $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据客户端版本自适应配置格式
|
||||
* 模板基准格式: 1.13.0+ (最新)
|
||||
*/
|
||||
protected function adaptConfigForVersion(): void
|
||||
{
|
||||
$coreVersion = $this->getSingBoxCoreVersion();
|
||||
if (empty($coreVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// >= 1.13.0: 移除已删除的 block/dns 出站
|
||||
if (version_compare($coreVersion, '1.13.0', '>=')) {
|
||||
$this->upgradeSpecialOutboundsToActions();
|
||||
}
|
||||
|
||||
// < 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', '<')) {
|
||||
$this->convertTunAddressToLegacy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核心版本 (Hiddify/SFM 等映射到内核版本)
|
||||
*/
|
||||
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)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->clientName === 'sing-box') {
|
||||
return $this->clientVersion;
|
||||
}
|
||||
|
||||
return '1.13.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* sing-box >= 1.13.0: block/dns 出站升级为 action
|
||||
*/
|
||||
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'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->config['inbounds'] as &$inbound) {
|
||||
if ($inbound['type'] === 'tun') {
|
||||
$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
|
||||
*/
|
||||
private function convertTunAddressToLegacy(): void
|
||||
{
|
||||
if (!isset($this->config['inbounds'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->config['inbounds'] as &$inbound) {
|
||||
if ($inbound['type'] !== 'tun' || !isset($inbound['address'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($inbound['address'] as $addr) {
|
||||
if (str_contains($addr, ':')) {
|
||||
$inbound['inet6_address'] = $addr;
|
||||
} else {
|
||||
$inbound['inet4_address'] = $addr;
|
||||
}
|
||||
}
|
||||
unset($inbound['address']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings');
|
||||
$array = [];
|
||||
$array['tag'] = $server['name'];
|
||||
$array['type'] = 'shadowsocks';
|
||||
$array['server'] = $server['host'];
|
||||
$array['server_port'] = $server['port'];
|
||||
$array['method'] = data_get($protocol_settings, 'cipher');
|
||||
$array['password'] = data_get($server, 'password', $password);
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$array['plugin'] = data_get($protocol_settings, 'plugin');
|
||||
$array['plugin_opts'] = data_get($protocol_settings, 'plugin_opts', '');
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
protected function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [
|
||||
'tag' => $server['name'],
|
||||
'type' => 'vmess',
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'uuid' => $uuid,
|
||||
'security' => 'auto',
|
||||
'alter_id' => 0,
|
||||
];
|
||||
|
||||
if ($protocol_settings['tls']) {
|
||||
$array['tls'] = [
|
||||
'enabled' => true,
|
||||
'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'),
|
||||
];
|
||||
|
||||
$this->appendUtls($array['tls'], $protocol_settings);
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['tls']['server_name'] = $serverName;
|
||||
}
|
||||
}
|
||||
|
||||
$this->appendMultiplex($array, $protocol_settings);
|
||||
|
||||
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||
$array['transport'] = $transport;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildVless($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
"type" => "vless",
|
||||
"tag" => $server['name'],
|
||||
"server" => $server['host'],
|
||||
"server_port" => $server['port'],
|
||||
"uuid" => $password,
|
||||
"packet_encoding" => "xudp",
|
||||
];
|
||||
if ($flow = data_get($protocol_settings, 'flow')) {
|
||||
$array['flow'] = $flow;
|
||||
}
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$tlsMode = (int) data_get($protocol_settings, 'tls', 0);
|
||||
$tlsConfig = [
|
||||
'enabled' => true,
|
||||
'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);
|
||||
|
||||
switch ($tlsMode) {
|
||||
case 1:
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$tlsConfig['server_name'] = $serverName;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$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;
|
||||
}
|
||||
|
||||
$array['tls'] = $tlsConfig;
|
||||
}
|
||||
|
||||
$this->appendMultiplex($array, $protocol_settings);
|
||||
|
||||
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||
$array['transport'] = $transport;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [
|
||||
'tag' => $server['name'],
|
||||
'type' => 'trojan',
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'password' => $password,
|
||||
];
|
||||
|
||||
$tlsMode = (int) data_get($protocol_settings, 'tls', 1);
|
||||
$tlsConfig = ['enabled' => true];
|
||||
|
||||
switch ($tlsMode) {
|
||||
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);
|
||||
|
||||
if ($transport = $this->buildTransport($protocol_settings, $server)) {
|
||||
$array['transport'] = $transport;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildHysteria($password, $server): array
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$baseConfig = [
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'tag' => $server['name'],
|
||||
'tls' => [
|
||||
'enabled' => true,
|
||||
'insecure' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||
]
|
||||
];
|
||||
// 支持 1.11.0 版本及以上 `server_ports` 和 `hop_interval` 配置
|
||||
if ($this->supportsFeature('sing-box', '1.11.0')) {
|
||||
if (isset($server['ports'])) {
|
||||
$baseConfig['server_ports'] = [str_replace('-', ':', $server['ports'])];
|
||||
}
|
||||
if (isset($protocol_settings['hop_interval'])) {
|
||||
$baseConfig['hop_interval'] = "{$protocol_settings['hop_interval']}s";
|
||||
}
|
||||
}
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$baseConfig['tls']['server_name'] = $serverName;
|
||||
}
|
||||
$speedConfig = [
|
||||
'up_mbps' => data_get($protocol_settings, 'bandwidth.up'),
|
||||
'down_mbps' => data_get($protocol_settings, 'bandwidth.down'),
|
||||
];
|
||||
$versionConfig = match (data_get($protocol_settings, 'version', 1)) {
|
||||
2 => [
|
||||
'type' => 'hysteria2',
|
||||
'password' => $password,
|
||||
'obfs' => data_get($protocol_settings, 'obfs.open') ? [
|
||||
'type' => data_get($protocol_settings, 'obfs.type'),
|
||||
'password' => data_get($protocol_settings, 'obfs.password')
|
||||
] : null,
|
||||
],
|
||||
default => [
|
||||
'type' => 'hysteria',
|
||||
'auth_str' => $password,
|
||||
'obfs' => data_get($protocol_settings, 'obfs.password'),
|
||||
'disable_mtu_discovery' => true,
|
||||
]
|
||||
};
|
||||
|
||||
return array_filter(
|
||||
array_merge($baseConfig, $speedConfig, $versionConfig),
|
||||
fn($v) => !is_null($v)
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildTuic($password, $server): array
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'type' => 'tuic',
|
||||
'tag' => $server['name'],
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'congestion_control' => data_get($protocol_settings, 'congestion_control', 'cubic'),
|
||||
'udp_relay_mode' => data_get($protocol_settings, 'udp_relay_mode', 'native'),
|
||||
'zero_rtt_handshake' => true,
|
||||
'heartbeat' => '10s',
|
||||
'tls' => [
|
||||
'enabled' => true,
|
||||
'insecure' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
||||
]
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['tls']['server_name'] = $serverName;
|
||||
}
|
||||
|
||||
if (data_get($protocol_settings, 'version') === 4) {
|
||||
$array['token'] = $password;
|
||||
} else {
|
||||
$array['uuid'] = $password;
|
||||
$array['password'] = $password;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildAnyTLS($password, $server): array
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'type' => 'anytls',
|
||||
'tag' => $server['name'],
|
||||
'server' => $server['host'],
|
||||
'password' => $password,
|
||||
'server_port' => $server['port'],
|
||||
'tls' => [
|
||||
'enabled' => true,
|
||||
'insecure' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
||||
]
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['tls']['server_name'] = $serverName;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildSocks($password, $server): array
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'type' => 'socks',
|
||||
'tag' => $server['name'],
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'version' => '5', // 默认使用 socks5
|
||||
'username' => $password,
|
||||
'password' => $password,
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'udp_over_tcp')) {
|
||||
$array['udp_over_tcp'] = true;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function buildHttp($password, $server): array
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'type' => 'http',
|
||||
'tag' => $server['name'],
|
||||
'server' => $server['host'],
|
||||
'server_port' => $server['port'],
|
||||
'username' => $password,
|
||||
'password' => $password,
|
||||
];
|
||||
|
||||
if ($path = data_get($protocol_settings, 'path')) {
|
||||
$array['path'] = $path;
|
||||
}
|
||||
|
||||
if ($headers = data_get($protocol_settings, 'headers')) {
|
||||
$array['headers'] = $headers;
|
||||
}
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = [
|
||||
'enabled' => true,
|
||||
'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false),
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['tls']['server_name'] = $serverName;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if ($multiplex = data_get($protocol_settings, 'multiplex')) {
|
||||
if (data_get($multiplex, 'enabled')) {
|
||||
$array['multiplex'] = [
|
||||
'enabled' => true,
|
||||
'protocol' => data_get($multiplex, 'protocol', 'yamux'),
|
||||
'max_connections' => data_get($multiplex, 'max_connections'),
|
||||
'min_streams' => data_get($multiplex, 'min_streams'),
|
||||
'max_streams' => data_get($multiplex, 'max_streams'),
|
||||
'padding' => (bool) data_get($multiplex, 'padding', false),
|
||||
];
|
||||
if (data_get($multiplex, 'brutal.enabled')) {
|
||||
$array['multiplex']['brutal'] = [
|
||||
'enabled' => true,
|
||||
'up_mbps' => data_get($multiplex, 'brutal.up_mbps'),
|
||||
'down_mbps' => data_get($multiplex, 'brutal.down_mbps'),
|
||||
];
|
||||
}
|
||||
$array['multiplex'] = array_filter($array['multiplex'], fn($v) => !is_null($v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function appendUtls(&$tlsConfig, $protocol_settings)
|
||||
{
|
||||
if ($utls = data_get($protocol_settings, 'utls')) {
|
||||
if (data_get($utls, 'enabled')) {
|
||||
$tlsConfig['utls'] = [
|
||||
'enabled' => true,
|
||||
'fingerprint' => Helper::getTlsFingerprint($utls)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
587
Xboard/app/Protocols/Stash.php
Normal file
587
Xboard/app/Protocols/Stash.php
Normal file
@@ -0,0 +1,587 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Stash extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['stash'];
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_TUIC,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
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' => [
|
||||
'anytls' => [
|
||||
'base_version' => '3.3.0' // AnyTLS 协议在3.3.0版本中添加
|
||||
],
|
||||
'vless' => [
|
||||
'protocol_settings.tls' => [
|
||||
'2' => '3.1.0' // Reality 在3.1.0版本中添加
|
||||
],
|
||||
'protocol_settings.flow' => [
|
||||
'xtls-rprx-vision' => '3.1.0',
|
||||
]
|
||||
],
|
||||
'hysteria' => [
|
||||
'base_version' => '2.0.0',
|
||||
'protocol_settings.version' => [
|
||||
'1' => '2.0.0', // Hysteria 1
|
||||
'2' => '2.5.0' // Hysteria 2,2.5.0 版本开始支持(2023年11月8日)
|
||||
],
|
||||
// 'protocol_settings.ports' => [
|
||||
// 'true' => '2.6.4' // Hysteria 2 端口跳转功能于2.6.4版本支持(2024年8月4日)
|
||||
// ]
|
||||
],
|
||||
'tuic' => [
|
||||
'base_version' => '2.3.0' // TUIC 协议自身需要 2.3.0+
|
||||
],
|
||||
'shadowsocks' => [
|
||||
'base_version' => '2.0.0',
|
||||
// ShadowSocks2022 在3.0.0版本中添加(2025年4月2日)
|
||||
'protocol_settings.cipher' => [
|
||||
'2022-blake3-aes-128-gcm' => '3.0.0',
|
||||
'2022-blake3-aes-256-gcm' => '3.0.0',
|
||||
'2022-blake3-chacha20-poly1305' => '3.0.0'
|
||||
]
|
||||
],
|
||||
'shadowtls' => [
|
||||
'base_version' => '3.0.0' // ShadowTLS 在3.0.0版本中添加(2025年4月2日)
|
||||
],
|
||||
'ssh' => [
|
||||
'base_version' => '2.6.4' // SSH 协议在2.6.4中添加(2024年8月4日)
|
||||
],
|
||||
'juicity' => [
|
||||
'base_version' => '2.6.4' // Juicity 协议在2.6.4中添加(2024年8月4日)
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.stash.yaml';
|
||||
const CUSTOM_CLASH_TEMPLATE_FILE = 'resources/rules/custom.clash.yaml';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.clash.yaml';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
|
||||
$template = subscribe_template('stash');
|
||||
|
||||
$config = Yaml::parse($template);
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
||||
array_push($proxy, self::buildShadowsocks($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
array_push($proxy, self::buildVmess($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VLESS) {
|
||||
array_push($proxy, $this->buildVless($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
array_push($proxy, self::buildHysteria($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
array_push($proxy, self::buildTrojan($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TUIC) {
|
||||
array_push($proxy, self::buildTuic($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
array_push($proxy, self::buildAnyTLS($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
array_push($proxy, self::buildSocks5($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HTTP) {
|
||||
array_push($proxy, self::buildHttp($item['password'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
}
|
||||
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
if (!is_array($config['proxy-groups'][$k]['proxies']))
|
||||
$config['proxy-groups'][$k]['proxies'] = [];
|
||||
$isFilter = false;
|
||||
foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
|
||||
foreach ($proxies as $dst) {
|
||||
if (!$this->isRegex($src))
|
||||
continue;
|
||||
$isFilter = true;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
|
||||
if ($this->isMatch($src, $dst)) {
|
||||
array_push($config['proxy-groups'][$k]['proxies'], $dst);
|
||||
}
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
}
|
||||
if ($isFilter)
|
||||
continue;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
$config['proxy-groups'] = array_filter($config['proxy-groups'], function ($group) {
|
||||
return $group['proxies'];
|
||||
});
|
||||
$config['proxy-groups'] = array_values($config['proxy-groups']);
|
||||
// Force the current subscription domain to be a direct rule
|
||||
$subsDomain = request()->header('Host');
|
||||
if ($subsDomain) {
|
||||
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
|
||||
}
|
||||
|
||||
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
|
||||
$yaml = str_replace('$app_name', admin_setting('app_name', 'XBoard'), $yaml);
|
||||
return response($yaml)
|
||||
->header('content-type', 'text/yaml')
|
||||
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
||||
->header('profile-update-interval', '24')
|
||||
->header('content-disposition', 'attachment;filename*=UTF-8\'\'' . rawurlencode($appName));
|
||||
}
|
||||
|
||||
public static function buildShadowsocks($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'ss';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['cipher'] = data_get($protocol_settings, 'cipher');
|
||||
$array['password'] = $uuid;
|
||||
$array['udp'] = true;
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
$array['plugin'] = $plugin;
|
||||
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
|
||||
// 根据插件类型进行字段映射
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
$array['plugin-opts'] = [
|
||||
'mode' => $parsedOpts['obfs'],
|
||||
'host' => $parsedOpts['obfs-host'],
|
||||
];
|
||||
|
||||
// 可选path参数
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$array['plugin-opts']['path'] = $parsedOpts['path'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v2ray-plugin':
|
||||
$array['plugin-opts'] = [
|
||||
'mode' => $parsedOpts['mode'] ?? 'websocket',
|
||||
'tls' => isset($parsedOpts['tls']) && $parsedOpts['tls'] == 'true',
|
||||
'host' => $parsedOpts['host'] ?? '',
|
||||
'path' => $parsedOpts['path'] ?? '/',
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
// 对于其他插件,直接使用解析出的键值对
|
||||
$array['plugin-opts'] = $parsedOpts;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'vmess';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['uuid'] = $uuid;
|
||||
$array['alterId'] = 0;
|
||||
$array['cipher'] = 'auto';
|
||||
$array['udp'] = true;
|
||||
|
||||
$array['tls'] = (bool) data_get($protocol_settings, 'tls');
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['servername'] = $serverName;
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
||||
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||
if ($headerType === 'http') {
|
||||
$array['http-opts']['path'] = data_get($protocol_settings, 'network_settings.header.request.path', ['/']);
|
||||
if ($host = data_get($protocol_settings, 'network_settings.header.request.headers.Host')) {
|
||||
$array['http-opts']['headers']['Host'] = $host;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts']['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host')) {
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
}
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
$array['grpc-opts'] = [];
|
||||
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||
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:
|
||||
break;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public function buildVless($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'vless';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['uuid'] = $uuid;
|
||||
$array['udp'] = true;
|
||||
|
||||
if ($fingerprint = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
|
||||
$array['client-fingerprint'] = $fingerprint;
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'tls')) {
|
||||
case 1:
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = data_get($protocol_settings, 'tls_settings.allow_insecure');
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['servername'] = $serverName;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$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['servername'] = $serverName;
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
$array['flow'] = data_get($protocol_settings, 'flow');
|
||||
$array['reality-opts'] = [
|
||||
'public-key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
||||
'short-id' => data_get($protocol_settings, 'reality_settings.short_id')
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data_get($protocol_settings, 'network')) {
|
||||
case 'tcp':
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
||||
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||
if ($headerType === 'http') {
|
||||
if (
|
||||
$httpOpts = array_filter([
|
||||
'headers' => data_get($protocol_settings, 'network_settings.header.request.headers'),
|
||||
'path' => data_get($protocol_settings, 'network_settings.header.request.path', ['/'])
|
||||
])
|
||||
) {
|
||||
$array['http-opts'] = $httpOpts;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts']['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host')) {
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
}
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
$array['grpc-opts']['grpc-service-name'] = data_get($protocol_settings, 'network_settings.serviceName');
|
||||
break;
|
||||
case 'h2':
|
||||
$array['network'] = 'h2';
|
||||
$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;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'trojan',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'password' => $password,
|
||||
'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')) {
|
||||
case 'tcp':
|
||||
$headerType = data_get($protocol_settings, 'network_settings.header.type', 'tcp');
|
||||
$array['network'] = ($headerType === 'http') ? 'http' : 'tcp';
|
||||
if ($headerType === 'http') {
|
||||
$array['http-opts']['path'] = data_get($protocol_settings, 'network_settings.header.request.path', ['/']);
|
||||
}
|
||||
break;
|
||||
case 'ws':
|
||||
$array['network'] = 'ws';
|
||||
$array['ws-opts']['path'] = data_get($protocol_settings, 'network_settings.path');
|
||||
if ($host = data_get($protocol_settings, 'network_settings.headers.Host')) {
|
||||
$array['ws-opts']['headers'] = ['Host' => $host];
|
||||
}
|
||||
break;
|
||||
case 'grpc':
|
||||
$array['network'] = 'grpc';
|
||||
if ($serviceName = data_get($protocol_settings, 'network_settings.serviceName'))
|
||||
$array['grpc-opts']['grpc-service-name'] = $serviceName;
|
||||
break;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildHysteria($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array['name'] = $server['name'];
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['up-speed'] = data_get($protocol_settings, 'bandwidth.up');
|
||||
$array['down-speed'] = data_get($protocol_settings, 'bandwidth.down');
|
||||
$array['skip-cert-verify'] = data_get($protocol_settings, 'tls.allow_insecure');
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
if (isset($server['ports'])) {
|
||||
$array['ports'] = $server['ports'];
|
||||
}
|
||||
switch (data_get($protocol_settings, 'version')) {
|
||||
case 1:
|
||||
$array['type'] = 'hysteria';
|
||||
$array['auth-str'] = $password;
|
||||
$array['protocol'] = 'udp';
|
||||
if (data_get($protocol_settings, 'obfs.open')) {
|
||||
$array['obfs'] = data_get($protocol_settings, 'obfs.password');
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
$array['type'] = 'hysteria2';
|
||||
$array['auth'] = $password;
|
||||
$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;
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTuic($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'tuic',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'congestion-controller' => data_get($protocol_settings, 'congestion_control', 'cubic'),
|
||||
'udp-relay-mode' => data_get($protocol_settings, 'udp_relay_mode', 'native'),
|
||||
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
||||
'reduce-rtt' => true,
|
||||
'fast-open' => true,
|
||||
'heartbeat-interval' => 10000,
|
||||
'request-timeout' => 8000,
|
||||
'max-udp-relay-packet-size' => 1500,
|
||||
'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);
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'anytls',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'password' => $password,
|
||||
'sni' => data_get($protocol_settings, 'tls.server_name'),
|
||||
'skip-cert-verify' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
||||
'udp' => true,
|
||||
];
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildSocks5($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'socks5',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'username' => $password,
|
||||
'password' => $password,
|
||||
'udp' => true,
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildHttp($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$array = [
|
||||
'name' => $server['name'],
|
||||
'type' => 'http',
|
||||
'server' => $server['host'],
|
||||
'port' => $server['port'],
|
||||
'username' => $password,
|
||||
'password' => $password,
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
$array['tls'] = true;
|
||||
$array['skip-cert-verify'] = (bool) data_get($protocol_settings, 'tls_settings.allow_insecure', false);
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$array['sni'] = $serverName;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function isRegex($exp)
|
||||
{
|
||||
if (empty($exp)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return preg_match($exp, '') !== false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function isMatch($exp, $str)
|
||||
{
|
||||
try {
|
||||
return preg_match($exp, $str);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
229
Xboard/app/Protocols/Surfboard.php
Normal file
229
Xboard/app/Protocols/Surfboard.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Surfboard extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['surfboard'];
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_ANYTLS,
|
||||
];
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surfboard.conf';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surfboard.conf';
|
||||
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
|
||||
$proxies = '';
|
||||
$proxyGroup = '';
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if (
|
||||
$item['type'] === Server::TYPE_SHADOWSOCKS
|
||||
&& in_array(data_get($item, 'protocol_settings.cipher'), [
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305'
|
||||
])
|
||||
) {
|
||||
// [Proxy]
|
||||
$proxies .= self::buildShadowsocks($item['password'], $item);
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
// [Proxy]
|
||||
$proxies .= self::buildVmess($item['password'], $item);
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
// [Proxy]
|
||||
$proxies .= self::buildTrojan($item['password'], $item);
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$proxies .= self::buildAnyTLS($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
}
|
||||
|
||||
$config = subscribe_template('surfboard');
|
||||
// Subscription link
|
||||
$subsURL = Helper::getSubscribeUrl($user['token']);
|
||||
$subsDomain = request()->header('Host');
|
||||
|
||||
$config = str_replace('$subs_link', $subsURL, $config);
|
||||
$config = str_replace('$subs_domain', $subsDomain, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
|
||||
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
|
||||
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
|
||||
$useTraffic = $upload + $download;
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||
$unusedTraffic = $totalTraffic - $useTraffic;
|
||||
$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}";
|
||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||
|
||||
return response($config, 200)
|
||||
->header('content-disposition', "attachment;filename*=UTF-8''" . rawurlencode($appName) . ".conf");
|
||||
}
|
||||
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']}=ss",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"encrypt-method=" . data_get($protocol_settings, 'cipher'),
|
||||
"password={$password}",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
|
||||
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
$config[] = "obfs={$parsedOpts['obfs']}";
|
||||
if (isset($parsedOpts['obfs-host'])) {
|
||||
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
||||
}
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$config[] = "obfs-uri={$parsedOpts['path']}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']}=vmess",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"username={$uuid}",
|
||||
"vmess-aead=true",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
array_push($config, 'tls=true');
|
||||
if (data_get($protocol_settings, 'tls_settings')) {
|
||||
$tlsSettings = data_get($protocol_settings, 'tls_settings');
|
||||
if (data_get($tlsSettings, 'allow_insecure')) {
|
||||
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allow_insecure'] ? 'true' : 'false'));
|
||||
}
|
||||
if ($sni = data_get($tlsSettings, 'server_name')) {
|
||||
array_push($config, "sni={$sni}");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data_get($protocol_settings, 'network') === 'ws') {
|
||||
array_push($config, 'ws=true');
|
||||
if (data_get($protocol_settings, 'network_settings')) {
|
||||
$wsSettings = data_get($protocol_settings, 'network_settings');
|
||||
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
||||
array_push($config, "ws-path={$wsSettings['path']}");
|
||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
|
||||
}
|
||||
}
|
||||
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']}=trojan",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
data_get($protocol_settings, 'server_name') ? "sni=" . data_get($protocol_settings, 'server_name') : "",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
if (data_get($protocol_settings, 'allow_insecure')) {
|
||||
array_push($config, !!data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
|
||||
}
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=anytls",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
"tfo=true",
|
||||
"udp-relay=true"
|
||||
];
|
||||
|
||||
// SNI
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
|
||||
// 跳过证书校验
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = "skip-cert-verify=true";
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
|
||||
return implode(',', $config) . "\r\n";
|
||||
}
|
||||
}
|
||||
319
Xboard/app/Protocols/Surge.php
Normal file
319
Xboard/app/Protocols/Surge.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Support\AbstractProtocol;
|
||||
use App\Models\Server;
|
||||
|
||||
class Surge extends AbstractProtocol
|
||||
{
|
||||
public $flags = ['surge'];
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surge.conf';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surge.conf';
|
||||
|
||||
public $allowedProtocols = [
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_ANYTLS,
|
||||
Server::TYPE_SOCKS,
|
||||
Server::TYPE_HTTP,
|
||||
];
|
||||
protected $protocolRequirements = [
|
||||
'surge.hysteria.protocol_settings.version' => [2 => '2398'],
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
|
||||
$appName = admin_setting('app_name', 'XBoard');
|
||||
|
||||
$proxies = '';
|
||||
$proxyGroup = '';
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if (
|
||||
$item['type'] === Server::TYPE_SHADOWSOCKS
|
||||
&& in_array(data_get($item, 'protocol_settings.cipher'), [
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm'
|
||||
])
|
||||
) {
|
||||
$proxies .= self::buildShadowsocks($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_VMESS) {
|
||||
$proxies .= self::buildVmess($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_TROJAN) {
|
||||
$proxies .= self::buildTrojan($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
||||
$proxies .= self::buildHysteria($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$proxies .= self::buildAnyTLS($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_SOCKS) {
|
||||
$proxies .= self::buildSocks($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_HTTP) {
|
||||
$proxies .= self::buildHttp($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$config = subscribe_template('surge');
|
||||
|
||||
// Subscription link
|
||||
$subsDomain = request()->header('Host');
|
||||
$subsURL = Helper::getSubscribeUrl($user['token'], $subsDomain ? 'https://' . $subsDomain : null);
|
||||
|
||||
$config = str_replace('$subs_link', $subsURL, $config);
|
||||
$config = str_replace('$subs_domain', $subsDomain, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
|
||||
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
|
||||
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
|
||||
$useTraffic = $upload + $download;
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
|
||||
$unusedTraffic = $totalTraffic - $useTraffic;
|
||||
$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}";
|
||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||
|
||||
return response($config, 200)
|
||||
->header('content-type', 'application/octet-stream')
|
||||
->header('content-disposition', "attachment;filename*=UTF-8''" . rawurlencode($appName) . ".conf");
|
||||
}
|
||||
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']} = ss",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"encrypt-method={$protocol_settings['cipher']}",
|
||||
"password={$password}",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
|
||||
$plugin = data_get($protocol_settings, 'plugin');
|
||||
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
|
||||
// 解析插件选项
|
||||
$parsedOpts = collect(explode(';', $pluginOpts))
|
||||
->filter()
|
||||
->mapWithKeys(function ($pair) {
|
||||
if (!str_contains($pair, '=')) {
|
||||
return [];
|
||||
}
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
return [trim($key) => trim($value)];
|
||||
})
|
||||
->all();
|
||||
switch ($plugin) {
|
||||
case 'obfs':
|
||||
$config[] = "obfs={$parsedOpts['obfs']}";
|
||||
if (isset($parsedOpts['obfs-host'])) {
|
||||
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
|
||||
}
|
||||
if (isset($parsedOpts['path'])) {
|
||||
$config[] = "obfs-uri={$parsedOpts['path']}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']} = vmess",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"username={$uuid}",
|
||||
"vmess-aead=true",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
array_push($config, 'tls=true');
|
||||
if (data_get($protocol_settings, 'tls_settings')) {
|
||||
$tlsSettings = data_get($protocol_settings, 'tls_settings');
|
||||
if (data_get($tlsSettings, 'allow_insecure'))
|
||||
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allow_insecure'] ? 'true' : 'false'));
|
||||
if (data_get($tlsSettings, 'server_name'))
|
||||
array_push($config, "sni={$tlsSettings['server_name']}");
|
||||
}
|
||||
}
|
||||
if (data_get($protocol_settings, 'network') === 'ws') {
|
||||
array_push($config, 'ws=true');
|
||||
if (data_get($protocol_settings, 'network_settings')) {
|
||||
$wsSettings = data_get($protocol_settings, 'network_settings');
|
||||
if (data_get($wsSettings, 'path'))
|
||||
array_push($config, "ws-path={$wsSettings['path']}");
|
||||
if (data_get($wsSettings, 'headers.Host'))
|
||||
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
|
||||
}
|
||||
}
|
||||
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$config = [
|
||||
"{$server['name']} = trojan",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
data_get($protocol_settings, 'server_name') ? "sni=" . data_get($protocol_settings, 'server_name') : "",
|
||||
'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
if (!empty($protocol_settings['allow_insecure'])) {
|
||||
array_push($config, !!data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
|
||||
}
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
//参考文档: https://manual.nssurge.com/policy/proxy.html
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$config = [
|
||||
"{$server['name']} = anytls",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
];
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = 'skip-cert-verify=true';
|
||||
}
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
//参考文档: https://manual.nssurge.com/policy/proxy.html
|
||||
public static function buildHysteria($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
if ($protocol_settings['version'] != 2)
|
||||
return '';
|
||||
$config = [
|
||||
"{$server['name']} = hysteria2",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
$protocol_settings['tls']['server_name'] ? "sni={$protocol_settings['tls']['server_name']}" : "",
|
||||
// 'tfo=true',
|
||||
'udp-relay=true'
|
||||
];
|
||||
if (data_get($protocol_settings, 'bandwidth.up')) {
|
||||
$config[] = "upload-bandwidth={$protocol_settings['bandwidth']['up']}";
|
||||
}
|
||||
if (data_get($protocol_settings, 'bandwidth.down')) {
|
||||
$config[] = "download-bandwidth={$protocol_settings['bandwidth']['down']}";
|
||||
}
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = !!data_get($protocol_settings, 'tls.allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false';
|
||||
}
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
//参考文档: https://manual.nssurge.com/policy/proxy.html
|
||||
public static function buildSocks($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$type = data_get($protocol_settings, 'tls') ? 'socks5-tls' : 'socks5';
|
||||
$config = [
|
||||
"{$server['name']} = {$type}",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
"{$password}",
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
if (data_get($protocol_settings, 'tls_settings.allow_insecure')) {
|
||||
$config[] = 'skip-cert-verify=true';
|
||||
}
|
||||
}
|
||||
$config[] = 'udp-relay=true';
|
||||
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
//参考文档: https://manual.nssurge.com/policy/proxy.html
|
||||
public static function buildHttp($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
$type = data_get($protocol_settings, 'tls') ? 'https' : 'http';
|
||||
$config = [
|
||||
"{$server['name']} = {$type}",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
"{$password}",
|
||||
];
|
||||
|
||||
if (data_get($protocol_settings, 'tls')) {
|
||||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
if (data_get($protocol_settings, 'tls_settings.allow_insecure')) {
|
||||
$config[] = 'skip-cert-verify=true';
|
||||
}
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
$uri = implode(',', $config);
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user