Refactor Node Precheck (#8464)

This commit is contained in:
DHR60 2025-12-09 20:03:07 +08:00 committed by GitHub
parent 3885ff8b31
commit 7cee98887b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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,21 +70,64 @@ 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())
{ {
var groupErrors = await ValidateGroupNode(item, coreType);
errors.AddRange(groupErrors);
return errors;
}
else if (!item.IsComplex())
{
var normalErrors = await ValidateNormalNode(item, coreType);
errors.AddRange(normalErrors);
return errors;
}
return errors;
}
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
{
var errors = new List<string>();
if (item.Address.IsNullOrEmpty()) if (item.Address.IsNullOrEmpty())
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Address")); errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
return errors; return errors;
} }
if (item.Port is <= 0 or >= 65536) if (item.Port is <= 0 or > 65535)
{ {
errors.Add(string.Format(ResUI.InvalidProperty, "Port")); errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
return errors; return errors;
} }
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(transportError);
}
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)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
nameof(ECoreType.Xray), item.ConfigType.ToString()));
}
}
switch (item.ConfigType) switch (item.ConfigType)
{ {
case EConfigType.VMess: case EConfigType.VMess:
@ -123,21 +165,41 @@ public class ActionPrecheckManager(Config config)
break; break;
} }
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan if (item.StreamSecurity == Global.StreamSecurity)
&& item.StreamSecurity == Global.StreamSecurityReality {
&& item.PublicKey.IsNullOrEmpty()) // 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")); errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
} }
}
if (errors.Count > 0) 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"));
}
}
return errors; return errors;
} }
}
if (item.ConfigType.IsGroupType()) private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
{ {
var errors = new List<string>();
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group); ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
if (group is null || group.NotHasChild()) if (group is null || group.NotHasChild())
{ {
@ -178,36 +240,11 @@ public class ActionPrecheckManager(Config config)
} }
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
errors.AddRange(childErrors); errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
} }
return errors; return errors;
} }
var net = item.GetNetwork();
if (coreType == ECoreType.sing_box)
{
var transportError = ValidateSingboxTransport(item.ConfigType, net);
if (transportError != null)
{
errors.Add(transportError);
return errors;
}
}
else if (coreType is ECoreType.Xray)
{
// Xray core does not support these protocols
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
&& !item.IsComplex())
{
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
return errors;
}
}
return errors;
}
private static string? ValidateSingboxTransport(EConfigType configType, string net) private static string? ValidateSingboxTransport(EConfigType configType, string net)
{ {
// sing-box does not support xhttp / kcp transports // sing-box does not support xhttp / kcp transports
@ -271,7 +308,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 +326,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 +354,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;