diff --git a/v2rayN/ServiceLib/Common/Extension.cs b/v2rayN/ServiceLib/Common/Extension.cs index d36b8169..b6ee8154 100644 --- a/v2rayN/ServiceLib/Common/Extension.cs +++ b/v2rayN/ServiceLib/Common/Extension.cs @@ -6,17 +6,17 @@ public static class Extension { public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) { - return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value); - } - - public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value) - { - return string.IsNullOrWhiteSpace(value); + return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value); } public static bool IsNotEmpty([NotNullWhen(false)] this string? value) { - return !string.IsNullOrEmpty(value); + return !string.IsNullOrWhiteSpace(value); + } + + public static string? NullIfEmpty(this string? value) + { + return string.IsNullOrWhiteSpace(value) ? null : value; } public static bool BeginWithAny(this string s, IEnumerable chars) diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index f0a24028..2a4ba9a5 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -626,5 +626,12 @@ public class Global "" ]; + public static readonly List EchForceQuerys = + [ + "none", + "half", + "full", + ]; + #endregion const } diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 41f990b7..3718a73f 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -253,6 +253,8 @@ public static class ConfigHandler item.Extra = profileItem.Extra; item.MuxEnabled = profileItem.MuxEnabled; item.Cert = profileItem.Cert; + item.EchConfigList = profileItem.EchConfigList; + item.EchForceQuery = profileItem.EchForceQuery; } var ret = item.ConfigType switch diff --git a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index 3665afdf..34f30505 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -70,6 +70,10 @@ public class BaseFmt } ToUriQueryAllowInsecure(item, ref dicQuery); } + if (item.EchConfigList.IsNotEmpty()) + { + dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList)); + } dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); @@ -209,6 +213,7 @@ public class BaseFmt item.ShortId = GetQueryDecoded(query, "sid"); item.SpiderX = GetQueryDecoded(query, "spx"); item.Mldsa65Verify = GetQueryDecoded(query, "pqv"); + item.EchConfigList = GetQueryDecoded(query, "ech"); if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) { diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index 00ed38da..fa7b16ba 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -161,4 +161,6 @@ public class ProfileItem : ReactiveObject public string Extra { get; set; } public bool? MuxEnabled { get; set; } public string Cert { get; set; } + public string EchConfigList { get; set; } + public string EchForceQuery { get; set; } } diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index b84a6a66..2d9c1d2d 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -356,6 +356,8 @@ public class TlsSettings4Ray public string? mldsa65Verify { get; set; } public List? certificates { get; set; } public bool? disableSystemRoot { get; set; } + public string? echConfigList { get; set; } + public string? echForceQuery { get; set; } } public class CertificateSettings4Ray diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 64ca0292..d7c7b43e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -19,7 +19,7 @@ namespace ServiceLib.Resx { // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class ResUI { @@ -2790,6 +2790,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 EchConfigList 的本地化字符串。 + /// + public static string TbEchConfigList { + get { + return ResourceManager.GetString("TbEchConfigList", resourceCulture); + } + } + + /// + /// 查找类似 EchForceQuery 的本地化字符串。 + /// + public static string TbEchForceQuery { + get { + return ResourceManager.GetString("TbEchForceQuery", resourceCulture); + } + } + /// /// 查找类似 Edit 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 4f5467b3..47bae402 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration Item 2, Select and add from self-built + + EchConfigList + + + EchForceQuery + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index db2c6abf..a2c95286 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1638,4 +1638,10 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non Élément de config 2 : choisir et ajouter depuis self-hosted - + + EchConfigList + + + EchForceQuery + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 0841b111..9a1235da 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration Item 2, Select and add from self-built + + EchConfigList + + + EchForceQuery + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index ceb7c91e..569798da 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration Item 2, Select and add from self-built + + EchConfigList + + + EchForceQuery + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 33102733..6e1ee37c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration Item 2, Select and add from self-built + + EchConfigList + + + EchForceQuery + \ 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 e53c4203..d37cd62c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1638,4 +1638,10 @@ 子配置项二,从自建中选择添加 + + EchConfigList + + + EchForceQuery + \ 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 92421199..89edc2f3 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1638,4 +1638,10 @@ 子配置項二,從自建中選擇新增 + + EchConfigList + + + EchForceQuery + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index f6190ea9..c273eb57 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -157,13 +157,13 @@ public partial class CoreConfigSingboxService new Rule4Sbox { server = Global.SingboxRemoteDNSTag, - strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy, + strategy = simpleDNSItem.SingboxStrategy4Proxy.NullIfEmpty(), clash_mode = ERuleMode.Global.ToString() }, new Rule4Sbox { server = Global.SingboxDirectDNSTag, - strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct, + strategy = simpleDNSItem.SingboxStrategy4Direct.NullIfEmpty(), clash_mode = ERuleMode.Direct.ToString() } }); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 79b10176..50b1807f 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -354,7 +354,7 @@ public partial class CoreConfigSingboxService 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; + transport.path = node.Path.NullIfEmpty(); break; case nameof(ETransport.tcp): //http @@ -362,7 +362,7 @@ public partial class CoreConfigSingboxService { transport.type = nameof(ETransport.http); transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.IsNullOrEmpty() ? null : node.Path; + transport.path = node.Path.NullIfEmpty(); } break; @@ -396,7 +396,7 @@ public partial class CoreConfigSingboxService } } - transport.path = wsPath.IsNullOrEmpty() ? null : wsPath; + transport.path = wsPath.NullIfEmpty(); if (node.RequestHost.IsNotEmpty()) { transport.headers = new() @@ -408,8 +408,8 @@ public partial class CoreConfigSingboxService 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; + transport.path = node.Path.NullIfEmpty(); + transport.host = node.RequestHost.NullIfEmpty(); break; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index b77a227b..b9e1cc1f 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -113,7 +113,7 @@ public partial class CoreConfigSingboxService clash_mode = ERuleMode.Global.ToString() }); - var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox; + var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 6389f86a..d4285a09 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -272,7 +272,9 @@ public partial class CoreConfigV2rayService { 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 + fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, + echConfigList = node.EchConfigList.NullIfEmpty(), + echForceQuery = node.EchForceQuery.NullIfEmpty() }; if (sni.IsNotEmpty()) { @@ -340,7 +342,7 @@ public partial class CoreConfigV2rayService kcpSettings.header = new Header4Ray { type = node.HeaderType, - domain = host.IsNullOrEmpty() ? null : host + domain = host.NullIfEmpty() }; if (path.IsNotEmpty()) { @@ -450,7 +452,7 @@ public partial class CoreConfigV2rayService case nameof(ETransport.grpc): GrpcSettings4Ray grpcSettings = new() { - authority = host.IsNullOrEmpty() ? null : host, + authority = host.NullIfEmpty(), serviceName = path, multiMode = node.HeaderType == Global.GrpcMultiMode, idle_timeout = _config.GrpcItem.IdleTimeout, diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 9556f26a..e7a45a77 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -713,7 +713,7 @@ Grid.Row="7" ColumnDefinitions="300,Auto" IsVisible="False" - RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> - + + + + + + diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index fe37f6f5..d7799edc 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -28,6 +28,7 @@ public partial class AddServerWindow : WindowBase cmbFingerprint2.ItemsSource = Global.Fingerprints; cmbAllowInsecure.ItemsSource = Global.AllowInsecure; cmbAlpn.ItemsSource = Global.Alpns; + cmbEchForceQuery.ItemsSource = Global.EchForceQuerys; var lstStreamSecurity = new List(); lstStreamSecurity.Add(string.Empty); @@ -187,6 +188,9 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables); + //reality this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index 99778dd2..980496c8 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -929,6 +929,8 @@ + + @@ -1003,9 +1005,40 @@ Margin="{StaticResource Margin4}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" + Text="{x:Static resx:ResUI.TbEchConfigList}" /> + + + + + + diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index 2eadd06a..09a6b507 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -23,6 +23,7 @@ public partial class AddServerWindow cmbFingerprint2.ItemsSource = Global.Fingerprints; cmbAllowInsecure.ItemsSource = Global.AllowInsecure; cmbAlpn.ItemsSource = Global.Alpns; + cmbEchForceQuery.ItemsSource = Global.EchForceQuerys; var lstStreamSecurity = new List(); lstStreamSecurity.Add(string.Empty); @@ -182,6 +183,10 @@ public partial class AddServerWindow this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.EchForceQuery, v => v.cmbEchForceQuery.Text).DisposeWith(disposables); + //reality this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);