2023-11-17 14:44:01 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
namespace App\Protocols;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Utils\Helper;
|
2025-05-10 17:10:41 +08:00
|
|
|
|
use Illuminate\Support\Arr;
|
2025-05-16 05:13:49 +08:00
|
|
|
|
use Illuminate\Support\Facades\File;
|
2025-05-22 17:58:22 +08:00
|
|
|
|
use App\Support\AbstractProtocol;
|
2025-07-18 15:42:58 +08:00
|
|
|
|
use App\Models\Server;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-05-22 17:58:22 +08:00
|
|
|
|
class SingBox extends AbstractProtocol
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
2025-05-22 17:58:22 +08:00
|
|
|
|
public $flags = ['sing-box', 'hiddify', 'sfm'];
|
2025-07-18 15:42:58 +08:00
|
|
|
|
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,
|
2026-03-15 09:49:11 +08:00
|
|
|
|
Server::TYPE_MIERU,
|
2025-07-18 15:42:58 +08:00
|
|
|
|
];
|
2024-05-14 21:57:36 +08:00
|
|
|
|
private $config;
|
2025-05-16 05:13:49 +08:00
|
|
|
|
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.sing-box.json';
|
|
|
|
|
|
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.sing-box.json';
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-05-22 17:58:22 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 多客户端协议支持配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
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'
|
|
|
|
|
|
],
|
|
|
|
|
|
'shadowtls' => [
|
|
|
|
|
|
'base_version' => '1.6.0'
|
|
|
|
|
|
],
|
|
|
|
|
|
'wireguard' => [
|
|
|
|
|
|
'base_version' => '1.5.0'
|
2025-05-24 13:45:32 +08:00
|
|
|
|
],
|
|
|
|
|
|
'anytls' => [
|
|
|
|
|
|
'base_version' => '1.12.0'
|
2026-03-15 09:49:11 +08:00
|
|
|
|
],
|
|
|
|
|
|
'mieru' => [
|
|
|
|
|
|
'base_version' => '1.12.0'
|
2025-05-22 17:58:22 +08:00
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
2025-01-21 14:57:54 +08:00
|
|
|
|
|
2023-11-17 14:44:01 +08:00
|
|
|
|
public function handle()
|
|
|
|
|
|
{
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$appName = admin_setting('app_name', 'XBoard');
|
|
|
|
|
|
$this->config = $this->loadConfig();
|
|
|
|
|
|
$this->buildOutbounds();
|
2024-07-19 06:29:35 +08:00
|
|
|
|
$this->buildRule();
|
2026-03-15 09:49:11 +08:00
|
|
|
|
$this->adaptConfigForVersion();
|
2023-12-03 22:17:33 +08:00
|
|
|
|
$user = $this->user;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2024-07-19 06:29:35 +08:00
|
|
|
|
return response()
|
|
|
|
|
|
->json($this->config)
|
2025-01-21 14:57:54 +08:00
|
|
|
|
->header('profile-title', 'base64:' . base64_encode($appName))
|
2023-12-03 22:17:33 +08:00
|
|
|
|
->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}")
|
2024-07-19 06:29:35 +08:00
|
|
|
|
->header('profile-update-interval', '24');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected function loadConfig()
|
|
|
|
|
|
{
|
2026-03-11 05:47:29 +08:00
|
|
|
|
$jsonData = subscribe_template('singbox');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-06-21 12:11:27 +08:00
|
|
|
|
return is_array($jsonData) ? $jsonData : json_decode($jsonData, true);
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected function buildOutbounds()
|
|
|
|
|
|
{
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$outbounds = $this->config['outbounds'];
|
|
|
|
|
|
$proxies = [];
|
2023-11-17 14:44:01 +08:00
|
|
|
|
foreach ($this->servers as $item) {
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$protocol_settings = $item['protocol_settings'];
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_SHADOWSOCKS) {
|
2024-05-24 22:45:27 +08:00
|
|
|
|
$ssConfig = $this->buildShadowsocks($item['password'], $item);
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$proxies[] = $ssConfig;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_TROJAN) {
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$trojanConfig = $this->buildTrojan($this->user['uuid'], $item);
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$proxies[] = $trojanConfig;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_VMESS) {
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$vmessConfig = $this->buildVmess($this->user['uuid'], $item);
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$proxies[] = $vmessConfig;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-05-10 17:10:41 +08:00
|
|
|
|
if (
|
2025-07-18 15:42:58 +08:00
|
|
|
|
$item['type'] === Server::TYPE_VLESS
|
2025-05-10 17:10:41 +08:00
|
|
|
|
&& in_array(data_get($protocol_settings, 'network'), ['tcp', 'ws', 'grpc', 'http', 'quic', 'httpupgrade'])
|
2025-01-21 14:57:54 +08:00
|
|
|
|
) {
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$vlessConfig = $this->buildVless($this->user['uuid'], $item);
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$proxies[] = $vlessConfig;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_HYSTERIA) {
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$hysteriaConfig = $this->buildHysteria($this->user['uuid'], $item);
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$proxies[] = $hysteriaConfig;
|
|
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_TUIC) {
|
2025-02-23 00:13:04 +08:00
|
|
|
|
$tuicConfig = $this->buildTuic($this->user['uuid'], $item);
|
|
|
|
|
|
$proxies[] = $tuicConfig;
|
|
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_ANYTLS) {
|
2025-05-19 09:25:52 +08:00
|
|
|
|
$anytlsConfig = $this->buildAnyTLS($this->user['uuid'], $item);
|
|
|
|
|
|
$proxies[] = $anytlsConfig;
|
|
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_SOCKS) {
|
2025-05-07 19:48:19 +08:00
|
|
|
|
$socksConfig = $this->buildSocks($this->user['uuid'], $item);
|
|
|
|
|
|
$proxies[] = $socksConfig;
|
|
|
|
|
|
}
|
2025-07-18 15:42:58 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_HTTP) {
|
2025-05-07 19:48:19 +08:00
|
|
|
|
$httpConfig = $this->buildHttp($this->user['uuid'], $item);
|
|
|
|
|
|
$proxies[] = $httpConfig;
|
|
|
|
|
|
}
|
2026-03-15 09:49:11 +08:00
|
|
|
|
if ($item['type'] === Server::TYPE_MIERU) {
|
|
|
|
|
|
$mieruConfig = $this->buildMieru($this->user['uuid'], $item);
|
|
|
|
|
|
$proxies[] = $mieruConfig;
|
|
|
|
|
|
}
|
2024-05-14 21:57:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
foreach ($outbounds as &$outbound) {
|
|
|
|
|
|
if (in_array($outbound['type'], ['urltest', 'selector'])) {
|
|
|
|
|
|
array_push($outbound['outbounds'], ...array_column($proxies, 'tag'));
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-14 21:57:36 +08:00
|
|
|
|
$outbounds = array_merge($outbounds, $proxies);
|
|
|
|
|
|
$this->config['outbounds'] = $outbounds;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
return $outbounds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-19 06:29:35 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* Build rule
|
|
|
|
|
|
*/
|
2025-01-21 14:57:54 +08:00
|
|
|
|
protected function buildRule()
|
|
|
|
|
|
{
|
2024-07-19 06:29:35 +08:00
|
|
|
|
$rules = $this->config['route']['rules'];
|
|
|
|
|
|
// Force the nodes ip to be a direct rule
|
2025-02-23 00:33:03 +08:00
|
|
|
|
// array_unshift($rules, [
|
|
|
|
|
|
// 'ip_cidr' => collect($this->servers)->pluck('host')->map(function ($host) {
|
|
|
|
|
|
// return filter_var($host, FILTER_VALIDATE_IP) ? [$host] : Helper::getIpByDomainName($host);
|
|
|
|
|
|
// })->flatten()->unique()->values(),
|
|
|
|
|
|
// 'outbound' => 'direct',
|
|
|
|
|
|
// ]);
|
2024-07-19 06:29:35 +08:00
|
|
|
|
$this->config['route']['rules'] = $rules;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 09:49:11 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 根据客户端版本自适应配置格式
|
|
|
|
|
|
*
|
|
|
|
|
|
* sing-box 版本断点:
|
|
|
|
|
|
* - 1.8.0: rule_set 替代 geoip/geosite db, cache_file 替代 clash_api.cache_file
|
|
|
|
|
|
* - 1.10.0: address 数组替代 inet4_address/inet6_address
|
|
|
|
|
|
* - 1.11.0: 移除 endpoint_independent_nat, sniff_override_destination
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function adaptConfigForVersion(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$coreVersion = $this->getSingBoxCoreVersion();
|
|
|
|
|
|
if (empty($coreVersion)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// >= 1.11.0: 移除已废弃字段,避免 "配置已过时" 警告
|
|
|
|
|
|
if (version_compare($coreVersion, '1.11.0', '>=')) {
|
|
|
|
|
|
$this->removeDeprecatedFieldsV111();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// < 1.10.0: address 数组 → inet4_address/inet6_address
|
|
|
|
|
|
if (version_compare($coreVersion, '1.10.0', '<')) {
|
|
|
|
|
|
$this->convertAddressToLegacy();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取实际 sing-box 核心版本
|
|
|
|
|
|
*
|
|
|
|
|
|
* sing-box 客户端直接报核心版本,hiddify/sfm 等 wrapper 客户端
|
|
|
|
|
|
* 报的是 app 版本,需要映射到对应的 sing-box 核心版本
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getSingBoxCoreVersion(): ?string
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($this->clientVersion)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// sing-box 原生客户端,版本即核心版本
|
|
|
|
|
|
if ($this->clientName === 'sing-box') {
|
|
|
|
|
|
return $this->clientVersion;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Hiddify/SFM 等 wrapper 默认内置较新的 sing-box 核心
|
|
|
|
|
|
// 保守策略: 直接按最新格式输出(移除废弃字段),因为这些客户端普遍内置 >= 1.11 的核心
|
|
|
|
|
|
return '1.11.0';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* sing-box >= 1.11.0: 移除废弃字段
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function removeDeprecatedFieldsV111(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isset($this->config['inbounds'])) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ($this->config['inbounds'] as &$inbound) {
|
|
|
|
|
|
unset($inbound['endpoint_independent_nat']);
|
|
|
|
|
|
unset($inbound['sniff_override_destination']);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* sing-box < 1.10.0: 将 tun address 数组转换为 inet4_address/inet6_address
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function convertAddressToLegacy(): 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']);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-17 14:44:01 +08:00
|
|
|
|
protected function buildShadowsocks($password, $server)
|
|
|
|
|
|
{
|
2025-05-22 17:58:22 +08:00
|
|
|
|
$protocol_settings = data_get($server, 'protocol_settings');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$array = [];
|
|
|
|
|
|
$array['tag'] = $server['name'];
|
|
|
|
|
|
$array['type'] = 'shadowsocks';
|
|
|
|
|
|
$array['server'] = $server['host'];
|
|
|
|
|
|
$array['server_port'] = $server['port'];
|
2025-05-22 17:58:22 +08:00
|
|
|
|
$array['method'] = data_get($protocol_settings, 'cipher');
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$array['password'] = data_get($server, 'password', $password);
|
2025-05-22 17:58:22 +08:00
|
|
|
|
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', '');
|
|
|
|
|
|
}
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected function buildVmess($uuid, $server)
|
|
|
|
|
|
{
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$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,
|
2026-03-15 09:49:11 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if ($protocol_settings['tls']) {
|
|
|
|
|
|
$array['tls'] = [
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'enabled' => true,
|
|
|
|
|
|
'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'),
|
2026-03-15 09:49:11 +08:00
|
|
|
|
];
|
|
|
|
|
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
|
|
|
|
|
$array['tls']['server_name'] = $serverName;
|
|
|
|
|
|
}
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$transport = match ($protocol_settings['network']) {
|
2025-08-16 03:08:56 +09:00
|
|
|
|
'tcp' => data_get($protocol_settings, 'network_settings.header.type', 'none') !== 'none' ? [
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'type' => 'http',
|
2025-08-24 18:39:00 +08:00
|
|
|
|
'path' => Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/'])),
|
|
|
|
|
|
'host' => data_get($protocol_settings, 'network_settings.header.request.headers.Host', [])
|
2025-08-16 03:08:56 +09:00
|
|
|
|
] : null,
|
2025-09-12 10:45:31 +08:00
|
|
|
|
'ws' => array_filter([
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'type' => 'ws',
|
|
|
|
|
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
|
|
|
|
'headers' => ($host = data_get($protocol_settings, 'network_settings.headers.Host')) ? ['Host' => $host] : null,
|
|
|
|
|
|
'max_early_data' => 2048,
|
|
|
|
|
|
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
2025-09-12 10:45:31 +08:00
|
|
|
|
]),
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'grpc' => [
|
|
|
|
|
|
'type' => 'grpc',
|
|
|
|
|
|
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
|
|
|
|
],
|
2026-03-15 09:49:11 +08:00
|
|
|
|
'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'
|
|
|
|
|
|
],
|
2025-01-21 14:57:54 +08:00
|
|
|
|
default => null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if ($transport) {
|
|
|
|
|
|
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
|
|
|
|
|
}
|
2023-11-17 14:44:01 +08:00
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected function buildVless($password, $server)
|
|
|
|
|
|
{
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$array = [
|
|
|
|
|
|
"type" => "vless",
|
|
|
|
|
|
"tag" => $server['name'],
|
|
|
|
|
|
"server" => $server['host'],
|
|
|
|
|
|
"server_port" => $server['port'],
|
|
|
|
|
|
"uuid" => $password,
|
2025-01-21 14:57:54 +08:00
|
|
|
|
"packet_encoding" => "xudp",
|
|
|
|
|
|
'flow' => data_get($protocol_settings, 'flow', ''),
|
2023-11-17 14:44:01 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
if ($protocol_settings['tls']) {
|
|
|
|
|
|
$tlsConfig = [
|
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
|
'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'),
|
|
|
|
|
|
'utls' => [
|
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
|
'fingerprint' => Helper::getRandFingerprint()
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
switch ($protocol_settings['tls']) {
|
|
|
|
|
|
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');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$tlsConfig['reality'] = [
|
|
|
|
|
|
'enabled' => true,
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'public_key' => data_get($protocol_settings, 'reality_settings.public_key'),
|
|
|
|
|
|
'short_id' => data_get($protocol_settings, 'reality_settings.short_id')
|
2023-11-17 14:44:01 +08:00
|
|
|
|
];
|
2025-01-21 14:57:54 +08:00
|
|
|
|
break;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-01-21 14:57:54 +08:00
|
|
|
|
|
2023-11-17 14:44:01 +08:00
|
|
|
|
$array['tls'] = $tlsConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$transport = match ($protocol_settings['network']) {
|
|
|
|
|
|
'tcp' => data_get($protocol_settings, 'network_settings.header.type') == 'http' ? [
|
|
|
|
|
|
'type' => 'http',
|
2025-05-16 05:13:49 +08:00
|
|
|
|
'path' => Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', ['/']))
|
2025-01-21 14:57:54 +08:00
|
|
|
|
] : null,
|
|
|
|
|
|
'ws' => array_filter([
|
|
|
|
|
|
'type' => 'ws',
|
|
|
|
|
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
|
|
|
|
'headers' => ($host = data_get($protocol_settings, 'network_settings.headers.Host')) ? ['Host' => $host] : null,
|
|
|
|
|
|
'max_early_data' => 2048,
|
|
|
|
|
|
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
|
|
|
|
|
], fn($value) => !is_null($value)),
|
|
|
|
|
|
'grpc' => [
|
|
|
|
|
|
'type' => 'grpc',
|
|
|
|
|
|
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
|
|
|
|
],
|
|
|
|
|
|
'h2' => [
|
|
|
|
|
|
'type' => 'http',
|
|
|
|
|
|
'host' => data_get($protocol_settings, 'network_settings.host'),
|
|
|
|
|
|
'path' => data_get($protocol_settings, 'network_settings.path')
|
|
|
|
|
|
],
|
|
|
|
|
|
'httpupgrade' => [
|
|
|
|
|
|
'type' => 'httpupgrade',
|
|
|
|
|
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
|
|
|
|
'host' => data_get($protocol_settings, 'network_settings.host', $server['host']),
|
|
|
|
|
|
'headers' => data_get($protocol_settings, 'network_settings.headers')
|
|
|
|
|
|
],
|
2026-03-15 09:49:11 +08:00
|
|
|
|
'quic' => [
|
|
|
|
|
|
'type' => 'quic'
|
|
|
|
|
|
],
|
2025-01-21 14:57:54 +08:00
|
|
|
|
default => null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if ($transport) {
|
|
|
|
|
|
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-14 21:57:36 +08:00
|
|
|
|
protected function buildTrojan($password, $server)
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$protocol_settings = $server['protocol_settings'];
|
|
|
|
|
|
$array = [
|
|
|
|
|
|
'tag' => $server['name'],
|
|
|
|
|
|
'type' => 'trojan',
|
|
|
|
|
|
'server' => $server['host'],
|
|
|
|
|
|
'server_port' => $server['port'],
|
|
|
|
|
|
'password' => $password,
|
|
|
|
|
|
'tls' => [
|
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
|
'insecure' => (bool) data_get($protocol_settings, 'allow_insecure', false),
|
|
|
|
|
|
]
|
2023-11-17 14:44:01 +08:00
|
|
|
|
];
|
2025-08-16 02:44:17 +09:00
|
|
|
|
if ($serverName = data_get($protocol_settings, 'server_name')) {
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$array['tls']['server_name'] = $serverName;
|
2024-05-14 21:57:36 +08:00
|
|
|
|
}
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$transport = match (data_get($protocol_settings, 'network')) {
|
|
|
|
|
|
'grpc' => [
|
|
|
|
|
|
'type' => 'grpc',
|
|
|
|
|
|
'service_name' => data_get($protocol_settings, 'network_settings.serviceName')
|
|
|
|
|
|
],
|
2025-09-12 10:45:31 +08:00
|
|
|
|
'ws' => array_filter([
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'type' => 'ws',
|
|
|
|
|
|
'path' => data_get($protocol_settings, 'network_settings.path'),
|
|
|
|
|
|
'headers' => data_get($protocol_settings, 'network_settings.headers.Host') ? ['Host' => [data_get($protocol_settings, 'network_settings.headers.Host')]] : null,
|
|
|
|
|
|
'max_early_data' => 2048,
|
|
|
|
|
|
'early_data_header_name' => 'Sec-WebSocket-Protocol'
|
2025-09-12 10:45:31 +08:00
|
|
|
|
]),
|
2025-01-21 14:57:54 +08:00
|
|
|
|
default => null
|
|
|
|
|
|
};
|
2026-03-15 09:49:11 +08:00
|
|
|
|
if ($transport) {
|
|
|
|
|
|
$array['transport'] = array_filter($transport, fn($value) => !is_null($value));
|
|
|
|
|
|
}
|
2023-11-17 14:44:01 +08:00
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
protected function buildHysteria($password, $server): array
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$protocol_settings = $server['protocol_settings'];
|
|
|
|
|
|
$baseConfig = [
|
2023-11-17 14:44:01 +08:00
|
|
|
|
'server' => $server['host'],
|
|
|
|
|
|
'server_port' => $server['port'],
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'tag' => $server['name'],
|
2023-11-17 14:44:01 +08:00
|
|
|
|
'tls' => [
|
|
|
|
|
|
'enabled' => true,
|
2025-08-23 11:19:37 +08:00
|
|
|
|
'insecure' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
2023-11-17 14:44:01 +08:00
|
|
|
|
]
|
|
|
|
|
|
];
|
2025-05-24 13:45:32 +08:00
|
|
|
|
// 支持 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";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-16 02:44:17 +09:00
|
|
|
|
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$baseConfig['tls']['server_name'] = $serverName;
|
2025-05-10 17:10:41 +08:00
|
|
|
|
}
|
2025-01-21 14:57:54 +08:00
|
|
|
|
$speedConfig = [
|
2025-08-23 11:19:37 +08:00
|
|
|
|
'up_mbps' => data_get($protocol_settings, 'bandwidth.up'),
|
|
|
|
|
|
'down_mbps' => data_get($protocol_settings, 'bandwidth.down'),
|
2025-01-21 14:57:54 +08:00
|
|
|
|
];
|
|
|
|
|
|
$versionConfig = match (data_get($protocol_settings, 'version', 1)) {
|
|
|
|
|
|
2 => [
|
|
|
|
|
|
'type' => 'hysteria2',
|
|
|
|
|
|
'password' => $password,
|
2025-08-23 11:19:37 +08:00
|
|
|
|
'obfs' => data_get($protocol_settings, 'obfs.open') ? [
|
|
|
|
|
|
'type' => data_get($protocol_settings, 'obfs.type'),
|
|
|
|
|
|
'password' => data_get($protocol_settings, 'obfs.password')
|
2025-01-21 14:57:54 +08:00
|
|
|
|
] : null,
|
|
|
|
|
|
],
|
|
|
|
|
|
default => [
|
|
|
|
|
|
'type' => 'hysteria',
|
|
|
|
|
|
'auth_str' => $password,
|
2025-08-23 11:19:37 +08:00
|
|
|
|
'obfs' => data_get($protocol_settings, 'obfs.password'),
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'disable_mtu_discovery' => true,
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
return array_merge(
|
|
|
|
|
|
$baseConfig,
|
|
|
|
|
|
$speedConfig,
|
|
|
|
|
|
$versionConfig
|
|
|
|
|
|
);
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
2025-02-23 00:13:04 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-05-07 19:48:19 +08:00
|
|
|
|
|
2025-05-19 09:25:52 +08:00
|
|
|
|
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'],
|
2025-05-22 17:58:22 +08:00
|
|
|
|
'tls' => [
|
|
|
|
|
|
'enabled' => true,
|
|
|
|
|
|
'insecure' => (bool) data_get($protocol_settings, 'tls.allow_insecure', false),
|
|
|
|
|
|
'alpn' => data_get($protocol_settings, 'alpn', ['h3']),
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
2025-05-19 09:25:52 +08:00
|
|
|
|
|
|
|
|
|
|
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
|
|
|
|
|
$array['tls']['server_name'] = $serverName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-07 19:48:19 +08:00
|
|
|
|
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),
|
|
|
|
|
|
];
|
2025-05-10 17:10:41 +08:00
|
|
|
|
|
2025-05-07 19:48:19 +08:00
|
|
|
|
if ($serverName = data_get($protocol_settings, 'tls_settings.server_name')) {
|
|
|
|
|
|
$array['tls']['server_name'] = $serverName;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
2026-03-15 09:49:11 +08:00
|
|
|
|
|
|
|
|
|
|
protected function buildMieru($password, $server): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$protocol_settings = data_get($server, 'protocol_settings', []);
|
|
|
|
|
|
$array = [
|
|
|
|
|
|
'type' => 'mieru',
|
|
|
|
|
|
'tag' => $server['name'],
|
|
|
|
|
|
'server' => $server['host'],
|
|
|
|
|
|
'server_port' => $server['port'],
|
|
|
|
|
|
'username' => $password,
|
|
|
|
|
|
'password' => $password,
|
|
|
|
|
|
'transport' => strtolower(data_get($protocol_settings, 'transport', 'tcp')),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($server['ports'])) {
|
|
|
|
|
|
$array['server_port_range'] = [$server['ports']];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($multiplexing = data_get($protocol_settings, 'multiplexing')) {
|
|
|
|
|
|
$array['multiplexing'] = $multiplexing;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $array;
|
|
|
|
|
|
}
|
2024-11-08 17:26:31 +08:00
|
|
|
|
}
|