Skip to content

Commit fefa7de

Browse files
authored
Optimize proxy chain handling for multiple nodes (#7468)
* Improves outbound proxy chain handling. * Improves sing-box outbound proxy chain handling. * AI-optimized code
1 parent a46a4ad commit fefa7de

File tree

2 files changed

+363
-35
lines changed

2 files changed

+363
-35
lines changed

v2rayN/ServiceLib/Services/CoreConfig/CoreConfigSingboxService.cs

Lines changed: 177 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem>
336336
await GenExperimental(singboxConfig);
337337
singboxConfig.outbounds.RemoveAt(0);
338338

339-
var tagProxy = new List<string>();
339+
var proxyProfiles = new List<ProfileItem>();
340340
foreach (var it in selecteds)
341341
{
342342
if (it.ConfigType == EConfigType.Custom)
@@ -370,42 +370,18 @@ public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem>
370370
}
371371

372372
//outbound
373-
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
374-
await GenOutbound(item, outbound);
375-
outbound.tag = $"{Global.ProxyTag}-{tagProxy.Count + 1}";
376-
singboxConfig.outbounds.Insert(0, outbound);
377-
tagProxy.Add(outbound.tag);
373+
proxyProfiles.Add(item);
378374
}
379-
if (tagProxy.Count <= 0)
375+
if (proxyProfiles.Count <= 0)
380376
{
381377
ret.Msg = ResUI.FailedGenDefaultConfiguration;
382378
return ret;
383379
}
380+
await GenOutboundsList(proxyProfiles, singboxConfig);
384381

385382
await GenDns(null, singboxConfig);
386383
await ConvertGeo2Ruleset(singboxConfig);
387384

388-
//add urltest outbound
389-
var outUrltest = new Outbound4Sbox
390-
{
391-
type = "urltest",
392-
tag = $"{Global.ProxyTag}-auto",
393-
outbounds = tagProxy,
394-
interrupt_exist_connections = false,
395-
};
396-
singboxConfig.outbounds.Insert(0, outUrltest);
397-
398-
//add selector outbound
399-
var outSelector = new Outbound4Sbox
400-
{
401-
type = "selector",
402-
tag = Global.ProxyTag,
403-
outbounds = JsonUtils.DeepCopy(tagProxy),
404-
interrupt_exist_connections = false,
405-
};
406-
outSelector.outbounds.Insert(0, outUrltest.tag);
407-
singboxConfig.outbounds.Insert(0, outSelector);
408-
409385
ret.Success = true;
410386
ret.Data = JsonUtils.Serialize(singboxConfig);
411387
return ret;
@@ -974,6 +950,179 @@ private async Task<int> GenMoreOutbounds(ProfileItem node, SingboxConfig singbox
974950
return 0;
975951
}
976952

