Optimize proxy chain handling

This commit is contained in:
DHR60 2025-06-30 14:02:56 +08:00
parent 30c09a7b54
commit c6757ec122
2 changed files with 157 additions and 253 deletions

View file

@ -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,7 @@ 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 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 +968,46 @@ 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;
var currentOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
Outbound4Sbox? nextOutbound = null; Outbound4Sbox? nextOutbound = null;
if (node.Subid.IsNotEmpty())
{
// Check if chain proxy is already cached
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}";
if (!node.Subid.IsNullOrEmpty())
{ {
// Process previous proxy if (prevProxyTags.ContainsKey(node.Subid))
if (!subItem.PrevProfile.IsNullOrEmpty())
{ {
// Check if this previous proxy was already created prevTag = prevProxyTags[node.Subid]; // 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);
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); }
if (nextNode != null && nextNode.ConfigType != EConfigType.Custom)
if (nextOutbound is not null)
{ {
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound); resultOutbounds.Add(nextOutbound);
await GenOutbound(nextNode, nextOutbound);
}
// Cache the chain proxy
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
}
}
}
// Create main outbound
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
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 +1049,50 @@ 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>
/// <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)
{
try
{
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
if (!prevOutboundTag.IsNullOrEmpty())
{
outbound.detour = prevOutboundTag;
}
// Next proxy
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
Outbound4Sbox? nextOutbound = null;
if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom)
{
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

View file

@ -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,8 @@ 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 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 +1369,48 @@ 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;
var currentOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
Outbounds4Ray? nextOutbound = null; Outbounds4Ray? nextOutbound = null;
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.ContainsKey(node.Subid))
if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy))
{ {
prevTag = chainProxy.Item1; prevTag = prevProxyTags[node.Subid]; // 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);
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile); }
if (nextNode != null
&& nextNode.ConfigType != EConfigType.Custom if (nextOutbound is not null)
&& nextNode.ConfigType != EConfigType.Hysteria2
&& nextNode.ConfigType != EConfigType.TUIC)
{ {
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound); resultOutbounds.Add(nextOutbound);
await GenOutbound(nextNode, nextOutbound);
}
// Cache the chain proxy
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
}
}
}
// Create main outbound
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
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 +1426,58 @@ 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>
/// <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)
{
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);
Outbounds4Ray? nextOutbound = null;
if (nextNode is not null
&& nextNode.ConfigType != EConfigType.Custom
&& nextNode.ConfigType != EConfigType.Hysteria2
&& nextNode.ConfigType != EConfigType.TUIC)
{
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)