mirror of
https://github.com/2dust/v2rayN.git
synced 2025-07-02 04:52:09 +00:00
Optimize proxy chain handling for multiple nodes (#7468)
* Improves outbound proxy chain handling. * Improves sing-box outbound proxy chain handling. * AI-optimized code
This commit is contained in:
parent
a46a4ad7c1
commit
fefa7ded5a
2 changed files with 363 additions and 35 deletions
|
@ -336,7 +336,7 @@ public class CoreConfigSingboxService
|
||||||
await GenExperimental(singboxConfig);
|
await GenExperimental(singboxConfig);
|
||||||
singboxConfig.outbounds.RemoveAt(0);
|
singboxConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
var tagProxy = new List<string>();
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (it.ConfigType == EConfigType.Custom)
|
||||||
|
@ -370,42 +370,18 @@ public class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
|
|
||||||
//outbound
|
//outbound
|
||||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
proxyProfiles.Add(item);
|
||||||
await GenOutbound(item, outbound);
|
|
||||||
outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}";
|
|
||||||
singboxConfig.outbounds.Insert(0, outbound);
|
|
||||||
tagProxy.Add(outbound.tag);
|
|
||||||
}
|
}
|
||||||
if (tagProxy.Count <= 0)
|
if (proxyProfiles.Count <= 0)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
await GenOutboundsList(proxyProfiles, singboxConfig);
|
||||||
|
|
||||||
await GenDns(null, singboxConfig);
|
await GenDns(null, singboxConfig);
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
//add urltest outbound
|
|
||||||
var outUrltest = new Outbound4Sbox
|
|
||||||
{
|
|
||||||
type = "urltest",
|
|
||||||
tag = $"{Global.ProxyTag}-auto",
|
|
||||||
outbounds = tagProxy,
|
|
||||||
interrupt_exist_connections = false,
|
|
||||||
};
|
|
||||||
singboxConfig.outbounds.Insert(0, outUrltest);
|
|
||||||
|
|
||||||
//add selector outbound
|
|
||||||
var outSelector = new Outbound4Sbox
|
|
||||||
{
|
|
||||||
type = "selector",
|
|
||||||
tag = Global.ProxyTag,
|
|
||||||
outbounds = JsonUtils.DeepCopy(tagProxy),
|
|
||||||
interrupt_exist_connections = false,
|
|
||||||
};
|
|
||||||
outSelector.outbounds.Insert(0, outUrltest.tag);
|
|
||||||
singboxConfig.outbounds.Insert(0, outSelector);
|
|
||||||
|
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
ret.Data = JsonUtils.Serialize(singboxConfig);
|
ret.Data = JsonUtils.Serialize(singboxConfig);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -974,6 +950,179 @@ public class CoreConfigSingboxService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get outbound template and initialize lists
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||||
|
if (txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultOutbounds = new List<Outbound4Sbox>();
|
||||||
|
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
|
||||||
|
var proxyTags = new List<string>(); // For selector and urltest outbounds
|
||||||
|
|
||||||
|
// 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
|
||||||
|
int prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
|
// Process each node
|
||||||
|
int index = 0;
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// Skip unsupported config types
|
||||||
|
if (node.ConfigType is EConfigType.Custom)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle proxy chain
|
||||||
|
string? prevTag = 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);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
if (prevNode != null && prevNode.ConfigType != EConfigType.Custom)
|
||||||
|
{
|
||||||
|
prevIndex++;
|
||||||
|
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
|
||||||
|
prevTag = $"{Global.ProxyTag}-prev-{prevIndex}";
|
||||||
|
prevOutbound.tag = prevTag;
|
||||||
|
prevProxyTags[subItem.PrevProfile] = prevTag;
|
||||||
|
|
||||||
|
// Add to prev outbounds list (will be added at the end)
|
||||||
|
prevOutbounds.Add(prevOutbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process next proxy
|
||||||
|
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode != null && nextNode.ConfigType != EConfigType.Custom)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add urltest outbound (auto selection based on latency)
|
||||||
|
if (proxyTags.Count > 0)
|
||||||
|
{
|
||||||
|
var outUrltest = new Outbound4Sbox
|
||||||
|
{
|
||||||
|
type = "urltest",
|
||||||
|
tag = $"{Global.ProxyTag}-auto",
|
||||||
|
outbounds = proxyTags,
|
||||||
|
interrupt_exist_connections = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add selector outbound (manual selection)
|
||||||
|
var outSelector = new Outbound4Sbox
|
||||||
|
{
|
||||||
|
type = "selector",
|
||||||
|
tag = Global.ProxyTag,
|
||||||
|
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||||
|
interrupt_exist_connections = false,
|
||||||
|
};
|
||||||
|
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||||
|
|
||||||
|
// Insert these at the beginning
|
||||||
|
resultOutbounds.Insert(0, outUrltest);
|
||||||
|
resultOutbounds.Insert(0, outSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds
|
||||||
|
resultOutbounds.AddRange(prevOutbounds);
|
||||||
|
resultOutbounds.AddRange(singboxConfig.outbounds);
|
||||||
|
singboxConfig.outbounds = resultOutbounds;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
private async Task<int> GenRouting(SingboxConfig singboxConfig)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -113,7 +113,7 @@ public class CoreConfigV2rayService
|
||||||
await GenStatistic(v2rayConfig);
|
await GenStatistic(v2rayConfig);
|
||||||
v2rayConfig.outbounds.RemoveAt(0);
|
v2rayConfig.outbounds.RemoveAt(0);
|
||||||
|
|
||||||
var tagProxy = new List<string>();
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (it.ConfigType == EConfigType.Custom)
|
||||||
|
@ -151,17 +151,14 @@ public class CoreConfigV2rayService
|
||||||
}
|
}
|
||||||
|
|
||||||
//outbound
|
//outbound
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
proxyProfiles.Add(item);
|
||||||
await GenOutbound(item, outbound);
|
|
||||||
outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}";
|
|
||||||
v2rayConfig.outbounds.Insert(0, outbound);
|
|
||||||
tagProxy.Add(outbound.tag);
|
|
||||||
}
|
}
|
||||||
if (tagProxy.Count <= 0)
|
if (proxyProfiles.Count <= 0)
|
||||||
{
|
{
|
||||||
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
ret.Msg = ResUI.FailedGenDefaultConfiguration;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
await GenOutboundsList(proxyProfiles, v2rayConfig);
|
||||||
|
|
||||||
//add balancers
|
//add balancers
|
||||||
await GenBalancer(v2rayConfig, multipleLoad);
|
await GenBalancer(v2rayConfig, multipleLoad);
|
||||||
|
@ -1364,6 +1361,188 @@ public class CoreConfigV2rayService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, V2rayConfig v2rayConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get template and initialize list
|
||||||
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
|
if (txtOutbound.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultOutbounds = new List<Outbounds4Ray>();
|
||||||
|
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
|
||||||
|
var chainProxyCache = new Dictionary<string, (string?, Outbounds4Ray?)>();
|
||||||
|
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
|
||||||
|
int prevIndex = 0; // Index for prev outbounds
|
||||||
|
|
||||||
|
// Process nodes
|
||||||
|
int index = 0;
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// Skip unsupported config types
|
||||||
|
if (node.ConfigType is EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle proxy chain
|
||||||
|
string? prevTag = null;
|
||||||
|
Outbounds4Ray? nextOutbound = null;
|
||||||
|
|
||||||
|
if (!node.Subid.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
|
if (prevNode != null
|
||||||
|
&& prevNode.ConfigType != EConfigType.Custom
|
||||||
|
&& prevNode.ConfigType != EConfigType.Hysteria2
|
||||||
|
&& prevNode.ConfigType != EConfigType.TUIC)
|
||||||
|
{
|
||||||
|
prevIndex++;
|
||||||
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
|
||||||
|
prevTag = $"{Global.ProxyTag}-prev-{prevIndex}";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process next proxy
|
||||||
|
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
|
if (nextNode != null
|
||||||
|
&& nextNode.ConfigType != EConfigType.Custom
|
||||||
|
&& nextNode.ConfigType != EConfigType.Hysteria2
|
||||||
|
&& nextNode.ConfigType != EConfigType.TUIC)
|
||||||
|
{
|
||||||
|
nextOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds
|
||||||
|
resultOutbounds.AddRange(prevOutbounds);
|
||||||
|
resultOutbounds.AddRange(v2rayConfig.outbounds);
|
||||||
|
v2rayConfig.outbounds = resultOutbounds;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
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