From 2ab044b4fcf2d45e7a209024c000c0c7da86df4a Mon Sep 17 00:00:00 2001 From: DHR60 Date: Thu, 11 Sep 2025 15:24:12 +0800 Subject: [PATCH] Add fallback support --- v2rayN/ServiceLib/Enums/EMultipleLoad.cs | 1 + v2rayN/ServiceLib/Handler/ConfigHandler.cs | 10 +++++-- v2rayN/ServiceLib/Models/SingboxConfig.cs | 1 + v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 27 +++++++++++++++++++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 9 +++++++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 9 +++++++ v2rayN/ServiceLib/Resx/ResUI.resx | 9 +++++++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 9 +++++++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 9 +++++++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 9 +++++++ .../Singbox/CoreConfigSingboxService.cs | 6 ++--- .../Singbox/SingboxOutboundService.cs | 7 ++++- .../CoreConfig/V2ray/V2rayBalancerService.cs | 11 ++++++-- .../ViewModels/AddGroupServerViewModel.cs | 2 ++ .../ViewModels/ProfilesViewModel.cs | 10 +++++++ .../Views/AddGroupServerWindow.axaml.cs | 1 + .../v2rayN.Desktop/Views/ProfilesView.axaml | 1 + .../Views/ProfilesView.axaml.cs | 1 + .../v2rayN/Views/AddGroupServerWindow.xaml.cs | 1 + v2rayN/v2rayN/Views/ProfilesView.xaml | 4 +++ v2rayN/v2rayN/Views/ProfilesView.xaml.cs | 1 + 21 files changed, 130 insertions(+), 8 deletions(-) diff --git a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs index af42fb60..ef6a5ff9 100644 --- a/v2rayN/ServiceLib/Enums/EMultipleLoad.cs +++ b/v2rayN/ServiceLib/Enums/EMultipleLoad.cs @@ -3,6 +3,7 @@ namespace ServiceLib.Enums; public enum EMultipleLoad { LeastPing, + Fallback, Random, RoundRobin, LeastLoad diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 35b47269..c9e82a8b 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -1199,16 +1199,22 @@ public static class ConfigHandler { remark = multipleLoad switch { + EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing, + EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback, EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom, EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin, - EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing, EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad, _ => ResUI.menuGenGroupMultipleServerXrayRoundRobin, }; } else if (coreType == ECoreType.sing_box) { - remark = ResUI.menuGenGroupMultipleServerSingBoxLeastPing; + remark = multipleLoad switch + { + EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, + EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback, + _ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing, + }; } var profile = new ProfileItem { diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index a5eec4ae..8263924a 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -145,6 +145,7 @@ public class Outbound4Sbox : BaseServer4Sbox public string? plugin_opts { get; set; } public List? outbounds { get; set; } public bool? interrupt_exist_connections { get; set; } + public int? tolerance { get; set; } } public class Endpoints4Sbox : BaseServer4Sbox diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index ed9ff30e..483fb417 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -987,6 +987,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。 + /// + public static string menuGenGroupMultipleServerSingBoxFallback { + get { + return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture); + } + } + /// /// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。 /// @@ -996,6 +1005,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。 + /// + public static string menuGenGroupMultipleServerXrayFallback { + get { + return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture); + } + } + /// /// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。 /// @@ -2598,6 +2616,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Fallback 的本地化字符串。 + /// + public static string TbFallback { + get { + return ResourceManager.GetString("TbFallback", resourceCulture); + } + } + /// /// 查找类似 Fingerprint 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 298c6985..9893d47d 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1554,4 +1554,13 @@ Server List + + Fallback + + + Multi-Configuration Fallback by sing-box + + + Multi-Configuration Fallback by Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 2cf7079f..aa1a3b13 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1554,4 +1554,13 @@ Server List + + Fallback + + + Multi-Configuration Fallback by sing-box + + + Multi-Configuration Fallback by Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 1797529a..85875a90 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1554,4 +1554,13 @@ Server List + + Fallback + + + Multi-Configuration Fallback by sing-box + + + Multi-Configuration Fallback by Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index e8e49b1c..1dfa268e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1554,4 +1554,13 @@ Server List + + Fallback + + + Multi-Configuration Fallback by sing-box + + + Multi-Configuration Fallback by Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index b5e33956..516c2f4a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1551,4 +1551,13 @@ 服务器列表 + + 故障转移 + + + 多配置文件故障转移 sing-box + + + 多配置文件故障转移 Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index bf538e1a..c041de1f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1551,4 +1551,13 @@ Server List + + Fallback + + + Multi-Configuration Fallback by sing-box + + + Multi-Configuration Fallback by Xray + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 0de88108..f938bc8a 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -36,7 +36,7 @@ public partial class CoreConfigSingboxService(Config config) switch (node.ConfigType) { case EConfigType.PolicyGroup: - return await GenerateClientMultipleLoadConfig(childProfiles); + return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad); case EConfigType.ProxyChain: return await GenerateClientChainConfig(childProfiles); } @@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config) } } - public async Task GenerateClientMultipleLoadConfig(List selecteds) + public async Task GenerateClientMultipleLoadConfig(List selecteds, EMultipleLoad multipleLoad) { var ret = new RetResult(); try @@ -446,7 +446,7 @@ public partial class CoreConfigSingboxService(Config config) ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenOutboundsList(proxyProfiles, singboxConfig); + await GenOutboundsList(proxyProfiles, singboxConfig, multipleLoad); await GenDns(null, singboxConfig); await ConvertGeo2Ruleset(singboxConfig); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index d2b93b6d..701ca575 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -410,7 +410,7 @@ public partial class CoreConfigSingboxService return 0; } - private async Task GenOutboundsList(List nodes, SingboxConfig singboxConfig) + private async Task GenOutboundsList(List nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad) { try { @@ -513,6 +513,11 @@ public partial class CoreConfigSingboxService interrupt_exist_connections = false, }; + if (multipleLoad == EMultipleLoad.Fallback) + { + outUrltest.tolerance = 5000; + } + // Add selector outbound (manual selection) var outSelector = new Outbound4Sbox { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs index 8d2476e6..634c6f97 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs @@ -15,7 +15,7 @@ public partial class CoreConfigV2rayService }; v2rayConfig.observatory = observatory; } - else if (multipleLoad == EMultipleLoad.LeastLoad) + else if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback) { var burstObservatory = new BurstObservatory4Ray { @@ -41,7 +41,14 @@ public partial class CoreConfigV2rayService var balancer = new BalancersItem4Ray { selector = [Global.ProxyTag], - strategy = new() { type = strategyType }, + strategy = new() + { + type = strategyType, + settings = new() + { + expected = 1, + }, + }, tag = $"{Global.ProxyTag}-round", }; v2rayConfig.routing.balancers = [balancer]; diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index f215f5eb..b54dbe09 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -76,6 +76,7 @@ public class AddGroupServerViewModel : MyReactiveObject PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch { EMultipleLoad.LeastPing => ResUI.TbLeastPing, + EMultipleLoad.Fallback => ResUI.TbFallback, EMultipleLoad.Random => ResUI.TbRandom, EMultipleLoad.RoundRobin => ResUI.TbRoundRobin, EMultipleLoad.LeastLoad => ResUI.TbLeastLoad, @@ -206,6 +207,7 @@ public class AddGroupServerViewModel : MyReactiveObject profileGroup.MultipleLoad = PolicyGroupType switch { var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, + var s when s == ResUI.TbFallback => EMultipleLoad.Fallback, var s when s == ResUI.TbRandom => EMultipleLoad.Random, var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin, var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad, diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 7b73a926..20cc85ac 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -56,7 +56,9 @@ public class ProfilesViewModel : MyReactiveObject public ReactiveCommand GenGroupMultipleServerXrayRoundRobinCmd { get; } public ReactiveCommand GenGroupMultipleServerXrayLeastPingCmd { get; } public ReactiveCommand GenGroupMultipleServerXrayLeastLoadCmd { get; } + public ReactiveCommand GenGroupMultipleServerXrayFallbackCmd { get; } public ReactiveCommand GenGroupMultipleServerSingBoxLeastPingCmd { get; } + public ReactiveCommand GenGroupMultipleServerSingBoxFallbackCmd { get; } //servers move public ReactiveCommand MoveTopCmd { get; } @@ -154,10 +156,18 @@ public class ProfilesViewModel : MyReactiveObject { await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); }, canEditRemove); + GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () => + { + await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback); + }, canEditRemove); GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => { await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); }, canEditRemove); + GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () => + { + await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback); + }, canEditRemove); //servers move MoveTopCmd = ReactiveCommand.CreateFromTask(async () => diff --git a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs index 7e375235..ede6bc71 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml.cs @@ -29,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase cmbPolicyGroupType.ItemsSource = new List { ResUI.TbLeastPing, + ResUI.TbFallback, ResUI.TbRandom, ResUI.TbRoundRobin, ResUI.TbLeastLoad, diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml index 73c3ed95..89bcbb28 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml @@ -106,6 +106,7 @@ + diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs index cde2d3ff..c6c16c86 100644 --- a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -71,6 +71,7 @@ public partial class ProfilesView : ReactiveUserControl this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); //servers move //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs index 921232ec..839d6c4c 100644 --- a/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml.cs @@ -24,6 +24,7 @@ public partial class AddGroupServerWindow cmbPolicyGroupType.ItemsSource = new List { ResUI.TbLeastPing, + ResUI.TbFallback, ResUI.TbRandom, ResUI.TbRoundRobin, ResUI.TbLeastLoad, diff --git a/v2rayN/v2rayN/Views/ProfilesView.xaml b/v2rayN/v2rayN/Views/ProfilesView.xaml index 547e69b0..e81ee068 100644 --- a/v2rayN/v2rayN/Views/ProfilesView.xaml +++ b/v2rayN/v2rayN/Views/ProfilesView.xaml @@ -147,6 +147,10 @@ x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Height="{StaticResource MenuItemHeight}" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" /> + vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables); //servers move this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);