From f66226c103b6a23857b080ddbb8efdb396dc2d42 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sun, 17 Aug 2025 20:09:41 +0800 Subject: [PATCH] Simple refactoring of CoreConfig generated code --- .../CoreConfig/CoreConfigSingboxService.cs | 2278 ----------------- .../CoreConfig/CoreConfigV2rayService.cs | 1952 -------------- .../Singbox/CoreConfigSingboxService.cs | 522 ++++ .../Singbox/SingboxConfigTemplateService.cs | 63 + .../CoreConfig/Singbox/SingboxDnsService.cs | 482 ++++ .../Singbox/SingboxInboundService.cs | 92 + .../CoreConfig/Singbox/SingboxLogService.cs | 40 + .../Singbox/SingboxOutboundService.cs | 577 +++++ .../Singbox/SingboxRoutingService.cs | 365 +++ .../Singbox/SingboxRulesetService.cs | 119 + .../Singbox/SingboxStatisticService.cs | 29 + .../V2ray/CoreConfigV2rayService.cs | 415 +++ .../CoreConfig/V2ray/V2rayBalancerService.cs | 50 + .../V2ray/V2rayConfigTemplateService.cs | 86 + .../CoreConfig/V2ray/V2rayDnsService.cs | 426 +++ .../CoreConfig/V2ray/V2rayInboundService.cs | 72 + .../CoreConfig/V2ray/V2rayLogService.cs | 29 + .../CoreConfig/V2ray/V2rayOutboundService.cs | 704 +++++ .../CoreConfig/V2ray/V2rayRoutingService.cs | 145 ++ .../CoreConfig/V2ray/V2rayStatisticService.cs | 51 + 20 files changed, 4267 insertions(+), 4230 deletions(-) delete mode 100644 v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs delete mode 100644 v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs create mode 100644 v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs deleted file mode 100644 index 53db08d1..00000000 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs +++ /dev/null @@ -1,2278 +0,0 @@ -using System.Data; -using System.Net; -using System.Net.NetworkInformation; -using System.Text.Json.Nodes; - -namespace ServiceLib.Services.CoreConfig; - -public class CoreConfigSingboxService -{ - private Config _config; - private static readonly string _tag = "CoreConfigSingboxService"; - - public CoreConfigSingboxService(Config config) - { - _config = config; - } - - #region public gen function - - public async Task 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; - - string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - if (result.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(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 GenerateClientSpeedtestConfig(List 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(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - List lstIpEndPoints = new(); - List 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 (int 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 { 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 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(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 GenerateClientMultipleLoadConfig(List selecteds) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - string result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - string txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(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(); - 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 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); - } - - string 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(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 - - #region private gen function - - private async Task 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); - } - - private async Task 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(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; - } - - private async Task 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 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 GenServer(ProfileItem node) - { - try - { - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (node.ConfigType == EConfigType.WireGuard) - { - var endpoint = JsonUtils.Deserialize(txtOutbound); - await GenEndpoint(node, endpoint); - return endpoint; - } - else - { - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(node, outbound); - return outbound; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(null); - } - - private async Task 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 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 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 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 GenOutboundsList(List 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(); - var resultEndpoints = new List(); // For endpoints - var prevOutbounds = new List(); // Separate list for prev outbounds - var prevEndpoints = new List(); // Separate list for prev endpoints - var proxyTags = new List(); // For selector and urltest outbounds - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag - int prevIndex = 0; // Index for prev outbounds - - // Process each node - int 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(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(); - resultEndpoints.AddRange(singboxConfig.endpoints); - singboxConfig.endpoints = resultEndpoints; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - /// - /// 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. - /// - /// The subscription item containing proxy chain information. - /// The current outbound configuration. Its tag must be set before calling this method. - /// The tag of the previous outbound in the chain, if any. - /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. - /// - /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. - /// - private async Task 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; - } - - private async Task 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>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); - if (tunRules != null) - { - singboxConfig.route.rules.AddRange(tunRules); - } - - GenRoutingDirectExe(out List lstDnsExe, out List 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(); - if (routing != null) - { - var rules = JsonUtils.Deserialize>(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 lstDnsExe, out List lstDirectExe) - { - var dnsExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); - var directExeSet = new HashSet(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(dnsExeSet); - lstDirectExe = new List(directExeSet); - } - - private async Task 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 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; - } - - private async Task 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>(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 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 { 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(); - 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 GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) - { - var finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS); - finalDns.tag = Global.SingboxFinalResolverTag; - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.servers ??= new List(); - singboxConfig.dns.servers.Add(finalDns); - return await Task.FromResult(finalDns); - } - - private async Task GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) - { - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.rules ??= new List(); - - 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 { 64, 65 }, - action = "predefined", - rcode = "NOTIMP" - }); - } - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing == null) - return 0; - - var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; - var expectedIPCidr = new List(); - var expectedIPsRegions = new List(); - var regionNames = new HashSet(); - - 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(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 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(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 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 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; - } - - private async Task 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); - } - - private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) - { - static void AddRuleSets(List ruleSets, List? rule_set) - { - if (rule_set != null) - ruleSets.AddRange(rule_set); - } - var geosite = "geosite"; - var geoip = "geoip"; - var ruleSets = new List(); - - //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(); - 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(); - 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(); - 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(); - 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 customRulesets = []; - - var routing = await ConfigHandler.GetDefaultRouting(_config); - if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) - { - var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); - if (result.IsNotEmpty()) - { - customRulesets = (JsonUtils.Deserialize>(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(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; - } - - private async Task 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)); - } - - #endregion private gen function -} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs deleted file mode 100644 index 041e3386..00000000 --- a/v2rayN/ServiceLib/Services/CoreConfig/CoreConfigV2rayService.cs +++ /dev/null @@ -1,1952 +0,0 @@ -using System.Net; -using System.Net.NetworkInformation; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -namespace ServiceLib.Services.CoreConfig; - -public class CoreConfigV2rayService -{ - private Config _config; - private static readonly string _tag = "CoreConfigV2rayService"; - - public CoreConfigV2rayService(Config config) - { - _config = config; - } - - #region public gen function - - public async Task 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(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 GenerateClientMultipleLoadConfig(List 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(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(); - 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 GenerateClientSpeedtestConfig(List 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(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - List lstIpEndPoints = new(); - List 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(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.port.ToString(); - v2rayConfig.outbounds.Add(outbound); - - //rule - RulesItem4Ray rule = new() - { - inboundTag = new List { 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 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(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 - - #region private gen function - - private async Task 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); - } - - private async Task 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 { 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(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; - } - - private async Task 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>(routing.RuleSet); - foreach (var item in rules) - { - if (item.Enabled) - { - var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); - await GenRoutingUserRule(item2, v2rayConfig); - } - } - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task 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 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(txtOutbound); - await GenOutbound(node, outbound); - outbound.tag = Global.ProxyTag + node.IndexId.ToString(); - v2rayConfig.outbounds.Add(outbound); - - return outbound.tag; - } - - private async Task 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() { 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 { 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 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 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(request); - - streamSettings.tcpSettings = tcpSettings; - } - break; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task 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 { 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 { Global.DnsTag }, - outboundTag = Global.ProxyTag, - }); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) - { - static List 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 { defaultAddress }; - return addresses.Count > 0 ? addresses : new List { defaultAddress }; - } - - static object CreateDnsServer(string dnsAddress, List domains, List? 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(); - var directGeositeList = new List(); - var proxyDomainList = new List(); - var proxyGeositeList = new List(); - var expectedDomainList = new List(); - var expectedIPs = new List(); - var regionNames = new HashSet(); - - 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? rules = null; - if (routing != null) - { - rules = JsonUtils.Deserialize>(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(); - - void AddDnsServers(List dnsAddresses, List domains, List? 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 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(); - 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 { 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 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 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(out var ip)) - { - normalHost[hostProp.Key] = new JsonArray(ip); - } - } - } - - await GenDnsDomainsCompatible(node, obj, item); - - v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - - private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) - { - if (node == null) - { - return 0; - } - var servers = dns["servers"]; - if (servers != null) - { - var domainList = new List(); - 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); - } - - private async Task 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 { tag }, - outboundTag = tag, - type = "field" - }; - - v2rayConfig.routing.rules.Add(apiRoutingRule); - } - } - return await Task.FromResult(0); - } - - private async Task 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(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 GenOutboundsList(List nodes, V2rayConfig v2rayConfig) - { - try - { - // Get template and initialize list - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - return 0; - } - - var resultOutbounds = new List(); - var prevOutbounds = new List(); // Separate list for prev outbounds and fragment - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // 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(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(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; - } - - /// - /// 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. - /// - /// The subscription item containing proxy chain information. - /// The current outbound configuration. Its tag must be set before calling this method. - /// The tag of the previous outbound in the chain, if any. - /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. - /// - /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. - /// - private async Task 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(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; - } - - private async Task 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); - } - - private async Task 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() == 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)); - } - - #endregion private gen function -} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs new file mode 100644 index 00000000..7c41418c --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -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 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(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 GenerateClientSpeedtestConfig(List 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(result); + if (singboxConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + List lstIpEndPoints = new(); + List 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 { 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 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(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 GenerateClientMultipleLoadConfig(List 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(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(); + 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 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(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 +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs new file mode 100644 index 00000000..c6bec22b --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs @@ -0,0 +1,63 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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)); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs new file mode 100644 index 00000000..ef22d926 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -0,0 +1,482 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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>(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 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 { 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(); + 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 GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) + { + var finalDns = ParseDnsAddress(simpleDNSItem.SingboxFinalResolveDNS); + finalDns.tag = Global.SingboxFinalResolverTag; + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.servers ??= new List(); + singboxConfig.dns.servers.Add(finalDns); + return await Task.FromResult(finalDns); + } + + private async Task GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + { + singboxConfig.dns ??= new Dns4Sbox(); + singboxConfig.dns.rules ??= new List(); + + 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 { 64, 65 }, + action = "predefined", + rcode = "NOTIMP" + }); + } + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing == null) + return 0; + + var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + var expectedIPCidr = new List(); + var expectedIPsRegions = new List(); + var regionNames = new HashSet(); + + 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(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 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(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 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 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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs new file mode 100644 index 00000000..c79d5b42 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -0,0 +1,92 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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(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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs new file mode 100644 index 00000000..59e65471 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs @@ -0,0 +1,40 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs new file mode 100644 index 00000000..5c48c014 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -0,0 +1,577 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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 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 GenServer(ProfileItem node) + { + try + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (node.ConfigType == EConfigType.WireGuard) + { + var endpoint = JsonUtils.Deserialize(txtOutbound); + await GenEndpoint(node, endpoint); + return endpoint; + } + else + { + var outbound = JsonUtils.Deserialize(txtOutbound); + await GenOutbound(node, outbound); + return outbound; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return await Task.FromResult(null); + } + + private async Task 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 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 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 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 GenOutboundsList(List 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(); + var resultEndpoints = new List(); // For endpoints + var prevOutbounds = new List(); // Separate list for prev outbounds + var prevEndpoints = new List(); // Separate list for prev endpoints + var proxyTags = new List(); // For selector and urltest outbounds + + // Cache for chain proxies to avoid duplicate generation + var nextProxyCache = new Dictionary(); + var prevProxyTags = new Dictionary(); // 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(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(); + resultEndpoints.AddRange(singboxConfig.endpoints); + singboxConfig.endpoints = resultEndpoints; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return 0; + } + + private async Task 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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs new file mode 100644 index 00000000..69492d9b --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -0,0 +1,365 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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>(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(); + if (routing != null) + { + var rules = JsonUtils.Deserialize>(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 lstDnsExe, out List lstDirectExe) + { + var dnsExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); + var directExeSet = new HashSet(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(dnsExeSet); + lstDirectExe = new List(directExeSet); + } + + private async Task 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 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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs new file mode 100644 index 00000000..ef611c91 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs @@ -0,0 +1,119 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) + { + static void AddRuleSets(List ruleSets, List? rule_set) + { + if (rule_set != null) + ruleSets.AddRange(rule_set); + } + var geosite = "geosite"; + var geoip = "geoip"; + var ruleSets = new List(); + + //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(); + 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(); + 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(); + 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(); + 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 customRulesets = []; + + var routing = await ConfigHandler.GetDefaultRouting(_config); + if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) + { + var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); + if (result.IsNotEmpty()) + { + customRulesets = (JsonUtils.Deserialize>(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(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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs new file mode 100644 index 00000000..c3acd810 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs @@ -0,0 +1,29 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigSingboxService +{ + private async Task 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); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs new file mode 100644 index 00000000..7f1ada5d --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -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 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(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 GenerateClientMultipleLoadConfig(List 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(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(); + 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 GenerateClientSpeedtestConfig(List 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(result); + if (v2rayConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + List lstIpEndPoints = new(); + List 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(txtOutbound); + await GenOutbound(item, outbound); + outbound.tag = Global.ProxyTag + inbound.port.ToString(); + v2rayConfig.outbounds.Add(outbound); + + //rule + RulesItem4Ray rule = new() + { + inboundTag = new List { 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 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(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 +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs new file mode 100644 index 00000000..8d2476e6 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs @@ -0,0 +1,50 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs new file mode 100644 index 00000000..5d1f7d63 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs @@ -0,0 +1,86 @@ +using System.Text.Json.Nodes; + +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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() == 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)); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs new file mode 100644 index 00000000..ddb47c99 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -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 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 { 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 { Global.DnsTag }, + outboundTag = Global.ProxyTag, + }); + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem) + { + static List 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 { defaultAddress }; + return addresses.Count > 0 ? addresses : new List { defaultAddress }; + } + + static object CreateDnsServer(string dnsAddress, List domains, List? 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(); + var directGeositeList = new List(); + var proxyDomainList = new List(); + var proxyGeositeList = new List(); + var expectedDomainList = new List(); + var expectedIPs = new List(); + var regionNames = new HashSet(); + + 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? rules = null; + if (routing != null) + { + rules = JsonUtils.Deserialize>(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(); + + void AddDnsServers(List dnsAddresses, List domains, List? 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 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(); + 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 { 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 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 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(out var ip)) + { + normalHost[hostProp.Key] = new JsonArray(ip); + } + } + } + + await GenDnsDomainsCompatible(node, obj, item); + + v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dNSItem) + { + if (node == null) + { + return 0; + } + var servers = dns["servers"]; + if (servers != null) + { + var domainList = new List(); + 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); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs new file mode 100644 index 00000000..7753c21e --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -0,0 +1,72 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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 { 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(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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs new file mode 100644 index 00000000..5b9344fb --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs @@ -0,0 +1,29 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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); + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs new file mode 100644 index 00000000..a40f2d73 --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -0,0 +1,704 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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() { 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 { 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 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 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(request); + + streamSettings.tcpSettings = tcpSettings; + } + break; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task 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(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 GenOutboundsList(List nodes, V2rayConfig v2rayConfig) + { + try + { + // Get template and initialize list + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + if (txtOutbound.IsNullOrEmpty()) + { + return 0; + } + + var resultOutbounds = new List(); + var prevOutbounds = new List(); // Separate list for prev outbounds and fragment + + // Cache for chain proxies to avoid duplicate generation + var nextProxyCache = new Dictionary(); + var prevProxyTags = new Dictionary(); // 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(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(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; + } + + /// + /// 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. + /// + /// The subscription item containing proxy chain information. + /// The current outbound configuration. Its tag must be set before calling this method. + /// The tag of the previous outbound in the chain, if any. + /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. + /// + /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. + /// + private async Task 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(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; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs new file mode 100644 index 00000000..fe5d64dc --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs @@ -0,0 +1,145 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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>(routing.RuleSet); + foreach (var item in rules) + { + if (item.Enabled) + { + var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); + await GenRoutingUserRule(item2, v2rayConfig); + } + } + } + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + return 0; + } + + private async Task 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 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(txtOutbound); + await GenOutbound(node, outbound); + outbound.tag = Global.ProxyTag + node.IndexId.ToString(); + v2rayConfig.outbounds.Add(outbound); + + return outbound.tag; + } +} diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs new file mode 100644 index 00000000..1269a11f --- /dev/null +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs @@ -0,0 +1,51 @@ +namespace ServiceLib.Services.CoreConfig; + +public partial class CoreConfigV2rayService +{ + private async Task 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 { tag }, + outboundTag = tag, + type = "field" + }; + + v2rayConfig.routing.rules.Add(apiRoutingRule); + } + } + return await Task.FromResult(0); + } +}