Skip to content
Merged

Fix #8864

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali
public bool Success => ValidatorResult.Success;
}

/// <summary>
/// Holds the results of a full context build, including the main context and an optional
/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
/// </summary>
public record CoreConfigContextBuilderAllResult(
CoreConfigContextBuilderResult MainResult,
CoreConfigContextBuilderResult? PreSocksResult)
{
/// <summary>True only when both the main result and (if present) the pre-socks result succeeded.</summary>
public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);

/// <summary>
/// Merges all errors and warnings from the main result and the optional pre-socks result
/// into a single <see cref="NodeValidatorResult"/> for unified notification.
/// </summary>
public NodeValidatorResult CombinedValidatorResult => new(
[.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);

/// <summary>
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
/// from the pre-socks result (if any). Pass this to the core runner.
/// </summary>
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
? MainResult.Context with
{
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
}
: MainResult.Context;
}

public class CoreConfigContextBuilder
{
/// <summary>
Expand Down Expand Up @@ -75,6 +108,79 @@ public static async Task<CoreConfigContextBuilderResult> Build(Config config, Pr
return new CoreConfigContextBuilderResult(context, validatorResult);
}

/// <summary>
/// Builds the main <see cref="CoreConfigContext"/> for <paramref name="node"/> and, when
/// the main build succeeds, also builds the optional pre-socks context required for TUN
/// protection or pre-socks proxy chaining.
/// </summary>
public static async Task<CoreConfigContextBuilderAllResult> BuildAll(Config config, ProfileItem node)
{
var mainResult = await Build(config, node);
if (!mainResult.Success)
{
return new CoreConfigContextBuilderAllResult(mainResult, null);
}

var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
return new CoreConfigContextBuilderAllResult(mainResult, preResult);
}

/// <summary>
/// Determines whether a pre-socks context is required for <paramref name="nodeContext"/>
/// and, if so, builds and returns it. Returns <c>null</c> when no pre-socks core is needed.
/// </summary>
private static async Task<CoreConfigContextBuilderResult?> BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);

var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
return preSocksResult with
{
Context = preSocksResult.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
}
};
}

if (!nodeContext.IsTunEnabled
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}

var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preResult2 = await Build(nodeContext.AppConfig, preItem);
return preResult2 with
{
Context = preResult2.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
}
};
}

/// <summary>
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
/// Returns the effective (possibly replaced) node and the validation result.
Expand Down
41 changes: 0 additions & 41 deletions v2rayN/ServiceLib/Handler/ConfigHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1251,47 +1251,6 @@ public static async Task<RetResult> AddGroupServer4Multiple(Config config, List<
return itemSocks;
}

public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);

var preSocksItem = GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
return nodeContext with { Node = preSocksItem, };
}

if ((!nodeContext.IsTunEnabled)
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preContext = nodeContext with
{
Node = preItem,
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
};
return preContext;
}

/// <summary>
/// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists
Expand Down
22 changes: 7 additions & 15 deletions v2rayN/ServiceLib/Manager/CoreManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,19 @@ public async Task Init(Config config, Func<bool, string, Task> updateFunc)
}
}

public async Task LoadCore(CoreConfigContext? context)
/// <param name="mainContext">Resolved main context (with pre-socks ports already merged if applicable).</param>
/// <param name="preContext">Optional pre-socks context passed to <see cref="CoreStartPreService"/>.</param>
public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
if (context == null)
if (mainContext == null)
{
await UpdateFunc(false, ResUI.CheckServerSettings);
return;
}

var contextMod = context;
var node = contextMod.Node;
var node = mainContext.Node;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod);
if (preContext is not null)
{
contextMod = contextMod with
{
TunProtectSsPort = preContext.TunProtectSsPort,
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
};
}
var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName);
var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
if (result.Success != true)
{
await UpdateFunc(true, result.Msg);
Expand All @@ -96,7 +88,7 @@ public async Task LoadCore(CoreConfigContext? context)
await WindowsUtils.RemoveTunDevice();
}

await CoreStart(contextMod);
await CoreStart(mainContext);
await CoreStartPreService(preContext);
if (_processService != null)
{
Expand Down
21 changes: 21 additions & 0 deletions v2rayN/ServiceLib/Manager/NoticeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,25 @@ public void SendMessageAndEnqueue(string? msg)
Enqueue(msg);
SendMessage(msg);
}

/// <summary>
/// Sends each error and warning in <paramref name="validatorResult"/> to the message panel
/// and enqueues a summary snack notification (capped at 10 messages).
/// Returns <c>true</c> when there were any messages so the caller can decide on early-return
/// based on <see cref="NodeValidatorResult.Success"/>.
/// </summary>
public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
{
var msgs = new List<string>([.. validatorResult.Errors, .. validatorResult.Warnings]);
if (msgs.Count == 0)
{
return false;
}
foreach (var msg in msgs)
{
SendMessage(msg);
}
Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
return true;
}
}
21 changes: 6 additions & 15 deletions v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -546,24 +546,15 @@ public async Task Reload()
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}

await Task.Run(async () =>
{
await LoadCore(context);
await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
Expand Down Expand Up @@ -604,9 +595,9 @@ private void SetReloadEnabled(bool enabled)
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
}

private async Task LoadCore(CoreConfigContext? context)
private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{
await CoreManager.Instance.LoadCore(context);
await CoreManager.Instance.LoadCore(mainContext, preContext);
}

#endregion core job
Expand Down
26 changes: 4 additions & 22 deletions v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -789,18 +789,9 @@ private async Task Export2ClientConfigAsync(bool blClipboard)
}

var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}

if (blClipboard)
Expand Down Expand Up @@ -829,18 +820,9 @@ public async Task Export2ClientConfigResult(string fileName, ProfileItem item)
return;
}
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]);
if (msgs.Count > 0)
if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
{
foreach (var msg in msgs)
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
return;
}
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true)
Expand Down