mirror of
https://github.com/2dust/v2rayN.git
synced 2025-12-10 05:19:42 +05:00
Compare commits
2 Commits
3885ff8b31
...
6e27dca6cd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e27dca6cd | ||
|
|
7cee98887b |
@@ -3,13 +3,11 @@ namespace ServiceLib.Manager;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActionPrecheckManager(Config config)
|
public class ActionPrecheckManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
|
private static readonly Lazy<ActionPrecheckManager> _instance = new();
|
||||||
public static ActionPrecheckManager Instance => _instance.Value;
|
public static ActionPrecheckManager Instance => _instance.Value;
|
||||||
|
|
||||||
private readonly Config _config = config;
|
|
||||||
|
|
||||||
// sing-box supported transports for different protocol types
|
// sing-box supported transports for different protocol types
|
||||||
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
|
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
|
||||||
|
|
||||||
@@ -56,6 +54,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
return await ValidateNodeAndCoreSupport(item, coreType);
|
return await ValidateNodeAndCoreSupport(item, coreType);
|
||||||
}
|
}
|
||||||
@@ -71,115 +70,35 @@ public class ActionPrecheckManager(Config config)
|
|||||||
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
else if (item.ConfigType.IsGroupType())
|
||||||
if (!item.IsComplex())
|
|
||||||
{
|
{
|
||||||
if (item.Address.IsNullOrEmpty())
|
var groupErrors = await ValidateGroupNode(item, coreType);
|
||||||
{
|
errors.AddRange(groupErrors);
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
return errors;
|
||||||
return errors;
|
}
|
||||||
}
|
else if (!item.IsComplex())
|
||||||
|
{
|
||||||
if (item.Port is <= 0 or >= 65536)
|
var normalErrors = await ValidateNormalNode(item, coreType);
|
||||||
{
|
errors.AddRange(normalErrors);
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
return errors;
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (item.ConfigType)
|
|
||||||
{
|
|
||||||
case EConfigType.VMess:
|
|
||||||
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EConfigType.VLESS:
|
|
||||||
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Global.Flows.Contains(item.Flow))
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EConfigType.Shadowsocks:
|
|
||||||
if (item.Id.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
|
||||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
|
||||||
&& item.PublicKey.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.Count > 0)
|
|
||||||
{
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ConfigType.IsGroupType())
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
if (item.Address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
||||||
if (group is null || group.NotHasChild())
|
return errors;
|
||||||
{
|
}
|
||||||
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
|
if (item.Port is <= 0 or > 65535)
|
||||||
if (hasCycle)
|
{
|
||||||
{
|
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
||||||
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
var childIds = Utils.String2List(group.ChildItems) ?? [];
|
|
||||||
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
|
|
||||||
childIds.AddRange(subItems.Select(p => p.IndexId));
|
|
||||||
|
|
||||||
foreach (var child in childIds)
|
|
||||||
{
|
|
||||||
var childErrors = new List<string>();
|
|
||||||
if (child.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var childItem = await AppManager.Instance.GetProfileItem(child);
|
|
||||||
if (childItem is null)
|
|
||||||
{
|
|
||||||
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
|
|
||||||
{
|
|
||||||
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
|
||||||
errors.AddRange(childErrors);
|
|
||||||
}
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,20 +110,151 @@ public class ActionPrecheckManager(Config config)
|
|||||||
if (transportError != null)
|
if (transportError != null)
|
||||||
{
|
{
|
||||||
errors.Add(transportError);
|
errors.Add(transportError);
|
||||||
return errors;
|
}
|
||||||
|
|
||||||
|
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
|
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (coreType is ECoreType.Xray)
|
else if (coreType is ECoreType.Xray)
|
||||||
{
|
{
|
||||||
// Xray core does not support these protocols
|
// Xray core does not support these protocols
|
||||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
|
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
|
||||||
&& !item.IsComplex())
|
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
return errors;
|
nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (item.ConfigType)
|
||||||
|
{
|
||||||
|
case EConfigType.VMess:
|
||||||
|
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.VLESS:
|
||||||
|
if (item.Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Id) && item.Id.Length > 30))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Global.Flows.Contains(item.Flow))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Flow"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EConfigType.Shadowsocks:
|
||||||
|
if (item.Id.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(item.Security) || !Global.SsSecuritiesInSingbox.Contains(item.Security))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "Security"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
// check certificate validity
|
||||||
|
if ((!item.Cert.IsNullOrEmpty()) && (CertPemManager.ParsePemChain(item.Cert).Count == 0))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||||
|
{
|
||||||
|
if (item.PublicKey.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Network == nameof(ETransport.xhttp)
|
||||||
|
&& !item.Extra.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
// check xhttp extra json validity
|
||||||
|
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
|
||||||
|
if (xhttpExtra is null)
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ws with tls, tls alpn should contain "http/1.1" in xray core
|
||||||
|
// rfc6455
|
||||||
|
// https://github.com/XTLS/Xray-core/blob/81f8f398c7b2b845853b1e85087c6122acc6db0b/transport/internet/tls/tls.go#L95-L116
|
||||||
|
if (item.Network == nameof(ETransport.ws)
|
||||||
|
&& item.StreamSecurity == Global.StreamSecurity)
|
||||||
|
{
|
||||||
|
var alpnList = Utils.String2List(item.Alpn) ?? [];
|
||||||
|
if (alpnList.Count > 0 && !alpnList.Contains("http/1.1"))
|
||||||
|
{
|
||||||
|
errors.Add(ResUI.AlpnMustContainHttp11ForWsTls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
|
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
||||||
|
if (group is null || group.NotHasChild())
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.GroupEmpty, item.Remarks));
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasCycle = ProfileGroupItemManager.HasCycle(item.IndexId);
|
||||||
|
if (hasCycle)
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks));
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childIds = Utils.String2List(group.ChildItems) ?? [];
|
||||||
|
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
|
||||||
|
childIds.AddRange(subItems.Select(p => p.IndexId));
|
||||||
|
|
||||||
|
foreach (var child in childIds)
|
||||||
|
{
|
||||||
|
var childErrors = new List<string>();
|
||||||
|
if (child.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childItem = await AppManager.Instance.GetProfileItem(child);
|
||||||
|
if (childItem is null)
|
||||||
|
{
|
||||||
|
childErrors.Add(string.Format(ResUI.NodeTagNotExist, child));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain)
|
||||||
|
{
|
||||||
|
childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
||||||
|
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
|
||||||
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +321,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
if (node is not null)
|
if (node is not null)
|
||||||
{
|
{
|
||||||
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
||||||
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
|
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
else if (tag.IsNotEmpty())
|
else if (tag.IsNotEmpty())
|
||||||
{
|
{
|
||||||
@@ -289,7 +339,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
{
|
{
|
||||||
return errors;
|
return errors;
|
||||||
@@ -317,7 +367,7 @@ public class ActionPrecheckManager(Config config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
||||||
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
|
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|||||||
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
11
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
|
|||||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
// (以 /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.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class ResUI {
|
public class ResUI {
|
||||||
@@ -78,6 +78,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 ALPN must contain 'http/1.1' when using WebSocket with TLS. 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string AlpnMustContainHttp11ForWsTls {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。
|
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1638,4 +1638,7 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1641,4 +1641,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1638,4 +1638,7 @@
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>子配置项二,从自建中选择添加</value>
|
<value>子配置项二,从自建中选择添加</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>使用 WebSocket+TLS 时,ALPN 必须包含 'http/1.1'。</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1638,4 +1638,7 @@
|
|||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>子配置項二,從自建中選擇新增</value>
|
<value>子配置項二,從自建中選擇新增</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve">
|
||||||
|
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Reference in New Issue
Block a user