mirror of
https://github.com/2dust/v2rayN.git
synced 2025-10-13 20:09:12 +00:00
Simple refactoring of CoreConfig generated code
This commit is contained in:
parent
d5c50ef27c
commit
f66226c103
20 changed files with 4267 additions and 4230 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,522 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService(Config config)
|
||||
{
|
||||
private readonly Config _config = config;
|
||||
private static readonly string _tag = "CoreConfigSingboxService";
|
||||
|
||||
#region public gen function
|
||||
|
||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| node.Port <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||
{
|
||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
|
||||
await GenInbounds(singboxConfig);
|
||||
|
||||
if (node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
var endpoints = new Endpoints4Sbox();
|
||||
await GenEndpoint(node, endpoints);
|
||||
endpoints.tag = Global.ProxyTag;
|
||||
singboxConfig.endpoints = new() { endpoints };
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenOutbound(node, singboxConfig.outbounds.First());
|
||||
}
|
||||
|
||||
await GenMoreOutbounds(node, singboxConfig);
|
||||
|
||||
await GenRouting(singboxConfig);
|
||||
|
||||
await GenDns(singboxConfig);
|
||||
|
||||
await GenExperimental(singboxConfig);
|
||||
|
||||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
List<IPEndPoint> lstIpEndPoints = new();
|
||||
List<TcpConnectionInformation> lstTcpConns = new();
|
||||
try
|
||||
{
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
//GenDns(new(), singboxConfig);
|
||||
singboxConfig.inbounds.Clear();
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//find unused port
|
||||
var port = initPort;
|
||||
for (var k = initPort; k < Global.MaxPort; k++)
|
||||
{
|
||||
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//found
|
||||
port = k;
|
||||
initPort = port + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
//Port In Used
|
||||
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
it.Port = port;
|
||||
it.AllowTest = true;
|
||||
|
||||
//inbound
|
||||
Inbound4Sbox inbound = new()
|
||||
{
|
||||
listen = Global.Loopback,
|
||||
listen_port = port,
|
||||
type = EInboundProtocol.mixed.ToString(),
|
||||
};
|
||||
inbound.tag = inbound.type + inbound.listen_port.ToString();
|
||||
singboxConfig.inbounds.Add(inbound);
|
||||
|
||||
//outbound
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS
|
||||
&& !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||
&& item.PublicKey.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var server = await GenServer(item);
|
||||
if (server is null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
var tag = Global.ProxyTag + inbound.listen_port.ToString();
|
||||
server.tag = tag;
|
||||
if (server is Endpoints4Sbox endpoint)
|
||||
{
|
||||
singboxConfig.endpoints ??= new();
|
||||
singboxConfig.endpoints.Add(endpoint);
|
||||
}
|
||||
else if (server is Outbound4Sbox outbound)
|
||||
{
|
||||
singboxConfig.outbounds.Add(outbound);
|
||||
}
|
||||
|
||||
//rule
|
||||
Rule4Sbox rule = new()
|
||||
{
|
||||
inbound = new List<string> { inbound.tag },
|
||||
outbound = tag
|
||||
};
|
||||
singboxConfig.route.rules.Add(rule);
|
||||
}
|
||||
|
||||
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
||||
{
|
||||
await GenDnsDomainsCompatible(singboxConfig, rawDNSItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
||||
}
|
||||
singboxConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = Global.SingboxFinalResolverTag
|
||||
};
|
||||
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node is not { Port: > 0 })
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp))
|
||||
{
|
||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
if (node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
var endpoints = new Endpoints4Sbox();
|
||||
await GenEndpoint(node, endpoints);
|
||||
endpoints.tag = Global.ProxyTag;
|
||||
singboxConfig.endpoints = new() { endpoints };
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenOutbound(node, singboxConfig.outbounds.First());
|
||||
}
|
||||
await GenMoreOutbounds(node, singboxConfig);
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (item != null && item.Enabled == true)
|
||||
{
|
||||
await GenDnsDomainsCompatible(singboxConfig, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenDnsDomains(singboxConfig, _config.SimpleDNSItem);
|
||||
}
|
||||
singboxConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = Global.SingboxFinalResolverTag
|
||||
};
|
||||
|
||||
singboxConfig.route.rules.Clear();
|
||||
singboxConfig.inbounds.Clear();
|
||||
singboxConfig.inbounds.Add(new()
|
||||
{
|
||||
tag = $"{EInboundProtocol.mixed}{port}",
|
||||
listen = Global.Loopback,
|
||||
listen_port = port,
|
||||
type = EInboundProtocol.mixed.ToString(),
|
||||
});
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(result);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(singboxConfig);
|
||||
await GenInbounds(singboxConfig);
|
||||
await GenRouting(singboxConfig);
|
||||
await GenExperimental(singboxConfig);
|
||||
singboxConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//outbound
|
||||
proxyProfiles.Add(item);
|
||||
}
|
||||
if (proxyProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, singboxConfig);
|
||||
|
||||
await GenDns(singboxConfig);
|
||||
await ConvertGeo2Ruleset(singboxConfig);
|
||||
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = await ApplyFullConfigTemplate(singboxConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientCustomConfig(ProfileItem node, string? fileName)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
if (node == null || fileName is null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
try
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
var addressFileName = node.Address;
|
||||
if (addressFileName.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
ret.Msg = ResUI.FailedReadConfiguration + "1";
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (node.Address == Global.CoreMultipleLoadConfigFileName)
|
||||
{
|
||||
var txtFile = File.ReadAllText(addressFileName);
|
||||
var singboxConfig = JsonUtils.Deserialize<SingboxConfig>(txtFile);
|
||||
if (singboxConfig == null)
|
||||
{
|
||||
File.Copy(addressFileName, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenInbounds(singboxConfig);
|
||||
await GenExperimental(singboxConfig);
|
||||
|
||||
var content = JsonUtils.Serialize(singboxConfig, true);
|
||||
await File.WriteAllTextAsync(fileName, content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Copy(addressFileName, fileName);
|
||||
}
|
||||
|
||||
//check again
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
ret.Msg = ResUI.FailedReadConfiguration + "2";
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public gen function
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<string> ApplyFullConfigTemplate(SingboxConfig singboxConfig)
|
||||
{
|
||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box);
|
||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled)
|
||||
{
|
||||
return JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
|
||||
var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(singboxConfig);
|
||||
}
|
||||
|
||||
// Process outbounds
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||
foreach (var outbound in singboxConfig.outbounds)
|
||||
{
|
||||
if (outbound.type.ToLower() is "direct" or "block")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
|
||||
{
|
||||
outbound.detour = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
}
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
// Process endpoints
|
||||
if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0)
|
||||
{
|
||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray();
|
||||
foreach (var endpoint in singboxConfig.endpoints)
|
||||
{
|
||||
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
{
|
||||
endpoint.detour = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
|
||||
}
|
||||
fullConfigTemplateNode["endpoints"] = customEndpointsNode;
|
||||
}
|
||||
|
||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,482 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenDns(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (item != null && item.Enabled == true)
|
||||
{
|
||||
return await GenDnsCompatible(singboxConfig);
|
||||
}
|
||||
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
await GenDnsServers(singboxConfig, simpleDNSItem);
|
||||
await GenDnsRules(singboxConfig, simpleDNSItem);
|
||||
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.independent_cache = true;
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
var useDirectDns = false;
|
||||
if (routing != null)
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
|
||||
useDirectDns = rules?.LastOrDefault() is { } lastRule &&
|
||||
lastRule.OutboundTag == Global.DirectTag &&
|
||||
(lastRule.Port == "0-65535" ||
|
||||
lastRule.Network == "tcp,udp" ||
|
||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||
}
|
||||
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
||||
{
|
||||
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
|
||||
|
||||
var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS);
|
||||
directDns.tag = Global.SingboxDirectDNSTag;
|
||||
directDns.domain_resolver = Global.SingboxFinalResolverTag;
|
||||
|
||||
var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS);
|
||||
remoteDns.tag = Global.SingboxRemoteDNSTag;
|
||||
remoteDns.detour = Global.ProxyTag;
|
||||
remoteDns.domain_resolver = Global.SingboxFinalResolverTag;
|
||||
|
||||
var resolverDns = ParseDnsAddress(simpleDNSItem.SingboxOutboundsResolveDNS);
|
||||
resolverDns.tag = Global.SingboxOutboundResolverTag;
|
||||
resolverDns.domain_resolver = Global.SingboxFinalResolverTag;
|
||||
|
||||
var hostsDns = new Server4Sbox
|
||||
{
|
||||
tag = Global.SingboxHostsDNSTag,
|
||||
type = "hosts",
|
||||
predefined = new(),
|
||||
};
|
||||
if (simpleDNSItem.AddCommonHosts == true)
|
||||
{
|
||||
hostsDns.predefined = Global.PredefinedHosts;
|
||||
}
|
||||
var userHostsMap = simpleDNSItem.Hosts?
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||
.Where(line => line.Contains(' '))
|
||||
.ToDictionary(
|
||||
line =>
|
||||
{
|
||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts[0];
|
||||
},
|
||||
line =>
|
||||
{
|
||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var values = parts.Skip(1).ToList();
|
||||
return values;
|
||||
}
|
||||
);
|
||||
|
||||
if (userHostsMap != null)
|
||||
{
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
hostsDns.predefined[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (simpleDNSItem.UseSystemHosts == true)
|
||||
{
|
||||
var systemHosts = Utils.GetSystemHosts();
|
||||
if (systemHosts.Count > 0)
|
||||
{
|
||||
foreach (var host in systemHosts)
|
||||
{
|
||||
if (userHostsMap[host.Key] != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
userHostsMap[host.Key] = new List<string> { host.Value };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var host in hostsDns.predefined)
|
||||
{
|
||||
if (finalDns.server == host.Key)
|
||||
{
|
||||
finalDns.domain_resolver = Global.SingboxHostsDNSTag;
|
||||
}
|
||||
if (remoteDns.server == host.Key)
|
||||
{
|
||||
remoteDns.domain_resolver = Global.SingboxHostsDNSTag;
|
||||
}
|
||||
if (resolverDns.server == host.Key)
|
||||
{
|
||||
resolverDns.domain_resolver = Global.SingboxHostsDNSTag;
|
||||
}
|
||||
if (directDns.server == host.Key)
|
||||
{
|
||||
directDns.domain_resolver = Global.SingboxHostsDNSTag;
|
||||
}
|
||||
}
|
||||
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
||||
singboxConfig.dns.servers.Add(remoteDns);
|
||||
singboxConfig.dns.servers.Add(directDns);
|
||||
singboxConfig.dns.servers.Add(resolverDns);
|
||||
singboxConfig.dns.servers.Add(hostsDns);
|
||||
|
||||
// fake ip
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
{
|
||||
var fakeip = new Server4Sbox
|
||||
{
|
||||
tag = Global.SingboxFakeDNSTag,
|
||||
type = "fakeip",
|
||||
inet4_range = "198.18.0.0/15",
|
||||
inet6_range = "fc00::/18",
|
||||
};
|
||||
singboxConfig.dns.servers.Add(fakeip);
|
||||
}
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
|
||||
{
|
||||
var finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS);
|
||||
finalDns.tag = Global.SingboxFinalResolverTag;
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.servers ??= new List<Server4Sbox>();
|
||||
singboxConfig.dns.servers.Add(finalDns);
|
||||
return await Task.FromResult(finalDns);
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
||||
{
|
||||
singboxConfig.dns ??= new Dns4Sbox();
|
||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
||||
|
||||
singboxConfig.dns.rules.AddRange(new[]
|
||||
{
|
||||
new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag },
|
||||
new Rule4Sbox
|
||||
{
|
||||
server = Global.SingboxRemoteDNSTag,
|
||||
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
},
|
||||
new Rule4Sbox
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
}
|
||||
});
|
||||
|
||||
if (simpleDNSItem.BlockBindingQuery == true)
|
||||
{
|
||||
singboxConfig.dns.rules.Add(new()
|
||||
{
|
||||
query_type = new List<int> { 64, 65 },
|
||||
action = "predefined",
|
||||
rcode = "NOTIMP"
|
||||
});
|
||||
}
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing == null)
|
||||
return 0;
|
||||
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
var expectedIPCidr = new List<string>();
|
||||
var expectedIPsRegions = new List<string>();
|
||||
var regionNames = new HashSet<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
|
||||
{
|
||||
var ipItems = simpleDNSItem.DirectExpectedIPs
|
||||
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToList();
|
||||
|
||||
foreach (var ip in ipItems)
|
||||
{
|
||||
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var region = ip["geoip:".Length..];
|
||||
if (!string.IsNullOrEmpty(region))
|
||||
{
|
||||
expectedIPsRegions.Add(region);
|
||||
regionNames.Add(region);
|
||||
regionNames.Add($"geolocation-{region}");
|
||||
regionNames.Add($"tld-{region}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedIPCidr.Add(ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in rules)
|
||||
{
|
||||
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rule = new Rule4Sbox();
|
||||
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
|
||||
if (validDomains <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
rule.server = Global.SingboxDirectDNSTag;
|
||||
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Direct) ? null : simpleDNSItem.SingboxStrategy4Direct;
|
||||
|
||||
if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0)
|
||||
{
|
||||
var geositeSet = new HashSet<string>(rule.geosite);
|
||||
if (regionNames.Intersect(geositeSet).Any())
|
||||
{
|
||||
if (expectedIPsRegions.Count > 0)
|
||||
{
|
||||
rule.geoip = expectedIPsRegions;
|
||||
}
|
||||
if (expectedIPCidr.Count > 0)
|
||||
{
|
||||
rule.ip_cidr = expectedIPCidr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.OutboundTag == Global.BlockTag)
|
||||
{
|
||||
rule.action = "predefined";
|
||||
rule.rcode = "NXDOMAIN";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
{
|
||||
var rule4Fake = JsonUtils.DeepCopy(rule);
|
||||
rule4Fake.server = Global.SingboxFakeDNSTag;
|
||||
singboxConfig.dns.rules.Add(rule4Fake);
|
||||
}
|
||||
rule.server = Global.SingboxRemoteDNSTag;
|
||||
rule.strategy = string.IsNullOrEmpty(simpleDNSItem.SingboxStrategy4Proxy) ? null : simpleDNSItem.SingboxStrategy4Proxy;
|
||||
}
|
||||
|
||||
singboxConfig.dns.rules.Add(rule);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsCompatible(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
var strDNS = string.Empty;
|
||||
if (_config.TunModeItem.EnableTun)
|
||||
{
|
||||
strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS;
|
||||
}
|
||||
else
|
||||
{
|
||||
strDNS = string.IsNullOrEmpty(item?.NormalDNS) ? EmbedUtils.GetEmbedText(Global.DNSSingboxNormalFileName) : item?.NormalDNS;
|
||||
}
|
||||
|
||||
var dns4Sbox = JsonUtils.Deserialize<Dns4Sbox>(strDNS);
|
||||
if (dns4Sbox is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
singboxConfig.dns = dns4Sbox;
|
||||
|
||||
if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty())
|
||||
{
|
||||
await GenDnsDomainsCompatible(singboxConfig, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem)
|
||||
{
|
||||
var dns4Sbox = singboxConfig.dns ?? new();
|
||||
dns4Sbox.servers ??= [];
|
||||
dns4Sbox.rules ??= [];
|
||||
|
||||
var tag = Global.SingboxFinalResolverTag;
|
||||
var localDnsAddress = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress;
|
||||
|
||||
var localDnsServer = ParseDnsAddress(localDnsAddress);
|
||||
localDnsServer.tag = tag;
|
||||
|
||||
dns4Sbox.servers.Add(localDnsServer);
|
||||
|
||||
singboxConfig.dns = dns4Sbox;
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dNSItem)
|
||||
{
|
||||
var dns4Sbox = singboxConfig.dns ?? new();
|
||||
dns4Sbox.servers ??= [];
|
||||
dns4Sbox.rules ??= [];
|
||||
|
||||
var tag = Global.SingboxFinalResolverTag;
|
||||
dns4Sbox.servers.Add(new()
|
||||
{
|
||||
tag = tag,
|
||||
address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress,
|
||||
detour = Global.DirectTag,
|
||||
strategy = string.IsNullOrEmpty(dNSItem?.DomainStrategy4Freedom) ? null : dNSItem?.DomainStrategy4Freedom,
|
||||
});
|
||||
dns4Sbox.rules.Insert(0, new()
|
||||
{
|
||||
server = tag,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
});
|
||||
dns4Sbox.rules.Insert(0, new()
|
||||
{
|
||||
server = dns4Sbox.servers.Where(t => t.detour == Global.ProxyTag).Select(t => t.tag).FirstOrDefault() ?? "remote",
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
var lstDomain = singboxConfig.outbounds
|
||||
.Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server))
|
||||
.Select(t => t.server)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (lstDomain != null && lstDomain.Count > 0)
|
||||
{
|
||||
dns4Sbox.rules.Insert(0, new()
|
||||
{
|
||||
server = tag,
|
||||
domain = lstDomain
|
||||
});
|
||||
}
|
||||
|
||||
singboxConfig.dns = dns4Sbox;
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private static Server4Sbox? ParseDnsAddress(string address)
|
||||
{
|
||||
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
|
||||
if (string.IsNullOrEmpty(addressFirst))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var server = new Server4Sbox();
|
||||
|
||||
if (addressFirst is "local" or "localhost")
|
||||
{
|
||||
server.type = "local";
|
||||
return server;
|
||||
}
|
||||
|
||||
if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var interface_name = addressFirst.Substring(7);
|
||||
server.type = "dhcp";
|
||||
server.Interface = interface_name == "auto" ? null : interface_name;
|
||||
return server;
|
||||
}
|
||||
|
||||
if (!addressFirst.Contains("://"))
|
||||
{
|
||||
// udp dns
|
||||
server.type = "udp";
|
||||
server.server = addressFirst;
|
||||
return server;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
|
||||
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
|
||||
|
||||
var uri = new Uri(addressFirst);
|
||||
server.server = uri.Host;
|
||||
|
||||
if (!uri.IsDefaultPort)
|
||||
{
|
||||
server.server_port = uri.Port;
|
||||
}
|
||||
|
||||
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
|
||||
{
|
||||
server.path = uri.AbsolutePath;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal);
|
||||
if (protocolEndIndex > 0)
|
||||
{
|
||||
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
|
||||
var remaining = addressFirst.Substring(protocolEndIndex + 3);
|
||||
|
||||
var portIndex = remaining.IndexOf(':');
|
||||
var pathIndex = remaining.IndexOf('/');
|
||||
|
||||
if (portIndex > 0)
|
||||
{
|
||||
server.server = remaining.Substring(0, portIndex);
|
||||
var portPart = pathIndex > portIndex
|
||||
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
|
||||
: remaining.Substring(portIndex + 1);
|
||||
|
||||
if (int.TryParse(portPart, out var parsedPort))
|
||||
{
|
||||
server.server_port = parsedPort;
|
||||
}
|
||||
}
|
||||
else if (pathIndex > 0)
|
||||
{
|
||||
server.server = remaining.Substring(0, pathIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
server.server = remaining;
|
||||
}
|
||||
|
||||
if (pathIndex > 0 && (server.type == "https" || server.type == "h3"))
|
||||
{
|
||||
server.path = remaining.Substring(pathIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenInbounds(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listen = "0.0.0.0";
|
||||
singboxConfig.inbounds = [];
|
||||
|
||||
if (!_config.TunModeItem.EnableTun
|
||||
|| _config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box)
|
||||
{
|
||||
var inbound = new Inbound4Sbox()
|
||||
{
|
||||
type = EInboundProtocol.mixed.ToString(),
|
||||
tag = EInboundProtocol.socks.ToString(),
|
||||
listen = Global.Loopback,
|
||||
};
|
||||
singboxConfig.inbounds.Add(inbound);
|
||||
|
||||
inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
|
||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||
{
|
||||
var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true);
|
||||
singboxConfig.inbounds.Add(inbound2);
|
||||
}
|
||||
|
||||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
if (_config.Inbound.First().NewPort4LAN)
|
||||
{
|
||||
var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true);
|
||||
inbound3.listen = listen;
|
||||
singboxConfig.inbounds.Add(inbound3);
|
||||
|
||||
//auth
|
||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||
{
|
||||
inbound3.users = new() { new() { username = _config.Inbound.First().User, password = _config.Inbound.First().Pass } };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inbound.listen = listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_config.TunModeItem.EnableTun)
|
||||
{
|
||||
if (_config.TunModeItem.Mtu <= 0)
|
||||
{
|
||||
_config.TunModeItem.Mtu = Global.TunMtus.First();
|
||||
}
|
||||
if (_config.TunModeItem.Stack.IsNullOrEmpty())
|
||||
{
|
||||
_config.TunModeItem.Stack = Global.TunStacks.First();
|
||||
}
|
||||
|
||||
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
|
||||
tunInbound.interface_name = Utils.IsOSX() ? $"utun{new Random().Next(99)}" : "singbox_tun";
|
||||
tunInbound.mtu = _config.TunModeItem.Mtu;
|
||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||
tunInbound.stack = _config.TunModeItem.Stack;
|
||||
if (_config.TunModeItem.EnableIPv6Address == false)
|
||||
{
|
||||
tunInbound.address = ["172.18.0.1/30"];
|
||||
}
|
||||
|
||||
singboxConfig.inbounds.Add(tunInbound);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks)
|
||||
{
|
||||
var inbound = JsonUtils.DeepCopy(inItem);
|
||||
inbound.tag = protocol.ToString();
|
||||
inbound.listen_port = inItem.listen_port + (int)protocol;
|
||||
inbound.type = EInboundProtocol.mixed.ToString();
|
||||
return inbound;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenLog(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (_config.CoreBasicItem.Loglevel)
|
||||
{
|
||||
case "debug":
|
||||
case "info":
|
||||
case "error":
|
||||
singboxConfig.log.level = _config.CoreBasicItem.Loglevel;
|
||||
break;
|
||||
|
||||
case "warning":
|
||||
singboxConfig.log.level = "warn";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (_config.CoreBasicItem.Loglevel == Global.None)
|
||||
{
|
||||
singboxConfig.log.disabled = true;
|
||||
}
|
||||
if (_config.CoreBasicItem.LogEnabled)
|
||||
{
|
||||
var dtNow = DateTime.Now;
|
||||
singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,577 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenOutbound(ProfileItem node, Outbound4Sbox outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
outbound.server = node.Address;
|
||||
outbound.server_port = node.Port;
|
||||
outbound.type = Global.ProtocolTypes[node.ConfigType];
|
||||
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
{
|
||||
outbound.uuid = node.Id;
|
||||
outbound.alter_id = node.AlterId;
|
||||
if (Global.VmessSecurities.Contains(node.Security))
|
||||
{
|
||||
outbound.security = node.Security;
|
||||
}
|
||||
else
|
||||
{
|
||||
outbound.security = Global.DefaultSecurity;
|
||||
}
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
break;
|
||||
}
|
||||
case EConfigType.Shadowsocks:
|
||||
{
|
||||
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : Global.None;
|
||||
outbound.password = node.Id;
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
break;
|
||||
}
|
||||
case EConfigType.SOCKS:
|
||||
{
|
||||
outbound.version = "5";
|
||||
if (node.Security.IsNotEmpty()
|
||||
&& node.Id.IsNotEmpty())
|
||||
{
|
||||
outbound.username = node.Security;
|
||||
outbound.password = node.Id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
if (node.Security.IsNotEmpty()
|
||||
&& node.Id.IsNotEmpty())
|
||||
{
|
||||
outbound.username = node.Security;
|
||||
outbound.password = node.Id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EConfigType.VLESS:
|
||||
{
|
||||
outbound.uuid = node.Id;
|
||||
|
||||
outbound.packet_encoding = "xudp";
|
||||
|
||||
if (node.Flow.IsNullOrEmpty())
|
||||
{
|
||||
await GenOutboundMux(node, outbound);
|
||||
}
|
||||
else
|
||||
{
|
||||
outbound.flow = node.Flow;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EConfigType.Trojan:
|
||||
{
|
||||
outbound.password = node.Id;
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
break;
|
||||
}
|
||||
case EConfigType.Hysteria2:
|
||||
{
|
||||
outbound.password = node.Id;
|
||||
|
||||
if (node.Path.IsNotEmpty())
|
||||
{
|
||||
outbound.obfs = new()
|
||||
{
|
||||
type = "salamander",
|
||||
password = node.Path.TrimEx(),
|
||||
};
|
||||
}
|
||||
|
||||
outbound.up_mbps = _config.HysteriaItem.UpMbps > 0 ? _config.HysteriaItem.UpMbps : null;
|
||||
outbound.down_mbps = _config.HysteriaItem.DownMbps > 0 ? _config.HysteriaItem.DownMbps : null;
|
||||
if (node.Ports.IsNotEmpty() && (node.Ports.Contains(':') || node.Ports.Contains('-') || node.Ports.Contains(',')))
|
||||
{
|
||||
outbound.server_port = null;
|
||||
outbound.server_ports = node.Ports.Split(',')
|
||||
.Select(p => p.Trim())
|
||||
.Where(p => p.IsNotEmpty())
|
||||
.Select(p =>
|
||||
{
|
||||
var port = p.Replace('-', ':');
|
||||
return port.Contains(':') ? port : $"{port}:{port}";
|
||||
})
|
||||
.ToList();
|
||||
outbound.hop_interval = _config.HysteriaItem.HopInterval > 0 ? $"{_config.HysteriaItem.HopInterval}s" : null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EConfigType.TUIC:
|
||||
{
|
||||
outbound.uuid = node.Id;
|
||||
outbound.password = node.Security;
|
||||
outbound.congestion_control = node.HeaderType;
|
||||
break;
|
||||
}
|
||||
case EConfigType.Anytls:
|
||||
{
|
||||
outbound.password = node.Id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await GenOutboundTls(node, outbound);
|
||||
|
||||
await GenOutboundTransport(node, outbound);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
endpoint.address = Utils.String2List(node.RequestHost);
|
||||
endpoint.type = Global.ProtocolTypes[node.ConfigType];
|
||||
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.WireGuard:
|
||||
{
|
||||
var peer = new Peer4Sbox
|
||||
{
|
||||
public_key = node.PublicKey,
|
||||
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
|
||||
address = node.Address,
|
||||
port = node.Port,
|
||||
// TODO default ["0.0.0.0/0", "::/0"]
|
||||
allowed_ips = new() { "0.0.0.0/0", "::/0" },
|
||||
};
|
||||
endpoint.private_key = node.Id;
|
||||
endpoint.mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt();
|
||||
endpoint.peers = new() { peer };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<BaseServer4Sbox?> GenServer(ProfileItem node)
|
||||
{
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
await GenEndpoint(node, endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
await GenOutbound(node, outbound);
|
||||
return outbound;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult<BaseServer4Sbox?>(null);
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||
if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty())
|
||||
{
|
||||
var mux = new Multiplex4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
protocol = _config.Mux4SboxItem.Protocol,
|
||||
max_connections = _config.Mux4SboxItem.MaxConnections,
|
||||
padding = _config.Mux4SboxItem.Padding,
|
||||
};
|
||||
outbound.multiplex = mux;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundTls(ProfileItem node, Outbound4Sbox outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node.StreamSecurity == Global.StreamSecurityReality || node.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
var server_name = string.Empty;
|
||||
if (node.Sni.IsNotEmpty())
|
||||
{
|
||||
server_name = node.Sni;
|
||||
}
|
||||
else if (node.RequestHost.IsNotEmpty())
|
||||
{
|
||||
server_name = Utils.String2List(node.RequestHost)?.First();
|
||||
}
|
||||
var tls = new Tls4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
record_fragment = _config.CoreBasicItem.EnableFragment,
|
||||
server_name = server_name,
|
||||
insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||
alpn = node.GetAlpn(),
|
||||
};
|
||||
if (node.Fingerprint.IsNotEmpty())
|
||||
{
|
||||
tls.utls = new Utls4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||
};
|
||||
}
|
||||
if (node.StreamSecurity == Global.StreamSecurityReality)
|
||||
{
|
||||
tls.reality = new Reality4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
public_key = node.PublicKey,
|
||||
short_id = node.ShortId
|
||||
};
|
||||
tls.insecure = false;
|
||||
}
|
||||
outbound.tls = tls;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transport = new Transport4Sbox();
|
||||
|
||||
switch (node.GetNetwork())
|
||||
{
|
||||
case nameof(ETransport.h2):
|
||||
transport.type = nameof(ETransport.http);
|
||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||
break;
|
||||
|
||||
case nameof(ETransport.tcp): //http
|
||||
if (node.HeaderType == Global.TcpHeaderHttp)
|
||||
{
|
||||
if (node.ConfigType == EConfigType.Shadowsocks)
|
||||
{
|
||||
outbound.plugin = "obfs-local";
|
||||
outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};";
|
||||
}
|
||||
else
|
||||
{
|
||||
transport.type = nameof(ETransport.http);
|
||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.ws):
|
||||
transport.type = nameof(ETransport.ws);
|
||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||
if (node.RequestHost.IsNotEmpty())
|
||||
{
|
||||
transport.headers = new()
|
||||
{
|
||||
Host = node.RequestHost
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.httpupgrade):
|
||||
transport.type = nameof(ETransport.httpupgrade);
|
||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
|
||||
|
||||
break;
|
||||
|
||||
case nameof(ETransport.quic):
|
||||
transport.type = nameof(ETransport.quic);
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
transport.type = nameof(ETransport.grpc);
|
||||
transport.service_name = node.Path;
|
||||
transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s");
|
||||
transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s");
|
||||
transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (transport.type != null)
|
||||
{
|
||||
outbound.transport = transport;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig)
|
||||
{
|
||||
if (node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
if (subItem is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//current proxy
|
||||
BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag, null);
|
||||
outbound ??= singboxConfig.outbounds.First();
|
||||
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
|
||||
//Previous proxy
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
string? prevOutboundTag = null;
|
||||
if (prevNode is not null
|
||||
&& prevNode.ConfigType != EConfigType.Custom)
|
||||
{
|
||||
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||
var prevServer = await GenServer(prevNode);
|
||||
prevServer.tag = prevOutboundTag;
|
||||
if (prevServer is Endpoints4Sbox endpoint)
|
||||
{
|
||||
singboxConfig.endpoints ??= new();
|
||||
singboxConfig.endpoints.Add(endpoint);
|
||||
}
|
||||
else if (prevServer is Outbound4Sbox outboundPrev)
|
||||
{
|
||||
singboxConfig.outbounds.Add(outboundPrev);
|
||||
}
|
||||
}
|
||||
var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||
|
||||
if (nextServer is not null)
|
||||
{
|
||||
if (nextServer is Endpoints4Sbox endpoint)
|
||||
{
|
||||
singboxConfig.endpoints ??= new();
|
||||
singboxConfig.endpoints.Insert(0, endpoint);
|
||||
}
|
||||
else if (nextServer is Outbound4Sbox outboundNext)
|
||||
{
|
||||
singboxConfig.outbounds.Insert(0, outboundNext);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get outbound template and initialize lists
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var resultOutbounds = new List<Outbound4Sbox>();
|
||||
var resultEndpoints = new List<Endpoints4Sbox>(); // For endpoints
|
||||
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
|
||||
var prevEndpoints = new List<Endpoints4Sbox>(); // Separate list for prev endpoints
|
||||
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||
|
||||
// Cache for chain proxies to avoid duplicate generation
|
||||
var nextProxyCache = new Dictionary<string, BaseServer4Sbox?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||
var prevIndex = 0; // Index for prev outbounds
|
||||
|
||||
// Process each node
|
||||
var index = 0;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
index++;
|
||||
|
||||
// Handle proxy chain
|
||||
string? prevTag = null;
|
||||
var currentServer = await GenServer(node);
|
||||
var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||
if (nextServer != null)
|
||||
{
|
||||
nextServer = JsonUtils.DeepCopy(nextServer);
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
|
||||
// current proxy
|
||||
currentServer.tag = $"{Global.ProxyTag}-{index}";
|
||||
proxyTags.Add(currentServer.tag);
|
||||
|
||||
if (!node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||
{
|
||||
prevTag = value; // maybe null
|
||||
}
|
||||
else
|
||||
{
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
if (prevNode is not null
|
||||
&& prevNode.ConfigType != EConfigType.Custom)
|
||||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||
prevOutbound.tag = prevTag;
|
||||
prevOutbounds.Add(prevOutbound);
|
||||
}
|
||||
prevProxyTags[node.Subid] = prevTag;
|
||||
}
|
||||
|
||||
nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer);
|
||||
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||
{
|
||||
nextProxyCache[node.Subid] = nextServer;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextServer is not null)
|
||||
{
|
||||
if (nextServer is Endpoints4Sbox nextEndpoint)
|
||||
{
|
||||
resultEndpoints.Add(nextEndpoint);
|
||||
}
|
||||
else if (nextServer is Outbound4Sbox nextOutbound)
|
||||
{
|
||||
resultOutbounds.Add(nextOutbound);
|
||||
}
|
||||
}
|
||||
if (currentServer is Endpoints4Sbox currentEndpoint)
|
||||
{
|
||||
resultEndpoints.Add(currentEndpoint);
|
||||
}
|
||||
else if (currentServer is Outbound4Sbox currentOutbound)
|
||||
{
|
||||
resultOutbounds.Add(currentOutbound);
|
||||
}
|
||||
}
|
||||
|
||||
// Add urltest outbound (auto selection based on latency)
|
||||
if (proxyTags.Count > 0)
|
||||
{
|
||||
var outUrltest = new Outbound4Sbox
|
||||
{
|
||||
type = "urltest",
|
||||
tag = $"{Global.ProxyTag}-auto",
|
||||
outbounds = proxyTags,
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
|
||||
// Add selector outbound (manual selection)
|
||||
var outSelector = new Outbound4Sbox
|
||||
{
|
||||
type = "selector",
|
||||
tag = Global.ProxyTag,
|
||||
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||
|
||||
// Insert these at the beginning
|
||||
resultOutbounds.Insert(0, outUrltest);
|
||||
resultOutbounds.Insert(0, outSelector);
|
||||
}
|
||||
|
||||
// Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds
|
||||
resultOutbounds.AddRange(prevOutbounds);
|
||||
resultOutbounds.AddRange(singboxConfig.outbounds);
|
||||
singboxConfig.outbounds = resultOutbounds;
|
||||
singboxConfig.endpoints ??= new List<Endpoints4Sbox>();
|
||||
resultEndpoints.AddRange(singboxConfig.endpoints);
|
||||
singboxConfig.endpoints = resultEndpoints;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<BaseServer4Sbox?> GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
|
||||
if (!prevOutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
outbound.detour = prevOutboundTag;
|
||||
}
|
||||
|
||||
// Next proxy
|
||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode is not null
|
||||
&& nextNode.ConfigType != EConfigType.Custom)
|
||||
{
|
||||
nextOutbound ??= await GenServer(nextNode);
|
||||
nextOutbound.tag = outbound.tag;
|
||||
|
||||
outbound.tag = $"mid-{outbound.tag}";
|
||||
nextOutbound.detour = outbound.tag;
|
||||
}
|
||||
return nextOutbound;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
singboxConfig.route.final = Global.ProxyTag;
|
||||
var item = _config.SimpleDNSItem;
|
||||
|
||||
var defaultDomainResolverTag = Global.SingboxOutboundResolverTag;
|
||||
var directDNSStrategy = item.SingboxStrategy4Direct.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : item.SingboxStrategy4Direct;
|
||||
|
||||
var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||
if (rawDNSItem != null && rawDNSItem.Enabled == true)
|
||||
{
|
||||
defaultDomainResolverTag = Global.SingboxFinalResolverTag;
|
||||
directDNSStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? Global.SingboxDomainStrategy4Out.FirstOrDefault() : rawDNSItem.DomainStrategy4Freedom;
|
||||
}
|
||||
singboxConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = directDNSStrategy
|
||||
};
|
||||
|
||||
if (_config.TunModeItem.EnableTun)
|
||||
{
|
||||
singboxConfig.route.auto_detect_interface = true;
|
||||
|
||||
var tunRules = JsonUtils.Deserialize<List<Rule4Sbox>>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName));
|
||||
if (tunRules != null)
|
||||
{
|
||||
singboxConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe);
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
port = new() { 53 },
|
||||
action = "hijack-dns",
|
||||
process_name = lstDnsExe
|
||||
});
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_name = lstDirectExe
|
||||
});
|
||||
}
|
||||
|
||||
if (_config.Inbound.First().SniffingEnabled)
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
action = "sniff"
|
||||
});
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
protocol = new() { "dns" },
|
||||
action = "hijack-dns"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
port = new() { 53 },
|
||||
network = new() { "udp" },
|
||||
action = "hijack-dns"
|
||||
});
|
||||
}
|
||||
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
clash_mode = ERuleMode.Direct.ToString()
|
||||
});
|
||||
singboxConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.ProxyTag,
|
||||
clash_mode = ERuleMode.Global.ToString()
|
||||
});
|
||||
|
||||
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
|
||||
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
||||
{
|
||||
domainStrategy = defaultRouting.DomainStrategy4Singbox;
|
||||
}
|
||||
var resolveRule = new Rule4Sbox
|
||||
{
|
||||
action = "resolve",
|
||||
strategy = domainStrategy
|
||||
};
|
||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand)
|
||||
{
|
||||
singboxConfig.route.rules.Add(resolveRule);
|
||||
}
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
var ipRules = new List<RulesItem>();
|
||||
if (routing != null)
|
||||
{
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var item1 in rules ?? [])
|
||||
{
|
||||
if (item1.Enabled)
|
||||
{
|
||||
await GenRoutingUserRule(item1, singboxConfig);
|
||||
if (item1.Ip != null && item1.Ip.Count > 0)
|
||||
{
|
||||
ipRules.Add(item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
singboxConfig.route.rules.Add(resolveRule);
|
||||
foreach (var item2 in ipRules)
|
||||
{
|
||||
await GenRoutingUserRule(item2, singboxConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void GenRoutingDirectExe(out List<string> lstDnsExe, out List<string> lstDirectExe)
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
{
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
lstDnsExe = new List<string>(dnsExeSet);
|
||||
lstDirectExe = new List<string>(directExeSet);
|
||||
}
|
||||
|
||||
private async Task<int> GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig);
|
||||
var rules = singboxConfig.route.rules;
|
||||
|
||||
var rule = new Rule4Sbox();
|
||||
if (item.OutboundTag == "block")
|
||||
{
|
||||
rule.action = "reject";
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.outbound = item.OutboundTag;
|
||||
}
|
||||
|
||||
if (item.Port.IsNotEmpty())
|
||||
{
|
||||
var portRanges = item.Port.Split(',').Where(it => it.Contains('-')).Select(it => it.Replace("-", ":")).ToList();
|
||||
var ports = item.Port.Split(',').Where(it => !it.Contains('-')).Select(it => it.ToInt()).ToList();
|
||||
|
||||
rule.port_range = portRanges.Count > 0 ? portRanges : null;
|
||||
rule.port = ports.Count > 0 ? ports : null;
|
||||
}
|
||||
if (item.Network.IsNotEmpty())
|
||||
{
|
||||
rule.network = Utils.String2List(item.Network);
|
||||
}
|
||||
if (item.Protocol?.Count > 0)
|
||||
{
|
||||
rule.protocol = item.Protocol;
|
||||
}
|
||||
if (item.InboundTag?.Count >= 0)
|
||||
{
|
||||
rule.inbound = item.InboundTag;
|
||||
}
|
||||
var rule1 = JsonUtils.DeepCopy(rule);
|
||||
var rule2 = JsonUtils.DeepCopy(rule);
|
||||
var rule3 = JsonUtils.DeepCopy(rule);
|
||||
|
||||
var hasDomainIp = false;
|
||||
if (item.Domain?.Count > 0)
|
||||
{
|
||||
var countDomain = 0;
|
||||
foreach (var it in item.Domain)
|
||||
{
|
||||
if (ParseV2Domain(it, rule1))
|
||||
countDomain++;
|
||||
}
|
||||
if (countDomain > 0)
|
||||
{
|
||||
rules.Add(rule1);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Ip?.Count > 0)
|
||||
{
|
||||
var countIp = 0;
|
||||
foreach (var it in item.Ip)
|
||||
{
|
||||
if (ParseV2Address(it, rule2))
|
||||
countIp++;
|
||||
}
|
||||
if (countIp > 0)
|
||||
{
|
||||
rules.Add(rule2);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_config.TunModeItem.EnableTun && item.Process?.Count > 0)
|
||||
{
|
||||
rule3.process_name = item.Process;
|
||||
rules.Add(rule3);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
|
||||
if (!hasDomainIp
|
||||
&& (rule.port != null || rule.port_range != null || rule.protocol != null || rule.inbound != null || rule.network != null))
|
||||
{
|
||||
rules.Add(rule);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private bool ParseV2Domain(string domain, Rule4Sbox rule)
|
||||
{
|
||||
if (domain.StartsWith("#") || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (domain.StartsWith("geosite:"))
|
||||
{
|
||||
rule.geosite ??= [];
|
||||
rule.geosite?.Add(domain.Substring(8));
|
||||
}
|
||||
else if (domain.StartsWith("regexp:"))
|
||||
{
|
||||
rule.domain_regex ??= [];
|
||||
rule.domain_regex?.Add(domain.Replace(Global.RoutingRuleComma, ",").Substring(7));
|
||||
}
|
||||
else if (domain.StartsWith("domain:"))
|
||||
{
|
||||
rule.domain ??= [];
|
||||
rule.domain_suffix ??= [];
|
||||
rule.domain?.Add(domain.Substring(7));
|
||||
rule.domain_suffix?.Add("." + domain.Substring(7));
|
||||
}
|
||||
else if (domain.StartsWith("full:"))
|
||||
{
|
||||
rule.domain ??= [];
|
||||
rule.domain?.Add(domain.Substring(5));
|
||||
}
|
||||
else if (domain.StartsWith("keyword:"))
|
||||
{
|
||||
rule.domain_keyword ??= [];
|
||||
rule.domain_keyword?.Add(domain.Substring(8));
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.domain_keyword ??= [];
|
||||
rule.domain_keyword?.Add(domain);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ParseV2Address(string address, Rule4Sbox rule)
|
||||
{
|
||||
if (address.StartsWith("ext:") || address.StartsWith("ext-ip:"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (address.Equals("geoip:private"))
|
||||
{
|
||||
rule.ip_is_private = true;
|
||||
}
|
||||
else if (address.StartsWith("geoip:"))
|
||||
{
|
||||
rule.geoip ??= new();
|
||||
rule.geoip?.Add(address.Substring(6));
|
||||
}
|
||||
else if (address.Equals("geoip:!private"))
|
||||
{
|
||||
rule.ip_is_private = false;
|
||||
}
|
||||
else if (address.StartsWith("geoip:!"))
|
||||
{
|
||||
rule.geoip ??= new();
|
||||
rule.geoip?.Add(address.Substring(6));
|
||||
rule.invert = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rule.ip_cidr ??= new();
|
||||
rule.ip_cidr?.Add(address);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig)
|
||||
{
|
||||
if (Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
return outboundTag;
|
||||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
if (node == null
|
||||
|| node.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var server = await GenServer(node);
|
||||
if (server is null)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
server.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
if (server is Endpoints4Sbox endpoint)
|
||||
{
|
||||
singboxConfig.endpoints ??= new();
|
||||
singboxConfig.endpoints.Add(endpoint);
|
||||
}
|
||||
else if (server is Outbound4Sbox outbound)
|
||||
{
|
||||
singboxConfig.outbounds.Add(outbound);
|
||||
}
|
||||
|
||||
return server.tag;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> ConvertGeo2Ruleset(SingboxConfig singboxConfig)
|
||||
{
|
||||
static void AddRuleSets(List<string> ruleSets, List<string>? rule_set)
|
||||
{
|
||||
if (rule_set != null)
|
||||
ruleSets.AddRange(rule_set);
|
||||
}
|
||||
var geosite = "geosite";
|
||||
var geoip = "geoip";
|
||||
var ruleSets = new List<string>();
|
||||
|
||||
//convert route geosite & geoip to ruleset
|
||||
foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||
{
|
||||
rule.rule_set ??= new List<string>();
|
||||
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||
rule.geosite = null;
|
||||
AddRuleSets(ruleSets, rule.rule_set);
|
||||
}
|
||||
foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||
{
|
||||
rule.rule_set ??= new List<string>();
|
||||
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||
rule.geoip = null;
|
||||
AddRuleSets(ruleSets, rule.rule_set);
|
||||
}
|
||||
|
||||
//convert dns geosite & geoip to ruleset
|
||||
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? [])
|
||||
{
|
||||
rule.rule_set ??= new List<string>();
|
||||
rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList());
|
||||
rule.geosite = null;
|
||||
}
|
||||
foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? [])
|
||||
{
|
||||
rule.rule_set ??= new List<string>();
|
||||
rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList());
|
||||
rule.geoip = null;
|
||||
}
|
||||
foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? [])
|
||||
{
|
||||
AddRuleSets(ruleSets, dnsRule.rule_set);
|
||||
}
|
||||
//rules in rules
|
||||
foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? [])
|
||||
{
|
||||
foreach (var item2 in item ?? [])
|
||||
{
|
||||
AddRuleSets(ruleSets, item2.rule_set);
|
||||
}
|
||||
}
|
||||
|
||||
//load custom ruleset file
|
||||
List<Ruleset4Sbox> customRulesets = [];
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing.CustomRulesetPath4Singbox.IsNotEmpty())
|
||||
{
|
||||
var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox);
|
||||
if (result.IsNotEmpty())
|
||||
{
|
||||
customRulesets = (JsonUtils.Deserialize<List<Ruleset4Sbox>>(result) ?? [])
|
||||
.Where(t => t.tag != null)
|
||||
.Where(t => t.type != null)
|
||||
.Where(t => t.format != null)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
//Local srs files address
|
||||
var localSrss = Utils.GetBinPath("srss");
|
||||
|
||||
//Add ruleset srs
|
||||
singboxConfig.route.rule_set = [];
|
||||
foreach (var item in new HashSet<string>(ruleSets))
|
||||
{
|
||||
if (item.IsNullOrEmpty())
|
||||
{ continue; }
|
||||
var customRuleset = customRulesets.FirstOrDefault(t => t.tag != null && t.tag.Equals(item));
|
||||
if (customRuleset is null)
|
||||
{
|
||||
var pathSrs = Path.Combine(localSrss, $"{item}.srs");
|
||||
if (File.Exists(pathSrs))
|
||||
{
|
||||
customRuleset = new()
|
||||
{
|
||||
type = "local",
|
||||
format = "binary",
|
||||
tag = item,
|
||||
path = pathSrs
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var srsUrl = string.IsNullOrEmpty(_config.ConstItem.SrsSourceUrl)
|
||||
? Global.SingboxRulesetUrl
|
||||
: _config.ConstItem.SrsSourceUrl;
|
||||
|
||||
customRuleset = new()
|
||||
{
|
||||
type = "remote",
|
||||
format = "binary",
|
||||
tag = item,
|
||||
url = string.Format(srsUrl, item.StartsWith(geosite) ? geosite : geoip, item),
|
||||
download_detour = Global.ProxyTag
|
||||
};
|
||||
}
|
||||
}
|
||||
singboxConfig.route.rule_set.Add(customRuleset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private async Task<int> GenExperimental(SingboxConfig singboxConfig)
|
||||
{
|
||||
//if (_config.guiItem.enableStatistics)
|
||||
{
|
||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||
singboxConfig.experimental.clash_api = new Clash_Api4Sbox()
|
||||
{
|
||||
external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}",
|
||||
};
|
||||
}
|
||||
|
||||
if (_config.CoreBasicItem.EnableCacheFile4Sbox)
|
||||
{
|
||||
singboxConfig.experimental ??= new Experimental4Sbox();
|
||||
singboxConfig.experimental.cache_file = new CacheFile4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
path = Utils.GetBinPath("cache.db"),
|
||||
store_fakeip = _config.SimpleDNSItem.FakeIP == true
|
||||
};
|
||||
}
|
||||
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,415 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService(Config config)
|
||||
{
|
||||
private readonly Config _config = config;
|
||||
private static readonly string _tag = "CoreConfigV2rayService";
|
||||
|
||||
#region public gen function
|
||||
|
||||
public async Task<RetResult> GenerateClientConfigContent(ProfileItem node)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node == null
|
||||
|| node.Port <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (node.GetNetwork() is nameof(ETransport.quic))
|
||||
{
|
||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
|
||||
await GenInbounds(v2rayConfig);
|
||||
|
||||
await GenOutbound(node, v2rayConfig.outbounds.First());
|
||||
|
||||
await GenMoreOutbounds(node, v2rayConfig);
|
||||
|
||||
await GenRouting(v2rayConfig);
|
||||
|
||||
await GenDns(node, v2rayConfig);
|
||||
|
||||
await GenStatistic(v2rayConfig);
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
string txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
await GenInbounds(v2rayConfig);
|
||||
await GenRouting(v2rayConfig);
|
||||
await GenDns(null, v2rayConfig);
|
||||
await GenStatistic(v2rayConfig);
|
||||
v2rayConfig.outbounds.RemoveAt(0);
|
||||
|
||||
var proxyProfiles = new List<ProfileItem>();
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//outbound
|
||||
proxyProfiles.Add(item);
|
||||
}
|
||||
if (proxyProfiles.Count <= 0)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
await GenOutboundsList(proxyProfiles, v2rayConfig);
|
||||
|
||||
//add balancers
|
||||
await GenBalancer(v2rayConfig, multipleLoad);
|
||||
|
||||
var balancer = v2rayConfig.routing.balancers.First();
|
||||
|
||||
//add rule
|
||||
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList();
|
||||
if (rules?.Count > 0)
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
rule.outboundTag = null;
|
||||
rule.balancerTag = balancer.tag;
|
||||
}
|
||||
}
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
ip = ["0.0.0.0/0", "::/0"],
|
||||
balancerTag = balancer.tag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.routing.rules.Add(new()
|
||||
{
|
||||
network = "tcp,udp",
|
||||
balancerTag = balancer.tag,
|
||||
type = "field"
|
||||
});
|
||||
}
|
||||
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientSpeedtestConfig(List<ServerTestItem> selecteds)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.Msg = ResUI.InitialConfiguration;
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
List<IPEndPoint> lstIpEndPoints = new();
|
||||
List<TcpConnectionInformation> lstTcpConns = new();
|
||||
try
|
||||
{
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
|
||||
lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
|
||||
lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
v2rayConfig.inbounds.Clear();
|
||||
v2rayConfig.outbounds.Clear();
|
||||
v2rayConfig.routing.rules.Clear();
|
||||
|
||||
var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest);
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.Port <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
|
||||
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
|
||||
{
|
||||
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//find unused port
|
||||
var port = initPort;
|
||||
for (var k = initPort; k < Global.MaxPort; k++)
|
||||
{
|
||||
if (lstIpEndPoints?.FindIndex(_it => _it.Port == k) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (lstTcpConns?.FindIndex(_it => _it.LocalEndPoint.Port == k) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//found
|
||||
port = k;
|
||||
initPort = port + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
//Port In Used
|
||||
if (lstIpEndPoints?.FindIndex(_it => _it.Port == port) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
it.Port = port;
|
||||
it.AllowTest = true;
|
||||
|
||||
//outbound
|
||||
if (item is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.Shadowsocks
|
||||
&& !Global.SsSecuritiesInXray.Contains(item.Security))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.ConfigType == EConfigType.VLESS
|
||||
&& !Global.Flows.Contains(item.Flow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
||||
&& item.PublicKey.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//inbound
|
||||
Inbounds4Ray inbound = new()
|
||||
{
|
||||
listen = Global.Loopback,
|
||||
port = port,
|
||||
protocol = EInboundProtocol.mixed.ToString(),
|
||||
};
|
||||
inbound.tag = inbound.protocol + inbound.port.ToString();
|
||||
v2rayConfig.inbounds.Add(inbound);
|
||||
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(item, outbound);
|
||||
outbound.tag = Global.ProxyTag + inbound.port.ToString();
|
||||
v2rayConfig.outbounds.Add(outbound);
|
||||
|
||||
//rule
|
||||
RulesItem4Ray rule = new()
|
||||
{
|
||||
inboundTag = new List<string> { inbound.tag },
|
||||
outboundTag = outbound.tag,
|
||||
type = "field"
|
||||
};
|
||||
v2rayConfig.routing.rules.Add(rule);
|
||||
}
|
||||
|
||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RetResult> GenerateClientSpeedtestConfig(ProfileItem node, int port)
|
||||
{
|
||||
var ret = new RetResult();
|
||||
try
|
||||
{
|
||||
if (node is not { Port: > 0 })
|
||||
{
|
||||
ret.Msg = ResUI.CheckServerSettings;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (node.GetNetwork() is nameof(ETransport.quic))
|
||||
{
|
||||
ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}";
|
||||
return ret;
|
||||
}
|
||||
|
||||
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
ret.Msg = ResUI.FailedGetDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
var v2rayConfig = JsonUtils.Deserialize<V2rayConfig>(result);
|
||||
if (v2rayConfig == null)
|
||||
{
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
|
||||
await GenLog(v2rayConfig);
|
||||
await GenOutbound(node, v2rayConfig.outbounds.First());
|
||||
await GenMoreOutbounds(node, v2rayConfig);
|
||||
|
||||
v2rayConfig.routing.rules.Clear();
|
||||
v2rayConfig.inbounds.Clear();
|
||||
v2rayConfig.inbounds.Add(new()
|
||||
{
|
||||
tag = $"{EInboundProtocol.socks}{port}",
|
||||
listen = Global.Loopback,
|
||||
port = port,
|
||||
protocol = EInboundProtocol.mixed.ToString(),
|
||||
});
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(v2rayConfig);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public gen function
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||
{
|
||||
if (multipleLoad == EMultipleLoad.LeastPing)
|
||||
{
|
||||
var observatory = new Observatory4Ray
|
||||
{
|
||||
subjectSelector = [Global.ProxyTag],
|
||||
probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||
probeInterval = "3m",
|
||||
enableConcurrency = true,
|
||||
};
|
||||
v2rayConfig.observatory = observatory;
|
||||
}
|
||||
else if (multipleLoad == EMultipleLoad.LeastLoad)
|
||||
{
|
||||
var burstObservatory = new BurstObservatory4Ray
|
||||
{
|
||||
subjectSelector = [Global.ProxyTag],
|
||||
pingConfig = new()
|
||||
{
|
||||
destination = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl,
|
||||
interval = "5m",
|
||||
timeout = "30s",
|
||||
sampling = 2,
|
||||
}
|
||||
};
|
||||
v2rayConfig.burstObservatory = burstObservatory;
|
||||
}
|
||||
var strategyType = multipleLoad switch
|
||||
{
|
||||
EMultipleLoad.Random => "random",
|
||||
EMultipleLoad.RoundRobin => "roundRobin",
|
||||
EMultipleLoad.LeastPing => "leastPing",
|
||||
EMultipleLoad.LeastLoad => "leastLoad",
|
||||
_ => "roundRobin",
|
||||
};
|
||||
var balancer = new BalancersItem4Ray
|
||||
{
|
||||
selector = [Global.ProxyTag],
|
||||
strategy = new() { type = strategyType },
|
||||
tag = $"{Global.ProxyTag}-round",
|
||||
};
|
||||
v2rayConfig.routing.balancers = [balancer];
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
|
||||
{
|
||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(v2rayConfig);
|
||||
}
|
||||
|
||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||
if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0)
|
||||
{
|
||||
var balancer = v2rayConfig.routing.balancers.First();
|
||||
|
||||
// Modify existing rules in custom config
|
||||
var rulesNode = fullConfigTemplateNode["routing"]?["rules"];
|
||||
if (rulesNode != null)
|
||||
{
|
||||
foreach (var rule in rulesNode.AsArray())
|
||||
{
|
||||
if (rule["outboundTag"]?.GetValue<string>() == Global.ProxyTag)
|
||||
{
|
||||
rule.AsObject().Remove("outboundTag");
|
||||
rule["balancerTag"] = balancer.tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure routing node exists
|
||||
if (fullConfigTemplateNode["routing"] == null)
|
||||
{
|
||||
fullConfigTemplateNode["routing"] = new JsonObject();
|
||||
}
|
||||
|
||||
// Handle balancers - append instead of override
|
||||
if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode)
|
||||
{
|
||||
if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers)
|
||||
{
|
||||
foreach (var balancerNode in newBalancers)
|
||||
{
|
||||
customBalancersNode.Add(balancerNode?.DeepClone());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle outbounds - append instead of override
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||
foreach (var outbound in v2rayConfig.outbounds)
|
||||
{
|
||||
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() == true) && (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) && !(Utils.IsPrivateNetwork(outbound.settings?.servers?.FirstOrDefault()?.address ?? string.Empty) || Utils.IsPrivateNetwork(outbound.settings?.vnext?.FirstOrDefault()?.address ?? string.Empty)))
|
||||
{
|
||||
outbound.streamSettings ??= new StreamSettings4Ray();
|
||||
outbound.streamSettings.sockopt ??= new Sockopt4Ray();
|
||||
outbound.streamSettings.sockopt.dialerProxy = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
}
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode));
|
||||
}
|
||||
}
|
426
v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs
Normal file
426
v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs
Normal file
|
@ -0,0 +1,426 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenDns(ProfileItem? node, V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
if (item != null && item.Enabled == true)
|
||||
{
|
||||
var result = await GenDnsCompatible(node, v2rayConfig);
|
||||
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
// DNS routing
|
||||
v2rayConfig.dns.tag = Global.DnsTag;
|
||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
var simpleDNSItem = _config.SimpleDNSItem;
|
||||
var domainStrategy4Freedom = simpleDNSItem?.RayStrategy4Freedom;
|
||||
|
||||
//Outbound Freedom domainStrategy
|
||||
if (domainStrategy4Freedom.IsNotEmpty())
|
||||
{
|
||||
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = domainStrategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await GenDnsServers(node, v2rayConfig, simpleDNSItem);
|
||||
await GenDnsHosts(v2rayConfig, simpleDNSItem);
|
||||
|
||||
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
|
||||
{
|
||||
// DNS routing
|
||||
v2rayConfig.dns.tag = Global.DnsTag;
|
||||
v2rayConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
|
||||
{
|
||||
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
|
||||
{
|
||||
var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';')
|
||||
.Select(addr => addr.Trim())
|
||||
.Where(addr => !string.IsNullOrEmpty(addr))
|
||||
.Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr)
|
||||
.Distinct()
|
||||
.ToList() ?? new List<string> { defaultAddress };
|
||||
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
|
||||
}
|
||||
|
||||
static object CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
|
||||
{
|
||||
var dnsServer = new DnsServer4Ray
|
||||
{
|
||||
address = dnsAddress,
|
||||
skipFallback = true,
|
||||
domains = domains.Count > 0 ? domains : null,
|
||||
expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null
|
||||
};
|
||||
return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
}
|
||||
|
||||
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
|
||||
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
|
||||
|
||||
var directDomainList = new List<string>();
|
||||
var directGeositeList = new List<string>();
|
||||
var proxyDomainList = new List<string>();
|
||||
var proxyGeositeList = new List<string>();
|
||||
var expectedDomainList = new List<string>();
|
||||
var expectedIPs = new List<string>();
|
||||
var regionNames = new HashSet<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
|
||||
{
|
||||
expectedIPs = simpleDNSItem.DirectExpectedIPs
|
||||
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToList();
|
||||
|
||||
foreach (var ip in expectedIPs)
|
||||
{
|
||||
if (ip.StartsWith("geoip:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var region = ip["geoip:".Length..];
|
||||
if (!string.IsNullOrEmpty(region))
|
||||
{
|
||||
regionNames.Add($"geosite:{region}");
|
||||
regionNames.Add($"geosite:geolocation-{region}");
|
||||
regionNames.Add($"geosite:tld-{region}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
List<RulesItem>? rules = null;
|
||||
if (routing != null)
|
||||
{
|
||||
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
|
||||
foreach (var item in rules)
|
||||
{
|
||||
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var domain in item.Domain)
|
||||
{
|
||||
if (domain.StartsWith('#'))
|
||||
continue;
|
||||
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
|
||||
|
||||
if (item.OutboundTag == Global.DirectTag)
|
||||
{
|
||||
if (normalizedDomain.StartsWith("geosite:"))
|
||||
{
|
||||
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
directDomainList.Add(normalizedDomain);
|
||||
}
|
||||
}
|
||||
else if (item.OutboundTag != Global.BlockTag)
|
||||
{
|
||||
if (normalizedDomain.StartsWith("geosite:"))
|
||||
{
|
||||
proxyGeositeList.Add(normalizedDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
proxyDomainList.Add(normalizedDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.IsDomain(node?.Address))
|
||||
{
|
||||
directDomainList.Add(node.Address);
|
||||
}
|
||||
|
||||
if (node?.Subid is not null)
|
||||
{
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
if (subItem is not null)
|
||||
{
|
||||
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
|
||||
{
|
||||
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
|
||||
if (profileNode is not null &&
|
||||
profileNode.ConfigType is not (EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) &&
|
||||
Utils.IsDomain(profileNode.Address))
|
||||
{
|
||||
directDomainList.Add(profileNode.Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v2rayConfig.dns ??= new Dns4Ray();
|
||||
v2rayConfig.dns.servers ??= new List<object>();
|
||||
|
||||
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
|
||||
{
|
||||
if (domains.Count > 0)
|
||||
{
|
||||
foreach (var dnsAddress in dnsAddresses)
|
||||
{
|
||||
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddDnsServers(remoteDNSAddress, proxyDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList);
|
||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||
AddDnsServers(directDNSAddress, directGeositeList);
|
||||
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
|
||||
|
||||
var useDirectDns = rules?.LastOrDefault() is { } lastRule &&
|
||||
lastRule.OutboundTag == Global.DirectTag &&
|
||||
(lastRule.Port == "0-65535" ||
|
||||
lastRule.Network == "tcp,udp" ||
|
||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||
|
||||
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
||||
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
|
||||
{
|
||||
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||
{
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
v2rayConfig.dns ??= new Dns4Ray();
|
||||
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
|
||||
if (simpleDNSItem.AddCommonHosts == true)
|
||||
{
|
||||
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => (object)kvp.Value
|
||||
);
|
||||
}
|
||||
|
||||
if (simpleDNSItem.UseSystemHosts == true)
|
||||
{
|
||||
var systemHosts = Utils.GetSystemHosts();
|
||||
if (systemHosts.Count > 0)
|
||||
{
|
||||
var normalHost = v2rayConfig.dns.hosts;
|
||||
if (normalHost != null)
|
||||
{
|
||||
foreach (var host in systemHosts)
|
||||
{
|
||||
if (normalHost[host.Key] != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
normalHost[host.Key] = new List<string> { host.Value };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var userHostsMap = simpleDNSItem.Hosts?
|
||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||
.Where(line => line.Contains(' '))
|
||||
.ToDictionary(
|
||||
line =>
|
||||
{
|
||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts[0];
|
||||
},
|
||||
line =>
|
||||
{
|
||||
var parts = line.Trim().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var values = parts.Skip(1).ToList();
|
||||
return values;
|
||||
}
|
||||
);
|
||||
|
||||
if (userHostsMap != null)
|
||||
{
|
||||
foreach (var kvp in userHostsMap)
|
||||
{
|
||||
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
var normalDNS = item?.NormalDNS;
|
||||
var domainStrategy4Freedom = item?.DomainStrategy4Freedom;
|
||||
if (normalDNS.IsNullOrEmpty())
|
||||
{
|
||||
normalDNS = EmbedUtils.GetEmbedText(Global.DNSV2rayNormalFileName);
|
||||
}
|
||||
|
||||
//Outbound Freedom domainStrategy
|
||||
if (domainStrategy4Freedom.IsNotEmpty())
|
||||
{
|
||||
var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new();
|
||||
outbound.settings.domainStrategy = domainStrategy4Freedom;
|
||||
outbound.settings.userLevel = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var obj = JsonUtils.ParseJson(normalDNS);
|
||||
if (obj is null)
|
||||
{
|
||||
List<string> servers = [];
|
||||
string[] arrDNS = normalDNS.Split(',');
|
||||
foreach (string str in arrDNS)
|
||||
{
|
||||
servers.Add(str);
|
||||
}
|
||||
obj = JsonUtils.ParseJson("{}");
|
||||
obj["servers"] = JsonUtils.SerializeToNode(servers);
|
||||
}
|
||||
|
||||
// Append to dns settings
|
||||
if (item.UseSystemHosts)
|
||||
{
|
||||
var systemHosts = Utils.GetSystemHosts();
|
||||
if (systemHosts.Count > 0)
|
||||
{
|
||||
var normalHost1 = obj["hosts"];
|
||||
if (normalHost1 != null)
|
||||
{
|
||||
foreach (var host in systemHosts)
|
||||
{
|
||||
if (normalHost1[host.Key] != null)
|
||||
continue;
|
||||
normalHost1[host.Key] = host.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var normalHost = obj["hosts"];
|
||||
if (normalHost != null)
|
||||
{
|
||||
foreach (var hostProp in normalHost.AsObject().ToList())
|
||||
{
|
||||
if (hostProp.Value is JsonValue value && value.TryGetValue<string>(out var ip))
|
||||
{
|
||||
normalHost[hostProp.Key] = new JsonArray(ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await GenDnsDomainsCompatible(node, obj, item);
|
||||
|
||||
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(obj));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dNSItem)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var servers = dns["servers"];
|
||||
if (servers != null)
|
||||
{
|
||||
var domainList = new List<string>();
|
||||
if (Utils.IsDomain(node.Address))
|
||||
{
|
||||
domainList.Add(node.Address);
|
||||
}
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
if (subItem is not null)
|
||||
{
|
||||
// Previous proxy
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
if (prevNode is not null
|
||||
&& prevNode.ConfigType != EConfigType.Custom
|
||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||
&& prevNode.ConfigType != EConfigType.TUIC
|
||||
&& Utils.IsDomain(prevNode.Address))
|
||||
{
|
||||
domainList.Add(prevNode.Address);
|
||||
}
|
||||
|
||||
// Next proxy
|
||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode is not null
|
||||
&& nextNode.ConfigType != EConfigType.Custom
|
||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
||||
&& nextNode.ConfigType != EConfigType.TUIC
|
||||
&& Utils.IsDomain(nextNode.Address))
|
||||
{
|
||||
domainList.Add(nextNode.Address);
|
||||
}
|
||||
}
|
||||
if (domainList.Count > 0)
|
||||
{
|
||||
var dnsServer = new DnsServer4Ray()
|
||||
{
|
||||
address = string.IsNullOrEmpty(dNSItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dNSItem?.DomainDNSAddress,
|
||||
skipFallback = true,
|
||||
domains = domainList
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenInbounds(V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listen = "0.0.0.0";
|
||||
v2rayConfig.inbounds = [];
|
||||
|
||||
var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true);
|
||||
v2rayConfig.inbounds.Add(inbound);
|
||||
|
||||
if (_config.Inbound.First().SecondLocalPortEnabled)
|
||||
{
|
||||
var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true);
|
||||
v2rayConfig.inbounds.Add(inbound2);
|
||||
}
|
||||
|
||||
if (_config.Inbound.First().AllowLANConn)
|
||||
{
|
||||
if (_config.Inbound.First().NewPort4LAN)
|
||||
{
|
||||
var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true);
|
||||
inbound3.listen = listen;
|
||||
v2rayConfig.inbounds.Add(inbound3);
|
||||
|
||||
//auth
|
||||
if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty())
|
||||
{
|
||||
inbound3.settings.auth = "password";
|
||||
inbound3.settings.accounts = new List<AccountsItem4Ray> { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inbound.listen = listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks)
|
||||
{
|
||||
string result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
var inbound = JsonUtils.Deserialize<Inbounds4Ray>(result);
|
||||
if (inbound == null)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
inbound.tag = protocol.ToString();
|
||||
inbound.port = inItem.LocalPort + (int)protocol;
|
||||
inbound.protocol = EInboundProtocol.mixed.ToString();
|
||||
inbound.settings.udp = inItem.UdpEnabled;
|
||||
inbound.sniffing.enabled = inItem.SniffingEnabled;
|
||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
||||
|
||||
return inbound;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenLog(V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config.CoreBasicItem.LogEnabled)
|
||||
{
|
||||
var dtNow = DateTime.Now;
|
||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt");
|
||||
v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel;
|
||||
v2rayConfig.log.access = null;
|
||||
v2rayConfig.log.error = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,704 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenOutbound(ProfileItem node, Outbounds4Ray outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled;
|
||||
switch (node.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
{
|
||||
VnextItem4Ray vnextItem;
|
||||
if (outbound.settings.vnext.Count <= 0)
|
||||
{
|
||||
vnextItem = new VnextItem4Ray();
|
||||
outbound.settings.vnext.Add(vnextItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
vnextItem = outbound.settings.vnext.First();
|
||||
}
|
||||
vnextItem.address = node.Address;
|
||||
vnextItem.port = node.Port;
|
||||
|
||||
UsersItem4Ray usersItem;
|
||||
if (vnextItem.users.Count <= 0)
|
||||
{
|
||||
usersItem = new UsersItem4Ray();
|
||||
vnextItem.users.Add(usersItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
usersItem = vnextItem.users.First();
|
||||
}
|
||||
|
||||
usersItem.id = node.Id;
|
||||
usersItem.alterId = node.AlterId;
|
||||
usersItem.email = Global.UserEMail;
|
||||
if (Global.VmessSecurities.Contains(node.Security))
|
||||
{
|
||||
usersItem.security = node.Security;
|
||||
}
|
||||
else
|
||||
{
|
||||
usersItem.security = Global.DefaultSecurity;
|
||||
}
|
||||
|
||||
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
||||
|
||||
outbound.settings.servers = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.Shadowsocks:
|
||||
{
|
||||
ServersItem4Ray serversItem;
|
||||
if (outbound.settings.servers.Count <= 0)
|
||||
{
|
||||
serversItem = new ServersItem4Ray();
|
||||
outbound.settings.servers.Add(serversItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
serversItem = outbound.settings.servers.First();
|
||||
}
|
||||
serversItem.address = node.Address;
|
||||
serversItem.port = node.Port;
|
||||
serversItem.password = node.Id;
|
||||
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(node.Security) ? node.Security : "none";
|
||||
|
||||
serversItem.ota = false;
|
||||
serversItem.level = 1;
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
|
||||
outbound.settings.vnext = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.SOCKS:
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
ServersItem4Ray serversItem;
|
||||
if (outbound.settings.servers.Count <= 0)
|
||||
{
|
||||
serversItem = new ServersItem4Ray();
|
||||
outbound.settings.servers.Add(serversItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
serversItem = outbound.settings.servers.First();
|
||||
}
|
||||
serversItem.address = node.Address;
|
||||
serversItem.port = node.Port;
|
||||
serversItem.method = null;
|
||||
serversItem.password = null;
|
||||
|
||||
if (node.Security.IsNotEmpty()
|
||||
&& node.Id.IsNotEmpty())
|
||||
{
|
||||
SocksUsersItem4Ray socksUsersItem = new()
|
||||
{
|
||||
user = node.Security,
|
||||
pass = node.Id,
|
||||
level = 1
|
||||
};
|
||||
|
||||
serversItem.users = new List<SocksUsersItem4Ray>() { socksUsersItem };
|
||||
}
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
|
||||
outbound.settings.vnext = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.VLESS:
|
||||
{
|
||||
VnextItem4Ray vnextItem;
|
||||
if (outbound.settings.vnext?.Count <= 0)
|
||||
{
|
||||
vnextItem = new VnextItem4Ray();
|
||||
outbound.settings.vnext.Add(vnextItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
vnextItem = outbound.settings.vnext.First();
|
||||
}
|
||||
vnextItem.address = node.Address;
|
||||
vnextItem.port = node.Port;
|
||||
|
||||
UsersItem4Ray usersItem;
|
||||
if (vnextItem.users.Count <= 0)
|
||||
{
|
||||
usersItem = new UsersItem4Ray();
|
||||
vnextItem.users.Add(usersItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
usersItem = vnextItem.users.First();
|
||||
}
|
||||
usersItem.id = node.Id;
|
||||
usersItem.email = Global.UserEMail;
|
||||
usersItem.encryption = node.Security;
|
||||
|
||||
if (node.Flow.IsNullOrEmpty())
|
||||
{
|
||||
await GenOutboundMux(node, outbound, muxEnabled, muxEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
usersItem.flow = node.Flow;
|
||||
await GenOutboundMux(node, outbound, false, muxEnabled);
|
||||
}
|
||||
outbound.settings.servers = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.Trojan:
|
||||
{
|
||||
ServersItem4Ray serversItem;
|
||||
if (outbound.settings.servers.Count <= 0)
|
||||
{
|
||||
serversItem = new ServersItem4Ray();
|
||||
outbound.settings.servers.Add(serversItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
serversItem = outbound.settings.servers.First();
|
||||
}
|
||||
serversItem.address = node.Address;
|
||||
serversItem.port = node.Port;
|
||||
serversItem.password = node.Id;
|
||||
|
||||
serversItem.ota = false;
|
||||
serversItem.level = 1;
|
||||
|
||||
await GenOutboundMux(node, outbound);
|
||||
|
||||
outbound.settings.vnext = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.WireGuard:
|
||||
{
|
||||
var peer = new WireguardPeer4Ray
|
||||
{
|
||||
publicKey = node.PublicKey,
|
||||
endpoint = node.Address + ":" + node.Port.ToString()
|
||||
};
|
||||
var setting = new Outboundsettings4Ray
|
||||
{
|
||||
address = Utils.String2List(node.RequestHost),
|
||||
secretKey = node.Id,
|
||||
reserved = Utils.String2List(node.Path)?.Select(int.Parse).ToList(),
|
||||
mtu = node.ShortId.IsNullOrEmpty() ? Global.TunMtus.First() : node.ShortId.ToInt(),
|
||||
peers = new List<WireguardPeer4Ray> { peer }
|
||||
};
|
||||
outbound.settings = setting;
|
||||
outbound.settings.vnext = null;
|
||||
outbound.settings.servers = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
outbound.protocol = Global.ProtocolTypes[node.ConfigType];
|
||||
await GenBoundStreamSettings(node, outbound);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
outbound.mux.enabled = false;
|
||||
outbound.mux.concurrency = -1;
|
||||
|
||||
if (enabledTCP)
|
||||
{
|
||||
outbound.mux.enabled = true;
|
||||
outbound.mux.concurrency = _config.Mux4RayItem.Concurrency;
|
||||
}
|
||||
else if (enabledUDP)
|
||||
{
|
||||
outbound.mux.enabled = true;
|
||||
outbound.mux.xudpConcurrency = _config.Mux4RayItem.XudpConcurrency;
|
||||
outbound.mux.xudpProxyUDP443 = _config.Mux4RayItem.XudpProxyUDP443;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<int> GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound)
|
||||
{
|
||||
try
|
||||
{
|
||||
var streamSettings = outbound.streamSettings;
|
||||
streamSettings.network = node.GetNetwork();
|
||||
var host = node.RequestHost.TrimEx();
|
||||
var path = node.Path.TrimEx();
|
||||
var sni = node.Sni.TrimEx();
|
||||
var useragent = "";
|
||||
if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
useragent = _config.CoreBasicItem.DefUserAgent;
|
||||
}
|
||||
}
|
||||
|
||||
//if tls
|
||||
if (node.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
streamSettings.security = node.StreamSecurity;
|
||||
|
||||
TlsSettings4Ray tlsSettings = new()
|
||||
{
|
||||
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||
alpn = node.GetAlpn(),
|
||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
||||
};
|
||||
if (sni.IsNotEmpty())
|
||||
{
|
||||
tlsSettings.serverName = sni;
|
||||
}
|
||||
else if (host.IsNotEmpty())
|
||||
{
|
||||
tlsSettings.serverName = Utils.String2List(host)?.First();
|
||||
}
|
||||
streamSettings.tlsSettings = tlsSettings;
|
||||
}
|
||||
|
||||
//if Reality
|
||||
if (node.StreamSecurity == Global.StreamSecurityReality)
|
||||
{
|
||||
streamSettings.security = node.StreamSecurity;
|
||||
|
||||
TlsSettings4Ray realitySettings = new()
|
||||
{
|
||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
|
||||
serverName = sni,
|
||||
publicKey = node.PublicKey,
|
||||
shortId = node.ShortId,
|
||||
spiderX = node.SpiderX,
|
||||
mldsa65Verify = node.Mldsa65Verify,
|
||||
show = false,
|
||||
};
|
||||
|
||||
streamSettings.realitySettings = realitySettings;
|
||||
}
|
||||
|
||||
//streamSettings
|
||||
switch (node.GetNetwork())
|
||||
{
|
||||
case nameof(ETransport.kcp):
|
||||
KcpSettings4Ray kcpSettings = new()
|
||||
{
|
||||
mtu = _config.KcpItem.Mtu,
|
||||
tti = _config.KcpItem.Tti
|
||||
};
|
||||
|
||||
kcpSettings.uplinkCapacity = _config.KcpItem.UplinkCapacity;
|
||||
kcpSettings.downlinkCapacity = _config.KcpItem.DownlinkCapacity;
|
||||
|
||||
kcpSettings.congestion = _config.KcpItem.Congestion;
|
||||
kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize;
|
||||
kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize;
|
||||
kcpSettings.header = new Header4Ray
|
||||
{
|
||||
type = node.HeaderType,
|
||||
domain = host.IsNullOrEmpty() ? null : host
|
||||
};
|
||||
if (path.IsNotEmpty())
|
||||
{
|
||||
kcpSettings.seed = path;
|
||||
}
|
||||
streamSettings.kcpSettings = kcpSettings;
|
||||
break;
|
||||
//ws
|
||||
case nameof(ETransport.ws):
|
||||
WsSettings4Ray wsSettings = new();
|
||||
wsSettings.headers = new Headers4Ray();
|
||||
|
||||
if (host.IsNotEmpty())
|
||||
{
|
||||
wsSettings.host = host;
|
||||
wsSettings.headers.Host = host;
|
||||
}
|
||||
if (path.IsNotEmpty())
|
||||
{
|
||||
wsSettings.path = path;
|
||||
}
|
||||
if (useragent.IsNotEmpty())
|
||||
{
|
||||
wsSettings.headers.UserAgent = useragent;
|
||||
}
|
||||
streamSettings.wsSettings = wsSettings;
|
||||
|
||||
break;
|
||||
//httpupgrade
|
||||
case nameof(ETransport.httpupgrade):
|
||||
HttpupgradeSettings4Ray httpupgradeSettings = new();
|
||||
|
||||
if (path.IsNotEmpty())
|
||||
{
|
||||
httpupgradeSettings.path = path;
|
||||
}
|
||||
if (host.IsNotEmpty())
|
||||
{
|
||||
httpupgradeSettings.host = host;
|
||||
}
|
||||
streamSettings.httpupgradeSettings = httpupgradeSettings;
|
||||
|
||||
break;
|
||||
//xhttp
|
||||
case nameof(ETransport.xhttp):
|
||||
streamSettings.network = ETransport.xhttp.ToString();
|
||||
XhttpSettings4Ray xhttpSettings = new();
|
||||
|
||||
if (path.IsNotEmpty())
|
||||
{
|
||||
xhttpSettings.path = path;
|
||||
}
|
||||
if (host.IsNotEmpty())
|
||||
{
|
||||
xhttpSettings.host = host;
|
||||
}
|
||||
if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType))
|
||||
{
|
||||
xhttpSettings.mode = node.HeaderType;
|
||||
}
|
||||
if (node.Extra.IsNotEmpty())
|
||||
{
|
||||
xhttpSettings.extra = JsonUtils.ParseJson(node.Extra);
|
||||
}
|
||||
|
||||
streamSettings.xhttpSettings = xhttpSettings;
|
||||
await GenOutboundMux(node, outbound);
|
||||
|
||||
break;
|
||||
//h2
|
||||
case nameof(ETransport.h2):
|
||||
HttpSettings4Ray httpSettings = new();
|
||||
|
||||
if (host.IsNotEmpty())
|
||||
{
|
||||
httpSettings.host = Utils.String2List(host);
|
||||
}
|
||||
httpSettings.path = path;
|
||||
|
||||
streamSettings.httpSettings = httpSettings;
|
||||
|
||||
break;
|
||||
//quic
|
||||
case nameof(ETransport.quic):
|
||||
QuicSettings4Ray quicsettings = new()
|
||||
{
|
||||
security = host,
|
||||
key = path,
|
||||
header = new Header4Ray
|
||||
{
|
||||
type = node.HeaderType
|
||||
}
|
||||
};
|
||||
streamSettings.quicSettings = quicsettings;
|
||||
if (node.StreamSecurity == Global.StreamSecurity)
|
||||
{
|
||||
if (sni.IsNotEmpty())
|
||||
{
|
||||
streamSettings.tlsSettings.serverName = sni;
|
||||
}
|
||||
else
|
||||
{
|
||||
streamSettings.tlsSettings.serverName = node.Address;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case nameof(ETransport.grpc):
|
||||
GrpcSettings4Ray grpcSettings = new()
|
||||
{
|
||||
authority = host.IsNullOrEmpty() ? null : host,
|
||||
serviceName = path,
|
||||
multiMode = node.HeaderType == Global.GrpcMultiMode,
|
||||
idle_timeout = _config.GrpcItem.IdleTimeout,
|
||||
health_check_timeout = _config.GrpcItem.HealthCheckTimeout,
|
||||
permit_without_stream = _config.GrpcItem.PermitWithoutStream,
|
||||
initial_windows_size = _config.GrpcItem.InitialWindowsSize,
|
||||
};
|
||||
streamSettings.grpcSettings = grpcSettings;
|
||||
break;
|
||||
|
||||
default:
|
||||
//tcp
|
||||
if (node.HeaderType == Global.TcpHeaderHttp)
|
||||
{
|
||||
TcpSettings4Ray tcpSettings = new()
|
||||
{
|
||||
header = new Header4Ray
|
||||
{
|
||||
type = node.HeaderType
|
||||
}
|
||||
};
|
||||
|
||||
//request Host
|
||||
string request = EmbedUtils.GetEmbedText(Global.V2raySampleHttpRequestFileName);
|
||||
string[] arrHost = host.Split(',');
|
||||
string host2 = string.Join(",".AppendQuotes(), arrHost);
|
||||
request = request.Replace("$requestHost$", $"{host2.AppendQuotes()}");
|
||||
request = request.Replace("$requestUserAgent$", $"{useragent.AppendQuotes()}");
|
||||
//Path
|
||||
string pathHttp = @"/";
|
||||
if (path.IsNotEmpty())
|
||||
{
|
||||
string[] arrPath = path.Split(',');
|
||||
pathHttp = string.Join(",".AppendQuotes(), arrPath);
|
||||
}
|
||||
request = request.Replace("$requestPath$", $"{pathHttp.AppendQuotes()}");
|
||||
tcpSettings.header.request = JsonUtils.Deserialize<object>(request);
|
||||
|
||||
streamSettings.tcpSettings = tcpSettings;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
|
||||
{
|
||||
//fragment proxy
|
||||
if (_config.CoreBasicItem.EnableFragment
|
||||
&& v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false)
|
||||
{
|
||||
var fragmentOutbound = new Outbounds4Ray
|
||||
{
|
||||
protocol = "freedom",
|
||||
tag = $"{Global.ProxyTag}3",
|
||||
settings = new()
|
||||
{
|
||||
fragment = new()
|
||||
{
|
||||
packets = _config.Fragment4RayItem?.Packets,
|
||||
length = _config.Fragment4RayItem?.Length,
|
||||
interval = _config.Fragment4RayItem?.Interval
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
v2rayConfig.outbounds.Add(fragmentOutbound);
|
||||
v2rayConfig.outbounds.First().streamSettings.sockopt = new()
|
||||
{
|
||||
dialerProxy = fragmentOutbound.tag
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
if (subItem is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//current proxy
|
||||
var outbound = v2rayConfig.outbounds.First();
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
|
||||
//Previous proxy
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
string? prevOutboundTag = null;
|
||||
if (prevNode is not null
|
||||
&& prevNode.ConfigType != EConfigType.Custom
|
||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||
&& prevNode.ConfigType != EConfigType.TUIC
|
||||
&& prevNode.ConfigType != EConfigType.Anytls)
|
||||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||
prevOutbound.tag = prevOutboundTag;
|
||||
v2rayConfig.outbounds.Add(prevOutbound);
|
||||
}
|
||||
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||
|
||||
if (nextOutbound is not null)
|
||||
{
|
||||
v2rayConfig.outbounds.Insert(0, nextOutbound);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get template and initialize list
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
if (txtOutbound.IsNullOrEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var resultOutbounds = new List<Outbounds4Ray>();
|
||||
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
||||
|
||||
// Cache for chain proxies to avoid duplicate generation
|
||||
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
||||
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||
int prevIndex = 0; // Index for prev outbounds
|
||||
|
||||
// Process nodes
|
||||
int index = 0;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
index++;
|
||||
|
||||
// Handle proxy chain
|
||||
string? prevTag = null;
|
||||
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||
if (nextOutbound != null)
|
||||
{
|
||||
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(node.Subid);
|
||||
|
||||
// current proxy
|
||||
await GenOutbound(node, currentOutbound);
|
||||
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||
|
||||
if (!node.Subid.IsNullOrEmpty())
|
||||
{
|
||||
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||
{
|
||||
prevTag = value; // maybe null
|
||||
}
|
||||
else
|
||||
{
|
||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||
if (prevNode is not null
|
||||
&& prevNode.ConfigType != EConfigType.Custom
|
||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||
&& prevNode.ConfigType != EConfigType.TUIC
|
||||
&& prevNode.ConfigType != EConfigType.Anytls)
|
||||
{
|
||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(prevNode, prevOutbound);
|
||||
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||
prevOutbound.tag = prevTag;
|
||||
prevOutbounds.Add(prevOutbound);
|
||||
}
|
||||
prevProxyTags[node.Subid] = prevTag;
|
||||
}
|
||||
|
||||
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
||||
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||
{
|
||||
nextProxyCache[node.Subid] = nextOutbound;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextOutbound is not null)
|
||||
{
|
||||
resultOutbounds.Add(nextOutbound);
|
||||
}
|
||||
resultOutbounds.Add(currentOutbound);
|
||||
}
|
||||
|
||||
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
||||
resultOutbounds.AddRange(prevOutbounds);
|
||||
resultOutbounds.AddRange(v2rayConfig.outbounds);
|
||||
v2rayConfig.outbounds = resultOutbounds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a chained outbound configuration for the given subItem and outbound.
|
||||
/// The outbound's tag must be set before calling this method.
|
||||
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
|
||||
/// </summary>
|
||||
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||
/// <returns>
|
||||
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||
/// </returns>
|
||||
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
|
||||
if (!prevOutboundTag.IsNullOrEmpty())
|
||||
{
|
||||
outbound.streamSettings.sockopt = new()
|
||||
{
|
||||
dialerProxy = prevOutboundTag
|
||||
};
|
||||
}
|
||||
|
||||
// Next proxy
|
||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||
if (nextNode is not null
|
||||
&& nextNode.ConfigType != EConfigType.Custom
|
||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
||||
&& nextNode.ConfigType != EConfigType.TUIC
|
||||
&& nextNode.ConfigType != EConfigType.Anytls)
|
||||
{
|
||||
if (nextOutbound == null)
|
||||
{
|
||||
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(nextNode, nextOutbound);
|
||||
}
|
||||
nextOutbound.tag = outbound.tag;
|
||||
|
||||
outbound.tag = $"mid-{outbound.tag}";
|
||||
nextOutbound.streamSettings.sockopt = new()
|
||||
{
|
||||
dialerProxy = outbound.tag
|
||||
};
|
||||
}
|
||||
return nextOutbound;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenRouting(V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (v2rayConfig.routing?.rules != null)
|
||||
{
|
||||
v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy;
|
||||
|
||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||
if (routing != null)
|
||||
{
|
||||
if (routing.DomainStrategy.IsNotEmpty())
|
||||
{
|
||||
v2rayConfig.routing.domainStrategy = routing.DomainStrategy;
|
||||
}
|
||||
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
|
||||
foreach (var item in rules)
|
||||
{
|
||||
if (item.Enabled)
|
||||
{
|
||||
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
|
||||
await GenRoutingUserRule(item2, v2rayConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<int> GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig);
|
||||
|
||||
if (rule.port.IsNullOrEmpty())
|
||||
{
|
||||
rule.port = null;
|
||||
}
|
||||
if (rule.network.IsNullOrEmpty())
|
||||
{
|
||||
rule.network = null;
|
||||
}
|
||||
if (rule.domain?.Count == 0)
|
||||
{
|
||||
rule.domain = null;
|
||||
}
|
||||
if (rule.ip?.Count == 0)
|
||||
{
|
||||
rule.ip = null;
|
||||
}
|
||||
if (rule.protocol?.Count == 0)
|
||||
{
|
||||
rule.protocol = null;
|
||||
}
|
||||
if (rule.inboundTag?.Count == 0)
|
||||
{
|
||||
rule.inboundTag = null;
|
||||
}
|
||||
|
||||
var hasDomainIp = false;
|
||||
if (rule.domain?.Count > 0)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
it.ip = null;
|
||||
it.type = "field";
|
||||
for (var k = it.domain.Count - 1; k >= 0; k--)
|
||||
{
|
||||
if (it.domain[k].StartsWith("#"))
|
||||
{
|
||||
it.domain.RemoveAt(k);
|
||||
}
|
||||
it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ",");
|
||||
}
|
||||
v2rayConfig.routing.rules.Add(it);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
if (rule.ip?.Count > 0)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
it.domain = null;
|
||||
it.type = "field";
|
||||
v2rayConfig.routing.rules.Add(it);
|
||||
hasDomainIp = true;
|
||||
}
|
||||
if (!hasDomainIp)
|
||||
{
|
||||
if (rule.port.IsNotEmpty()
|
||||
|| rule.protocol?.Count > 0
|
||||
|| rule.inboundTag?.Count > 0
|
||||
|| rule.network != null
|
||||
)
|
||||
{
|
||||
var it = JsonUtils.DeepCopy(rule);
|
||||
it.type = "field";
|
||||
v2rayConfig.routing.rules.Add(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
|
||||
private async Task<string?> GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig)
|
||||
{
|
||||
if (Global.OutboundTags.Contains(outboundTag))
|
||||
{
|
||||
return outboundTag;
|
||||
}
|
||||
|
||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||
if (node == null
|
||||
|| node.ConfigType == EConfigType.Custom
|
||||
|| node.ConfigType == EConfigType.Hysteria2
|
||||
|| node.ConfigType == EConfigType.TUIC
|
||||
|| node.ConfigType == EConfigType.Anytls)
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
await GenOutbound(node, outbound);
|
||||
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
|
||||
v2rayConfig.outbounds.Add(outbound);
|
||||
|
||||
return outbound.tag;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private async Task<int> GenStatistic(V2rayConfig v2rayConfig)
|
||||
{
|
||||
if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed)
|
||||
{
|
||||
string tag = EInboundProtocol.api.ToString();
|
||||
Metrics4Ray apiObj = new();
|
||||
Policy4Ray policyObj = new();
|
||||
SystemPolicy4Ray policySystemSetting = new();
|
||||
|
||||
v2rayConfig.stats = new Stats4Ray();
|
||||
|
||||
apiObj.tag = tag;
|
||||
v2rayConfig.metrics = apiObj;
|
||||
|
||||
policySystemSetting.statsOutboundDownlink = true;
|
||||
policySystemSetting.statsOutboundUplink = true;
|
||||
policyObj.system = policySystemSetting;
|
||||
v2rayConfig.policy = policyObj;
|
||||
|
||||
if (!v2rayConfig.inbounds.Exists(item => item.tag == tag))
|
||||
{
|
||||
Inbounds4Ray apiInbound = new();
|
||||
Inboundsettings4Ray apiInboundSettings = new();
|
||||
apiInbound.tag = tag;
|
||||
apiInbound.listen = Global.Loopback;
|
||||
apiInbound.port = AppManager.Instance.StatePort;
|
||||
apiInbound.protocol = Global.InboundAPIProtocol;
|
||||
apiInboundSettings.address = Global.Loopback;
|
||||
apiInbound.settings = apiInboundSettings;
|
||||
v2rayConfig.inbounds.Add(apiInbound);
|
||||
}
|
||||
|
||||
if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag))
|
||||
{
|
||||
RulesItem4Ray apiRoutingRule = new()
|
||||
{
|
||||
inboundTag = new List<string> { tag },
|
||||
outboundTag = tag,
|
||||
type = "field"
|
||||
};
|
||||
|
||||
v2rayConfig.routing.rules.Add(apiRoutingRule);
|
||||
}
|
||||
}
|
||||
return await Task.FromResult(0);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue