diff --git a/v2rayN/v2rayN/Handler/ConfigHandler.cs b/v2rayN/v2rayN/Handler/ConfigHandler.cs index 5b626885..eb18382f 100644 --- a/v2rayN/v2rayN/Handler/ConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/ConfigHandler.cs @@ -1066,11 +1066,11 @@ namespace v2rayN.Handler return 0; } - public static int AddCustomServer4Multiple(Config config, List selecteds, out string indexId) + public static int AddCustomServer4Multiple(Config config, List selecteds, ECoreType coreType, out string indexId) { indexId = Utils.GetMD5(Global.CoreMultipleLoadConfigFileName); string configPath = Utils.GetConfigPath(Global.CoreMultipleLoadConfigFileName); - if (CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, out string msg) != 0) + if (CoreConfigHandler.GenerateClientMultipleLoadConfig(config, configPath, selecteds, coreType, out string msg) != 0) { return -1; } @@ -1083,10 +1083,10 @@ namespace v2rayN.Handler var profileItem = LazyConfig.Instance.GetProfileItem(indexId) ?? new(); profileItem.indexId = indexId; - profileItem.remarks = "Multi-server Config"; + profileItem.remarks = coreType == ECoreType.sing_box ? Resx.ResUI.menuSetDefaultMultipleServer : Resx.ResUI.menuSetDefaultLoadBalanceServer; profileItem.address = Global.CoreMultipleLoadConfigFileName; profileItem.configType = EConfigType.Custom; - profileItem.coreType = ECoreType.sing_box; + profileItem.coreType = coreType; AddServerCommon(config, profileItem, true); diff --git a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs index e42f15af..b1b810dd 100644 --- a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs +++ b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigHandler.cs @@ -150,13 +150,25 @@ namespace v2rayN.Handler.CoreConfig return 0; } - public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List selecteds, out string msg) + public static int GenerateClientMultipleLoadConfig(Config config, string fileName, List selecteds, ECoreType coreType, out string msg) { - if (new CoreConfigSingbox(config).GenerateClientMultipleLoadConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0) + msg = ResUI.CheckServerSettings; + if (coreType == ECoreType.sing_box) { - return -1; + if (new CoreConfigSingbox(config).GenerateClientMultipleLoadConfig(selecteds, out SingboxConfig? singboxConfig, out msg) != 0) + { + return -1; + } + JsonUtils.ToFile(singboxConfig, fileName, false); + } + else if (coreType == ECoreType.Xray) + { + if (new CoreConfigV2ray(config).GenerateClientMultipleLoadConfig(selecteds, out V2rayConfig? v2rayConfig, out msg) != 0) + { + return -1; + } + JsonUtils.ToFile(v2rayConfig, fileName, false); } - JsonUtils.ToFile(singboxConfig, fileName, false); return 0; } diff --git a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigV2ray.cs b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigV2ray.cs index 3323e445..1ab89542 100644 --- a/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigV2ray.cs +++ b/v2rayN/v2rayN/Handler/CoreConfig/CoreConfigV2ray.cs @@ -71,6 +71,130 @@ namespace v2rayN.Handler.CoreConfig return 0; } + public int GenerateClientMultipleLoadConfig(List selecteds, out V2rayConfig? v2rayConfig, out string msg) + { + v2rayConfig = null; + try + { + if (_config == null) + { + msg = ResUI.CheckServerSettings; + return -1; + } + + msg = ResUI.InitialConfiguration; + + string result = Utils.GetEmbedText(Global.V2raySampleClient); + string txtOutbound = Utils.GetEmbedText(Global.V2raySampleOutbound); + if (Utils.IsNullOrEmpty(result) || txtOutbound.IsNullOrEmpty()) + { + msg = ResUI.FailedGetDefaultConfiguration; + return -1; + } + + v2rayConfig = JsonUtils.Deserialize(result); + if (v2rayConfig == null) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + GenLog(v2rayConfig); + GenInbounds(v2rayConfig); + GenRouting(v2rayConfig); + GenDns(null, v2rayConfig); + GenStatistic(v2rayConfig); + v2rayConfig.outbounds.RemoveAt(0); + + var tagProxy = new List(); + foreach (var it in selecteds) + { + if (it.configType == EConfigType.Custom) + { + continue; + } + if (it.configType is EConfigType.Hysteria2 or EConfigType.Tuic or EConfigType.Wireguard) + { + continue; + } + if (it.port <= 0) + { + continue; + } + var item = LazyConfig.Instance.GetProfileItem(it.indexId); + if (item is null) + { + continue; + } + if (it.configType is EConfigType.VMess or EConfigType.VLESS) + { + if (Utils.IsNullOrEmpty(item.id) || !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 + var outbound = JsonUtils.Deserialize(txtOutbound); + GenOutbound(item, outbound); + outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}"; + v2rayConfig.outbounds.Add(outbound); + tagProxy.Add(outbound.tag); + } + if (tagProxy.Count <= 0) + { + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + + //add balancers + var balancer = new BalancersItem4Ray + { + selector = [Global.ProxyTag], + strategy = new() { type = "roundRobin" }, + tag = $"{Global.ProxyTag}-round", + }; + v2rayConfig.routing.balancers = [balancer]; + + //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; + } + } + else + { + v2rayConfig.routing.rules.Add(new() + { + network = "tcp,udp", + balancerTag = balancer.tag, + type = "field" + }); + } + + return 0; + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + msg = ResUI.FailedGenDefaultConfiguration; + return -1; + } + } + public int GenerateClientSpeedtestConfig(List selecteds, out V2rayConfig? v2rayConfig, out string msg) { v2rayConfig = null; @@ -897,7 +1021,7 @@ namespace v2rayN.Handler.CoreConfig return 0; } - private int GenDns(ProfileItem node, V2rayConfig v2rayConfig) + private int GenDns(ProfileItem? node, V2rayConfig v2rayConfig) { try { @@ -960,8 +1084,10 @@ namespace v2rayN.Handler.CoreConfig return 0; } - private int GenDnsDomains(ProfileItem node, JsonNode dns) + private int GenDnsDomains(ProfileItem? node, JsonNode dns) { + if (node == null) + { return 0; } var servers = dns["servers"]; if (servers != null) { diff --git a/v2rayN/v2rayN/Models/ProfileItem.cs b/v2rayN/v2rayN/Models/ProfileItem.cs index aea244e8..8fb05bad 100644 --- a/v2rayN/v2rayN/Models/ProfileItem.cs +++ b/v2rayN/v2rayN/Models/ProfileItem.cs @@ -49,7 +49,7 @@ namespace v2rayN.Models switch (configType) { case EConfigType.Custom: - summary += string.Format("{0}", remarks); + summary += string.Format("[{1}]{0}", remarks, coreType.ToString()); break; default: diff --git a/v2rayN/v2rayN/Models/V2rayConfig.cs b/v2rayN/v2rayN/Models/V2rayConfig.cs index 750aea84..b86f4720 100644 --- a/v2rayN/v2rayN/Models/V2rayConfig.cs +++ b/v2rayN/v2rayN/Models/V2rayConfig.cs @@ -392,6 +392,8 @@ namespace v2rayN.Models /// /// public List rules { get; set; } + + public List? balancers { get; set; } } [Serializable] @@ -406,6 +408,8 @@ namespace v2rayN.Models public string? outboundTag { get; set; } + public string? balancerTag { get; set; } + public List? ip { get; set; } public List? domain { get; set; } @@ -413,6 +417,18 @@ namespace v2rayN.Models public List? protocol { get; set; } } + public class BalancersItem4Ray + { + public List? selector { get; set; } + public BalancersStrategy4Ray? strategy { get; set; } + public string? tag { get; set; } + } + + public class BalancersStrategy4Ray + { + public string? type { get; set; } + } + public class StreamSettings4Ray { /// diff --git a/v2rayN/v2rayN/Resx/ResUI.Designer.cs b/v2rayN/v2rayN/Resx/ResUI.Designer.cs index ee5ab7da..989cb301 100644 --- a/v2rayN/v2rayN/Resx/ResUI.Designer.cs +++ b/v2rayN/v2rayN/Resx/ResUI.Designer.cs @@ -1249,7 +1249,16 @@ namespace v2rayN.Resx { } /// - /// 查找类似 Multi-server set to active 的本地化字符串。 + /// 查找类似 Multi-server load balancing 的本地化字符串。 + /// + public static string menuSetDefaultLoadBalanceServer { + get { + return ResourceManager.GetString("menuSetDefaultLoadBalanceServer", resourceCulture); + } + } + + /// + /// 查找类似 Multi-Server Preferred Latency 的本地化字符串。 /// public static string menuSetDefaultMultipleServer { get { diff --git a/v2rayN/v2rayN/Resx/ResUI.resx b/v2rayN/v2rayN/Resx/ResUI.resx index 3808267e..94862dd1 100644 --- a/v2rayN/v2rayN/Resx/ResUI.resx +++ b/v2rayN/v2rayN/Resx/ResUI.resx @@ -1247,9 +1247,12 @@ Default domain strategy for outbound - Multi-server set to active + Multi-Server Preferred Latency Main layout orientation(Require restart) + + Multi-server load balancing + \ No newline at end of file diff --git a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx index e0f6e20b..b07937e5 100644 --- a/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/v2rayN/Resx/ResUI.zh-Hans.resx @@ -1244,9 +1244,12 @@ Outbound默认解析策略 - 多服务器设为活动(多选) + 多服务器优选延迟 (多选) 主界面布局方向(需重启) + + 多服务器负载均衡 (多选) + \ No newline at end of file diff --git a/v2rayN/v2rayN/ViewModels/ClashProxiesViewModel.cs b/v2rayN/v2rayN/ViewModels/ClashProxiesViewModel.cs index 22e34cc7..42242333 100644 --- a/v2rayN/v2rayN/ViewModels/ClashProxiesViewModel.cs +++ b/v2rayN/v2rayN/ViewModels/ClashProxiesViewModel.cs @@ -187,7 +187,7 @@ namespace v2rayN.ViewModels { ClashApiHandler.Instance.GetClashProxies(_config, (it, it2) => { - UpdateHandler(false, "Refresh Clash Proxies"); + //UpdateHandler(false, "Refresh Clash Proxies"); proxies = it?.proxies; providers = it2?.providers; @@ -413,7 +413,7 @@ namespace v2rayN.ViewModels private void ProxiesDelayTest(bool blAll) { - UpdateHandler(false, "Clash Proxies Latency Test"); + //UpdateHandler(false, "Clash Proxies Latency Test"); ClashApiHandler.Instance.ClashProxiesDelayTest(blAll, _proxyDetails.ToList(), (item, result) => { diff --git a/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs b/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs index 616f8314..bfd5365c 100644 --- a/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/v2rayN/ViewModels/ProfilesViewModel.cs @@ -74,6 +74,7 @@ namespace v2rayN.ViewModels public ReactiveCommand SetDefaultServerCmd { get; } public ReactiveCommand ShareServerCmd { get; } public ReactiveCommand SetDefaultMultipleServerCmd { get; } + public ReactiveCommand SetDefaultLoadBalanceServerCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -170,8 +171,13 @@ namespace v2rayN.ViewModels }, canEditRemove); SetDefaultMultipleServerCmd = ReactiveCommand.Create(() => { - SetDefaultMultipleServer(); + SetDefaultMultipleServer(ECoreType.sing_box); }, canEditRemove); + SetDefaultLoadBalanceServerCmd = ReactiveCommand.Create(() => + { + SetDefaultMultipleServer(ECoreType.Xray); + }, canEditRemove); + //servers move MoveTopCmd = ReactiveCommand.Create(() => { @@ -612,14 +618,14 @@ namespace v2rayN.ViewModels await DialogHost.Show(dialog, "RootDialog"); } - private void SetDefaultMultipleServer() + private void SetDefaultMultipleServer(ECoreType coreType) { if (GetProfileItems(out List lstSelecteds, true) < 0) { return; } - if (ConfigHandler.AddCustomServer4Multiple(_config, lstSelecteds, out string indexId) != 0) + if (ConfigHandler.AddCustomServer4Multiple(_config, lstSelecteds, coreType, out string indexId) != 0) { _noticeHandler?.Enqueue(ResUI.OperationFailed); return; diff --git a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml index cde13d4e..cbe4c8ee 100644 --- a/v2rayN/v2rayN/Views/ClashConnectionsView.xaml +++ b/v2rayN/v2rayN/Views/ClashConnectionsView.xaml @@ -29,7 +29,7 @@ @@ -55,7 +55,7 @@ Text="{x:Static resx:ResUI.TbAutoRefresh}" /> diff --git a/v2rayN/v2rayN/Views/ClashProxiesView.xaml b/v2rayN/v2rayN/Views/ClashProxiesView.xaml index efa973d1..7c7a6e36 100644 --- a/v2rayN/v2rayN/Views/ClashProxiesView.xaml +++ b/v2rayN/v2rayN/Views/ClashProxiesView.xaml @@ -34,7 +34,7 @@ @@ -49,7 +49,7 @@ @@ -81,7 +81,7 @@ Text="{x:Static resx:ResUI.TbAutoRefresh}" /> diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml index a1c357af..15051537 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -115,10 +115,16 @@ x:Name="menuShareServer" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuShareServer}" /> + + + vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SetDefaultLoadBalanceServerCmd, v => v.menuSetDefaultLoadBalanceServer).DisposeWith(disposables); //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);