953+
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig)
954+
{
955+
try
956+
{
957+
// Get outbound template and initialize lists
958+
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
959+
if (txtOutbound.IsNullOrEmpty())
960+
{
961+
return 0;
962+
}
963+
964+
var resultOutbounds = new List<Outbound4Sbox>();
965+
var prevOutbounds = new List<Outbound4Sbox>(); // Separate list for prev outbounds
966+
var proxyTags = new List<string>(); // For selector and urltest outbounds
967+
968+
// Cache for chain proxies to avoid duplicate generation
969+
var chainProxyCache = new Dictionary<string, (string?, Outbound4Sbox?)>();
970+
var prevProxyTags = new Dictionary<string, string>(); // Map from profile name to tag
971+
int prevIndex = 0; // Index for prev outbounds
972+
973+
// Process each node
974+
int index = 0;
975+
foreach (var node in nodes)
976+
{
977+
index++;
978+
979+
// Skip unsupported config types
980+
if (node.ConfigType is EConfigType.Custom)
981+
{
982+
continue;
983+
}
984+
985+
// Handle proxy chain
986+
string? prevTag = null;
987+
Outbound4Sbox? nextOutbound = null;
988+
989+
if (node.Subid.IsNotEmpty())
990+
{
991+
// Check if chain proxy is already cached
992+
if (chainProxyCache.TryGetValue(node.Subid, out var chainProxy))
993+
{
994+
prevTag = chainProxy.Item1;
995+
nextOutbound = chainProxy.Item2;
996+
}
997+
else
998+
{
999+
// Generate chain proxy and cache it
1000+
var subItem = await AppHandler.Instance.GetSubItem(node.Subid);
1001+
if (subItem != null)
1002+
{
1003+
// Process previous proxy
1004+
if (!subItem.PrevProfile.IsNullOrEmpty())
1005+
{
1006+
// Check if this previous proxy was already created
1007+
if (prevProxyTags.TryGetValue(subItem.PrevProfile, out var existingTag))
1008+
{
1009+
prevTag = existingTag;
1010+
}
1011+
else
1012+
{
1013+
var prevNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
1014+
if (prevNode != null && prevNode.ConfigType != EConfigType.Custom)
1015+
{
1016+
prevIndex++;
1017+
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
1018+
await GenOutbound(prevNode, prevOutbound);
1019+
1020+
prevTag = $"{Global.ProxyTag}-prev-{prevIndex}";
1021+
prevOutbound.tag = prevTag;
1022+
prevProxyTags[subItem.PrevProfile] = prevTag;
1023+
1024+
// Add to prev outbounds list (will be added at the end)
1025+
prevOutbounds.Add(prevOutbound);
1026+
}
1027+
}
1028+
}
1029+
1030+
// Process next proxy
1031+
var nextNode = await AppHandler.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
1032+
if (nextNode != null && nextNode.ConfigType != EConfigType.Custom)
1033+
{
1034+
nextOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
1035+
await GenOutbound(nextNode, nextOutbound);
1036+
}
1037+
1038+
// Cache the chain proxy
1039+
chainProxyCache[node.Subid] = (prevTag, nextOutbound);
1040+
}
1041+
}
1042+
}
1043+
1044+
// Create main outbound
1045+
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
1046+
1047+
await GenOutbound(node, outbound);
1048+
outbound.tag = $"{Global.ProxyTag}-{index}";
1049+
1050+
// Configure proxy chain relationships
1051+
if (nextOutbound != null)
1052+
{
1053+
// If there's a next proxy, it should be the final outbound in the chain
1054+
var originalTag = outbound.tag;
1055+
outbound.tag = $"mid-{Global.ProxyTag}-{index}";
1056+
1057+
var nextOutboundCopy = JsonUtils.DeepCopy(nextOutbound);
1058+
nextOutboundCopy.tag = originalTag;
1059+
nextOutboundCopy.detour = outbound.tag; // Use detour instead of sockopt
1060+
1061+
if (prevTag != null)
1062+
{
1063+
outbound.detour = prevTag;
1064+
}
1065+
1066+
// Add to proxy tags for selector/urltest
1067+
proxyTags.Add(originalTag);
1068+
1069+
// Add in reverse order to ensure final outbound is added first
1070+
resultOutbounds.Add(nextOutboundCopy); // Final outbound (exposed to internet)
1071+
resultOutbounds.Add(outbound); // Middle outbound
1072+
}
1073+
else
1074+
{
1075+
// If no next proxy, the main outbound is the final one
1076+
if (prevTag != null)
1077+
{
1078+
outbound.detour = prevTag;
1079+
}
1080+
1081+
// Add to proxy tags for selector/urltest
1082+
proxyTags.Add(outbound.tag);
1083+
resultOutbounds.Add(outbound);
1084+
}
1085+
}
1086+
1087+
// Add urltest outbound (auto selection based on latency)
1088+
if (proxyTags.Count > 0)
1089+
{
1090+
var outUrltest = new Outbound4Sbox
1091+
{
1092+
type = "urltest",
1093+
tag = $"{Global.ProxyTag}-auto",
1094+
outbounds = proxyTags,
1095+
interrupt_exist_connections = false,
1096+
};
1097+
1098+
// Add selector outbound (manual selection)
1099+
var outSelector = new Outbound4Sbox
1100+
{
1101+
type = "selector",
1102+
tag = Global.ProxyTag,
1103+
outbounds = JsonUtils.DeepCopy(proxyTags),
1104+
interrupt_exist_connections = false,
1105+
};
1106+
outSelector.outbounds.Insert(0, outUrltest.tag);
1107+
1108+
// Insert these at the beginning
1109+
resultOutbounds.Insert(0, outUrltest);
1110+
resultOutbounds.Insert(0, outSelector);
1111+
}
1112+
1113+
// Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds
1114+
resultOutbounds.AddRange(prevOutbounds);
1115+
resultOutbounds.AddRange(singboxConfig.outbounds);
1116+
singboxConfig.outbounds = resultOutbounds;
1117+
}
1118+
catch (Exception ex)
1119+
{
1120+
Logging.SaveLog(_tag, ex);
1121+
}
1122+
1123+
return 0;
1124+
}
1125+
9771126
private async Task<int> GenRouting(SingboxConfig singboxConfig)
9781127
{
9791128
try

0 commit comments

Comments
 (0)