From 183be5315345ba4f1d8ed67e8444b3446fa7b51a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 11 Sep 2025 14:29:14 +0800 Subject: [PATCH] Add Proxy Chain support --- .../Singbox/CoreConfigSingboxService.cs | 116 ++++++++++++++++-- .../Singbox/SingboxOutboundService.cs | 48 ++++++++ .../V2ray/CoreConfigV2rayService.cs | 115 +++++++++++++++-- .../CoreConfig/V2ray/V2rayOutboundService.cs | 45 +++++++ 4 files changed, 308 insertions(+), 16 deletions(-) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index d06df2f0..0de88108 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -23,16 +23,23 @@ public partial class CoreConfigSingboxService(Config config) ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.ConfigType is EConfigType.PolicyGroup) + var childProfiles = (await Task.WhenAll( + Utils.String2List(profileGroupItem.ChildItems) + .Where(p => !p.IsNullOrEmpty()) + .Select(AppManager.Instance.GetProfileItem) + )).Where(p => p != null).ToList(); + if (childProfiles.Count <= 0) { - var childProfiles = (await Task.WhenAll( - Utils.String2List(profileGroupItem.ChildItems) - .Where(p => !p.IsNullOrEmpty()) - .Select(AppManager.Instance.GetProfileItem) - )).Where(p => p != null).ToList(); - return await GenerateClientMultipleLoadConfig(childProfiles); + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + switch (node.ConfigType) + { + case EConfigType.PolicyGroup: + return await GenerateClientMultipleLoadConfig(childProfiles); + case EConfigType.ProxyChain: + return await GenerateClientChainConfig(childProfiles); } - // TODO proxy chain } if (node == null @@ -457,6 +464,99 @@ public partial class CoreConfigSingboxService(Config config) } } + public async Task GenerateClientChainConfig(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 (!Global.SingboxSupportConfigType.Contains(it.ConfigType)) + { + 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 GenChainOutboundsList(proxyProfiles, singboxConfig); + + await GenDns(null, 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(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 3f03b93c..d2b93b6d 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -574,4 +574,52 @@ public partial class CoreConfigSingboxService } return null; } + + private async Task GenChainOutboundsList(List nodes, SingboxConfig singboxConfig) + { + var resultOutbounds = new List(); + var resultEndpoints = new List(); // For endpoints + for (var i = 0; i < nodes.Count; i++) + { + var node = nodes[i]; + var server = await GenServer(node); + + if (server is null) + { + break; + } + + if (i == 0) + { + server.tag = Global.ProxyTag; + } + else + { + server.tag = Global.ProxyTag + i.ToString(); + } + + if (i != nodes.Count - 1) + { + server.detour = Global.ProxyTag + (i + 1).ToString(); + } + + if (server is Endpoints4Sbox endpoint) + { + resultEndpoints.Add(endpoint); + } + else if (server is Outbound4Sbox outbound) + { + resultOutbounds.Add(outbound); + } + } + singboxConfig.outbounds ??= new(); + resultOutbounds.AddRange(singboxConfig.outbounds); + singboxConfig.outbounds = resultOutbounds; + + singboxConfig.endpoints ??= new(); + resultEndpoints.AddRange(singboxConfig.endpoints); + singboxConfig.endpoints = resultEndpoints; + + return await Task.FromResult(0); + } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index eecdec5f..fec48fb2 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -23,16 +23,23 @@ public partial class CoreConfigV2rayService(Config config) ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.ConfigType is EConfigType.PolicyGroup) + var childProfiles = (await Task.WhenAll( + Utils.String2List(profileGroupItem.ChildItems) + .Where(p => !p.IsNullOrEmpty()) + .Select(AppManager.Instance.GetProfileItem) + )).Where(p => p != null).ToList(); + if (childProfiles.Count <= 0) { - var childProfiles = (await Task.WhenAll( - Utils.String2List(profileGroupItem.ChildItems) - .Where(p => !p.IsNullOrEmpty()) - .Select(AppManager.Instance.GetProfileItem) - )).Where(p => p != null).ToList(); - return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad); + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + switch (node.ConfigType) + { + case EConfigType.PolicyGroup: + return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad); + case EConfigType.ProxyChain: + return await GenerateClientChainConfig(childProfiles); } - // TODO proxy chain } if (node == null @@ -217,6 +224,98 @@ public partial class CoreConfigV2rayService(Config config) } } + public async Task GenerateClientChainConfig(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.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 (!Global.XraySupportConfigType.Contains(it.ConfigType)) + { + 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 GenChainOutboundsList(proxyProfiles, v2rayConfig); + + 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(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 11e8a8fa..565ec587 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -692,4 +692,49 @@ public partial class CoreConfigV2rayService } return null; } + + private async Task GenChainOutboundsList(List nodes, V2rayConfig v2RayConfig) + { + var resultOutbounds = new List(); + for (var i = 0; i < nodes.Count; i++) + { + var node = nodes[i]; + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + if (txtOutbound.IsNullOrEmpty()) + { + break; + } + var outbound = JsonUtils.Deserialize(txtOutbound); + var result = await GenOutbound(node, outbound); + + if (result != 0) + { + break; + } + + if (i == 0) + { + outbound.tag = Global.ProxyTag; + } + else + { + outbound.tag = Global.ProxyTag + i.ToString(); + } + + if (i != nodes.Count - 1) + { + outbound.streamSettings.sockopt = new() + { + dialerProxy = Global.ProxyTag + (i + 1).ToString() + }; + } + + resultOutbounds.Add(outbound); + } + v2RayConfig.outbounds ??= new(); + resultOutbounds.AddRange(v2RayConfig.outbounds); + v2RayConfig.outbounds = resultOutbounds; + + return await Task.FromResult(0); + } }