mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-03 18:40:52 +08:00
321 lines
12 KiB
PHP
321 lines
12 KiB
PHP
<?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_SOCKS,
|
||
Server::TYPE_TUIC,
|
||
];
|
||
|
||
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_SOCKS => self::buildSocks($item['password'], $item),
|
||
Server::TYPE_TUIC => self::buildTuic($item['password'], $item),
|
||
default => '',
|
||
};
|
||
}
|
||
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("{$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=' . $plugin . ';' . rawurlencode($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" => $server['protocol_settings']['network'],
|
||
"type" => "none",
|
||
"host" => "",
|
||
"path" => "",
|
||
"tls" => $protocol_settings['tls'] ? "tls" : "",
|
||
];
|
||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||
$config['sni'] = $serverName;
|
||
}
|
||
|
||
switch ($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;
|
||
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' => 'none', //加密方式
|
||
'type' => $server['protocol_settings']['network'], //传输协议
|
||
'flow' => $protocol_settings['flow'] ? $protocol_settings['flow'] : null,
|
||
];
|
||
// 处理TLS
|
||
switch ($server['protocol_settings']['tls']) {
|
||
case 1:
|
||
$config['security'] = "tls";
|
||
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
||
$config['sni'] = $serverName;
|
||
$config['fp'] = Helper::getRandFingerprint();
|
||
}
|
||
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'] = "/";
|
||
$config['fp'] = Helper::getRandFingerprint();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
// 处理传输协议
|
||
switch ($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 '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 = [];
|
||
$array['allowInsecure'] = $protocol_settings['allow_insecure'];
|
||
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
||
$array['peer'] = $serverName;
|
||
$array['sni'] = $serverName;
|
||
}
|
||
switch ($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;
|
||
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 = [];
|
||
// Return empty if version is not 2
|
||
if ($server['protocol_settings']['version'] !== 2) {
|
||
return '';
|
||
}
|
||
|
||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||
$params['sni'] = $serverName;
|
||
$params['security'] = 'tls';
|
||
}
|
||
|
||
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'];
|
||
}
|
||
|
||
$params['insecure'] = data_get($protocol_settings, 'tls.allow_insecure');
|
||
|
||
$query = http_build_query($params);
|
||
$name = rawurlencode($server['name']);
|
||
$addr = Helper::wrapIPv6($server['host']);
|
||
|
||
$uri = "hysteria2://{$password}@{$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;
|
||
|
||
$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 buildSocks($password, $server)
|
||
{
|
||
$name = rawurlencode($server['name']);
|
||
$credentials = base64_encode("{$password}:{$password}");
|
||
return "socks://{$credentials}@{$server['host']}:{$server['port']}#{$name}\r\n";
|
||
}
|
||
}
|