Files
Xboard/app/Protocols/General.php
xboard b55091a066 feat: Refactor uTLS & Multiplex Support, Node Status Push Optimization
- Server/ServerSave/Server.php: Unified utls and multiplex schema, validation, and defaults for vmess/vless/trojan/mieru protocols, enabling more flexible protocol configuration.
- Protocols (SingBox/ClashMeta/Shadowrocket/Stash/General): All protocol generators now support utls (client-fingerprint/fp) and multiplex options. Removed getRandFingerprint, replaced with getTlsFingerprint supporting random/custom fingerprints.
- Helper.php: Refactored TLS fingerprint utility to support object/string/random input.
- ServerService: Abstracted updateMetrics method to unify HTTP/WS node status caching logic.
- NodeWebSocketServer: Improved node connection, status push, and full sync logic; adjusted log levels; clarified push logic.
- ServerController: Reused ServerService for node metrics handling, reducing code duplication.
- Docs: Improved aapanel installation docs, added fix for empty admin dashboard.
2026-03-16 23:09:56 +08:00

411 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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');
}
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;
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' => '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 ($fp = Helper::getTlsFingerprint(data_get($protocol_settings, 'utls'))) {
$config['fp'] = $fp;
}
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
$config['sni'] = $serverName;
}
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 ($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 = [];
$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;
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 ($obfsPassword = data_get($protocol_settings, 'obfs.password'))
$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;
$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);
$uri = "anytls://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\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";
}
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;
}
}