mirror of
https://github.com/2dust/v2rayN.git
synced 2025-07-02 04:52:09 +00:00
Optimize proxy chain handling (#7515)
* Optimize proxy chain handling * Avoids duplicate proxy chain generation
This commit is contained in:
parent
30c09a7b54
commit
84f93f2ae6
2 changed files with 183 additions and 254 deletions
|
@ -918,29 +918,21 @@ public class CoreConfigSingboxService
|
||||||
|
|
||||||
//Previous proxy
|
//Previous proxy
|
||||||
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
string? prevOutboundTag = null;
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom)
|
&& prevNode.ConfigType != EConfigType.Custom)
|
||||||
{
|
{
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
prevOutbound.tag = $"{Global.ProxyTag}2";
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||||
|
prevOutbound.tag = prevOutboundTag;
|
||||||
singboxConfig.outbounds.Add(prevOutbound);
|
singboxConfig.outbounds.Add(prevOutbound);
|
||||||
|
|
||||||
outbound.detour = prevOutbound.tag;
|
|
||||||
}
|
}
|
||||||
|
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||||
|
|
||||||
//Next proxy
|
if (nextOutbound is not null)
|
||||||
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
|
||||||
if (nextNode is not null
|
|
||||||
&& nextNode.ConfigType != EConfigType.Custom)
|
|
||||||
{
|
{
|
||||||
var nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
|
||||||
await GenOutbound(nextNode, nextOutbound);
|
|
||||||
nextOutbound.tag = Global.ProxyTag;
|
|
||||||
singboxConfig.outbounds.Insert(0, nextOutbound);
|
singboxConfig.outbounds.Insert(0, nextOutbound);
|
||||||
|
|
||||||
outbound.tag = $"{Global.ProxyTag}1";
|
|
||||||
nextOutbound.detour = outbound.tag;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -967,8 +959,8 @@ public class CoreConfigSingboxService
|
||||||
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||||
|
|
||||||
// Cache for chain proxies to avoid duplicate generation
|
// Cache for chain proxies to avoid duplicate generation
|
||||||
var chainProxyCache = new Dictionary<string, (string?, Outbound4Sbox?)>();
|
var nextProxyCache = new Dictionary<string, Outbound4Sbox?>();
|
||||||
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
|
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||||
int prevIndex = 0; // Index for prev outbounds
|
int prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
// Process each node
|
// Process each node
|
||||||
|
@ -977,112 +969,55 @@ public class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
// Skip unsupported config types
|
|
||||||
if (node.ConfigType is EConfigType.Custom)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle proxy chain
|
// Handle proxy chain
|
||||||
string? prevTag = null;
|
string? prevTag = null;
|
||||||
Outbound4Sbox? nextOutbound = null;
|
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||||
if (node.Subid.IsNotEmpty())
|
if (nextOutbound != null)
|
||||||
{
|
{
|
||||||
// Check if chain proxy is already cached
|
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||||
if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy))
|
|
||||||
{
|
|
||||||
prevTag = chainProxy.Item1;
|
|
||||||
nextOutbound = chainProxy.Item2;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Generate chain proxy and cache it
|
|
||||||
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
|
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
|
||||||
if (subItem != null)
|
|
||||||
|
// current proxy
|
||||||
|
await GenOutbound(node, currentOutbound);
|
||||||
|
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||||
|
proxyTags.Add(currentOutbound.tag);
|
||||||
|
|
||||||
|
if (!node.Subid.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
// Process previous proxy
|
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||||
if (!subItem.PrevProfile.IsNullOrEmpty())
|
|
||||||
{
|
{
|
||||||
// Check if this previous proxy was already created
|
prevTag = value; // maybe null
|
||||||
if (prevProxyTags.TryGetValue(subItem.PrevProfile, out var existingTag))
|
|
||||||
{
|
|
||||||
prevTag = existingTag;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
if (prevNode != null && prevNode.ConfigType != EConfigType.Custom)
|
if (prevNode is not null
|
||||||
|
&& prevNode.ConfigType != EConfigType.Custom)
|
||||||
{
|
{
|
||||||
prevIndex++;
|
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||||
prevTag = $"{Global.ProxyTag}-prev-{prevIndex}";
|
|
||||||
prevOutbound.tag = prevTag;
|
prevOutbound.tag = prevTag;
|
||||||
prevProxyTags[subItem.PrevProfile] = prevTag;
|
|
||||||
|
|
||||||
// Add to prev outbounds list (will be added at the end)
|
|
||||||
prevOutbounds.Add(prevOutbound);
|
prevOutbounds.Add(prevOutbound);
|
||||||
}
|
}
|
||||||
}
|
prevProxyTags[node.Subid] = prevTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process next proxy
|
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
||||||
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||||
if (nextNode != null && nextNode.ConfigType != EConfigType.Custom)
|
|
||||||
{
|
{
|
||||||
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
nextProxyCache[node.Subid] = nextOutbound;
|
||||||
await GenOutbound(nextNode, nextOutbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the chain proxy
|
|
||||||
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create main outbound
|
if (nextOutbound is not null)
|
||||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
|
||||||
|
|
||||||
await GenOutbound(node, outbound);
|
|
||||||
outbound.tag = $"{Global.ProxyTag}-{index}";
|
|
||||||
|
|
||||||
// Configure proxy chain relationships
|
|
||||||
if (nextOutbound != null)
|
|
||||||
{
|
{
|
||||||
// If there's a next proxy, it should be the final outbound in the chain
|
resultOutbounds.Add(nextOutbound);
|
||||||
var originalTag = outbound.tag;
|
|
||||||
outbound.tag = $"mid-{Global.ProxyTag}-{index}";
|
|
||||||
|
|
||||||
var nextOutboundCopy = JsonUtils.DeepCopy(nextOutbound);
|
|
||||||
nextOutboundCopy.tag = originalTag;
|
|
||||||
nextOutboundCopy.detour = outbound.tag; // Use detour instead of sockopt
|
|
||||||
|
|
||||||
if (prevTag != null)
|
|
||||||
{
|
|
||||||
outbound.detour = prevTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to proxy tags for selector/urltest
|
|
||||||
proxyTags.Add(originalTag);
|
|
||||||
|
|
||||||
// Add in reverse order to ensure final outbound is added first
|
|
||||||
resultOutbounds.Add(nextOutboundCopy); // Final outbound (exposed to internet)
|
|
||||||
resultOutbounds.Add(outbound); // Middle outbound
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If no next proxy, the main outbound is the final one
|
|
||||||
if (prevTag != null)
|
|
||||||
{
|
|
||||||
outbound.detour = prevTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to proxy tags for selector/urltest
|
|
||||||
proxyTags.Add(outbound.tag);
|
|
||||||
resultOutbounds.Add(outbound);
|
|
||||||
}
|
}
|
||||||
|
resultOutbounds.Add(currentOutbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add urltest outbound (auto selection based on latency)
|
// Add urltest outbound (auto selection based on latency)
|
||||||
|
@ -1124,6 +1059,53 @@ public class CoreConfigSingboxService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a chained outbound configuration for the given subItem and outbound.
|
||||||
|
/// The outbound's tag must be set before calling this method.
|
||||||
|
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||||
|
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||||
|
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||||
|
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||||
|
/// </returns>
|
||||||
|
private async Task<Outbound4Sbox?> GenChainOutbounds(SubItem subItem, Outbound4Sbox outbound, string? prevOutboundTag, Outbound4Sbox? nextOutbound = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
|
||||||
|
if (!prevOutboundTag.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
outbound.detour = prevOutboundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next proxy
|
||||||
|
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode is not null
|
||||||
|
&& nextNode.ConfigType != EConfigType.Custom)
|
||||||
|
{
|
||||||
|
if (nextOutbound == null)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
await GenOutbound(nextNode, nextOutbound);
|
||||||
|
}
|
||||||
|
nextOutbound.tag = outbound.tag;
|
||||||
|
|
||||||
|
outbound.tag = $"mid-{outbound.tag}";
|
||||||
|
nextOutbound.detour = outbound.tag;
|
||||||
|
}
|
||||||
|
return nextOutbound;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -1318,6 +1318,7 @@ public class CoreConfigV2rayService
|
||||||
|
|
||||||
//Previous proxy
|
//Previous proxy
|
||||||
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
string? prevOutboundTag = null;
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom
|
&& prevNode.ConfigType != EConfigType.Custom
|
||||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||||
|
@ -1325,32 +1326,15 @@ public class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
prevOutbound.tag = $"{Global.ProxyTag}2";
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||||
|
prevOutbound.tag = prevOutboundTag;
|
||||||
v2rayConfig.outbounds.Add(prevOutbound);
|
v2rayConfig.outbounds.Add(prevOutbound);
|
||||||
|
|
||||||
outbound.streamSettings.sockopt = new()
|
|
||||||
{
|
|
||||||
dialerProxy = prevOutbound.tag
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag);
|
||||||
|
|
||||||
//Next proxy
|
if (nextOutbound is not null)
|
||||||
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
|
||||||
if (nextNode is not null
|
|
||||||
&& nextNode.ConfigType != EConfigType.Custom
|
|
||||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& nextNode.ConfigType != EConfigType.TUIC)
|
|
||||||
{
|
{
|
||||||
var nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
||||||
await GenOutbound(nextNode, nextOutbound);
|
|
||||||
nextOutbound.tag = Global.ProxyTag;
|
|
||||||
v2rayConfig.outbounds.Insert(0, nextOutbound);
|
v2rayConfig.outbounds.Insert(0, nextOutbound);
|
||||||
|
|
||||||
outbound.tag = $"{Global.ProxyTag}1";
|
|
||||||
nextOutbound.streamSettings.sockopt = new()
|
|
||||||
{
|
|
||||||
dialerProxy = outbound.tag
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -1375,31 +1359,9 @@ public class CoreConfigV2rayService
|
||||||
var resultOutbounds = new List<Outbounds4Ray>();
|
var resultOutbounds = new List<Outbounds4Ray>();
|
||||||
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
var prevOutbounds = new List<Outbounds4Ray>(); // Separate list for prev outbounds and fragment
|
||||||
|
|
||||||
// Handle fragment outbound
|
|
||||||
Outbounds4Ray? fragmentOutbound = null;
|
|
||||||
if (_config.CoreBasicItem.EnableFragment)
|
|
||||||
{
|
|
||||||
fragmentOutbound = new Outbounds4Ray
|
|
||||||
{
|
|
||||||
protocol = "freedom",
|
|
||||||
tag = $"fragment-{Global.ProxyTag}",
|
|
||||||
settings = new()
|
|
||||||
{
|
|
||||||
fragment = new()
|
|
||||||
{
|
|
||||||
packets = _config.Fragment4RayItem?.Packets,
|
|
||||||
length = _config.Fragment4RayItem?.Length,
|
|
||||||
interval = _config.Fragment4RayItem?.Interval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Add to prevOutbounds instead of v2rayConfig.outbounds
|
|
||||||
prevOutbounds.Add(fragmentOutbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache for chain proxies to avoid duplicate generation
|
// Cache for chain proxies to avoid duplicate generation
|
||||||
var chainProxyCache = new Dictionary<string, (string?, Outbounds4Ray?)>();
|
var nextProxyCache = new Dictionary<string, Outbounds4Ray?>();
|
||||||
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
|
var prevProxyTags = new Dictionary<string, string?>(); // Map from profile name to tag
|
||||||
int prevIndex = 0; // Index for prev outbounds
|
int prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
// Process nodes
|
// Process nodes
|
||||||
|
@ -1408,126 +1370,56 @@ public class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
// Skip unsupported config types
|
|
||||||
if (node.ConfigType is EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle proxy chain
|
// Handle proxy chain
|
||||||
string? prevTag = null;
|
string? prevTag = null;
|
||||||
Outbounds4Ray? nextOutbound = null;
|
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null);
|
||||||
|
if (nextOutbound != null)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.DeepCopy(nextOutbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
|
||||||
|
|
||||||
|
// current proxy
|
||||||
|
await GenOutbound(node, currentOutbound);
|
||||||
|
currentOutbound.tag = $"{Global.ProxyTag}-{index}";
|
||||||
|
|
||||||
if (!node.Subid.IsNullOrEmpty())
|
if (!node.Subid.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
// Check if chain proxy is already cached
|
if (prevProxyTags.TryGetValue(node.Subid, out var value))
|
||||||
if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy))
|
|
||||||
{
|
{
|
||||||
prevTag = chainProxy.Item1;
|
prevTag = value; // maybe null
|
||||||
nextOutbound = chainProxy.Item2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Generate chain proxy and cache it
|
|
||||||
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
|
|
||||||
if (subItem != null)
|
|
||||||
{
|
|
||||||
// Process previous proxy
|
|
||||||
if (!subItem.PrevProfile.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
// Check if this previous proxy was already created
|
|
||||||
if (prevProxyTags.TryGetValue(subItem.PrevProfile, out var existingTag))
|
|
||||||
{
|
|
||||||
prevTag = existingTag;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
if (prevNode != null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom
|
&& prevNode.ConfigType != EConfigType.Custom
|
||||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||||
&& prevNode.ConfigType != EConfigType.TUIC)
|
&& prevNode.ConfigType != EConfigType.TUIC)
|
||||||
{
|
{
|
||||||
prevIndex++;
|
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
prevTag = $"prev-{Global.ProxyTag}-{++prevIndex}";
|
||||||
prevTag = $"{Global.ProxyTag}-prev-{prevIndex}";
|
|
||||||
prevOutbound.tag = prevTag;
|
prevOutbound.tag = prevTag;
|
||||||
prevProxyTags[subItem.PrevProfile] = prevTag;
|
|
||||||
|
|
||||||
// Set fragment if needed
|
|
||||||
if (fragmentOutbound != null && prevOutbound.streamSettings?.security.IsNullOrEmpty() == false)
|
|
||||||
{
|
|
||||||
prevOutbound.streamSettings.sockopt = new()
|
|
||||||
{
|
|
||||||
dialerProxy = fragmentOutbound.tag
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to prev outbounds list (will be added at the end)
|
|
||||||
prevOutbounds.Add(prevOutbound);
|
prevOutbounds.Add(prevOutbound);
|
||||||
}
|
}
|
||||||
}
|
prevProxyTags[node.Subid] = prevTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process next proxy
|
nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound);
|
||||||
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
if (!nextProxyCache.ContainsKey(node.Subid))
|
||||||
if (nextNode != null
|
|
||||||
&& nextNode.ConfigType != EConfigType.Custom
|
|
||||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& nextNode.ConfigType != EConfigType.TUIC)
|
|
||||||
{
|
{
|
||||||
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
nextProxyCache[node.Subid] = nextOutbound;
|
||||||
await GenOutbound(nextNode, nextOutbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the chain proxy
|
|
||||||
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create main outbound
|
if (nextOutbound is not null)
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
|
||||||
|
|
||||||
await GenOutbound(node, outbound);
|
|
||||||
outbound.tag = $"{Global.ProxyTag}-{index}";
|
|
||||||
|
|
||||||
// Configure proxy chain relationships
|
|
||||||
if (nextOutbound != null)
|
|
||||||
{
|
{
|
||||||
// If there's a next proxy, it should be the final outbound in the chain
|
resultOutbounds.Add(nextOutbound);
|
||||||
var originalTag = outbound.tag;
|
|
||||||
outbound.tag = $"mid-{Global.ProxyTag}-{index}";
|
|
||||||
|
|
||||||
var nextOutboundCopy = JsonUtils.DeepCopy(nextOutbound);
|
|
||||||
nextOutboundCopy.tag = originalTag;
|
|
||||||
nextOutboundCopy.streamSettings.sockopt = new() { dialerProxy = outbound.tag };
|
|
||||||
|
|
||||||
if (prevTag != null)
|
|
||||||
{
|
|
||||||
outbound.streamSettings.sockopt = new() { dialerProxy = prevTag };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add in reverse order to ensure final outbound is added first
|
|
||||||
resultOutbounds.Add(nextOutboundCopy); // Final outbound (exposed to internet)
|
|
||||||
resultOutbounds.Add(outbound); // Middle outbound
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If no next proxy, the main outbound is the final one
|
|
||||||
if (prevTag != null)
|
|
||||||
{
|
|
||||||
outbound.streamSettings.sockopt = new() { dialerProxy = prevTag };
|
|
||||||
}
|
|
||||||
else if (fragmentOutbound != null && outbound.streamSettings?.security.IsNullOrEmpty() == false)
|
|
||||||
{
|
|
||||||
outbound.streamSettings.sockopt = new() { dialerProxy = fragmentOutbound.tag };
|
|
||||||
}
|
|
||||||
|
|
||||||
resultOutbounds.Add(outbound);
|
|
||||||
}
|
}
|
||||||
|
resultOutbounds.Add(currentOutbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
||||||
|
@ -1543,6 +1435,61 @@ public class CoreConfigV2rayService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a chained outbound configuration for the given subItem and outbound.
|
||||||
|
/// The outbound's tag must be set before calling this method.
|
||||||
|
/// Returns the next proxy's outbound configuration, which may be null if no next proxy exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subItem">The subscription item containing proxy chain information.</param>
|
||||||
|
/// <param name="outbound">The current outbound configuration. Its tag must be set before calling this method.</param>
|
||||||
|
/// <param name="prevOutboundTag">The tag of the previous outbound in the chain, if any.</param>
|
||||||
|
/// <param name="nextOutbound">The outbound for the next proxy in the chain, if already created. If null, will be created inside.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The outbound configuration for the next proxy in the chain, or null if no next proxy exists.
|
||||||
|
/// </returns>
|
||||||
|
private async Task<Outbounds4Ray?> GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
|
||||||
|
if (!prevOutboundTag.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
outbound.streamSettings.sockopt = new()
|
||||||
|
{
|
||||||
|
dialerProxy = prevOutboundTag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next proxy
|
||||||
|
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode is not null
|
||||||
|
&& nextNode.ConfigType != EConfigType.Custom
|
||||||
|
&& nextNode.ConfigType != EConfigType.Hysteria2
|
||||||
|
&& nextNode.ConfigType != EConfigType.TUIC)
|
||||||
|
{
|
||||||
|
if (nextOutbound == null)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(nextNode, nextOutbound);
|
||||||
|
}
|
||||||
|
nextOutbound.tag = outbound.tag;
|
||||||
|
|
||||||
|
outbound.tag = $"mid-{outbound.tag}";
|
||||||
|
nextOutbound.streamSettings.sockopt = new()
|
||||||
|
{
|
||||||
|
dialerProxy = outbound.tag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return nextOutbound;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
|
||||||
{
|
{
|
||||||
if (multipleLoad == EMultipleLoad.LeastPing)
|
if (multipleLoad == EMultipleLoad.LeastPing)
|
||||||
|
|
Loading…
Reference in a new issue