Add group in traffic splitting support

This commit is contained in:
DHR60 2025-09-11 17:18:53 +08:00
parent c00b0b7c43
commit ff6ce3334a
9 changed files with 115 additions and 17 deletions

View file

@ -50,6 +50,7 @@ public class Global
public const string DirectTag = "direct"; public const string DirectTag = "direct";
public const string BlockTag = "block"; public const string BlockTag = "block";
public const string DnsTag = "dns-module"; public const string DnsTag = "dns-module";
public const string BalancerTagSuffix = "-round";
public const string StreamSecurity = "tls"; public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality"; public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1"; public const string Loopback = "127.0.0.1";

View file

@ -398,12 +398,12 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
singboxConfig.outbounds.RemoveAt(0);
await GenLog(singboxConfig); await GenLog(singboxConfig);
await GenInbounds(singboxConfig); await GenInbounds(singboxConfig);
await GenRouting(singboxConfig); await GenRouting(singboxConfig);
await GenExperimental(singboxConfig); await GenExperimental(singboxConfig);
singboxConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)

View file

@ -368,6 +368,39 @@ public partial class CoreConfigSingboxService
} }
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return Global.ProxyTag;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
return Global.ProxyTag;
}
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
if (ret == 0)
{
return childBaseTagName;
}
return Global.ProxyTag;
}
if (node == null if (node == null
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType)) || !Global.SingboxSupportConfigType.Contains(node.ConfigType))
{ {

View file

@ -126,13 +126,13 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
v2rayConfig.outbounds.RemoveAt(0);
await GenLog(v2rayConfig); await GenLog(v2rayConfig);
await GenInbounds(v2rayConfig); await GenInbounds(v2rayConfig);
await GenRouting(v2rayConfig); await GenRouting(v2rayConfig);
await GenDns(null, v2rayConfig); await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig); await GenStatistic(v2rayConfig);
v2rayConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>(); var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds) foreach (var it in selecteds)
@ -183,18 +183,37 @@ public partial class CoreConfigV2rayService(Config config)
await GenOutboundsList(proxyProfiles, v2rayConfig); await GenOutboundsList(proxyProfiles, v2rayConfig);
//add balancers //add balancers
await GenObservatory(v2rayConfig, multipleLoad);
await GenBalancer(v2rayConfig, multipleLoad); await GenBalancer(v2rayConfig, multipleLoad);
var balancer = v2rayConfig.routing.balancers.First(); var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
//add rule //add rule
var rules = v2rayConfig.routing.rules.Where(t => t.outboundTag == Global.ProxyTag).ToList(); var rules = v2rayConfig.routing.rules;
if (rules?.Count > 0) if (rules?.Count > 0)
{ {
var balancerTagSet = v2rayConfig.routing.balancers
.Select(b => b.tag)
.ToHashSet();
foreach (var rule in rules) foreach (var rule in rules)
{ {
if (rule.outboundTag == null)
continue;
if (balancerTagSet.Contains(rule.outboundTag))
{
rule.balancerTag = rule.outboundTag;
rule.outboundTag = null; rule.outboundTag = null;
rule.balancerTag = balancer.tag; continue;
}
var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix;
if (balancerTagSet.Contains(outboundWithSuffix))
{
rule.balancerTag = outboundWithSuffix;
rule.outboundTag = null;
}
} }
} }
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
@ -202,7 +221,7 @@ public partial class CoreConfigV2rayService(Config config)
v2rayConfig.routing.rules.Add(new() v2rayConfig.routing.rules.Add(new()
{ {
ip = ["0.0.0.0/0", "::/0"], ip = ["0.0.0.0/0", "::/0"],
balancerTag = balancer.tag, balancerTag = defaultBalancerTag,
type = "field" type = "field"
}); });
} }
@ -211,7 +230,7 @@ public partial class CoreConfigV2rayService(Config config)
v2rayConfig.routing.rules.Add(new() v2rayConfig.routing.rules.Add(new()
{ {
network = "tcp,udp", network = "tcp,udp",
balancerTag = balancer.tag, balancerTag = defaultBalancerTag,
type = "field" type = "field"
}); });
} }

View file

@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig;
public partial class CoreConfigV2rayService public partial class CoreConfigV2rayService
{ {
private async Task<int> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad) private async Task<int> GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad)
{ {
if (multipleLoad == EMultipleLoad.LeastPing) if (multipleLoad == EMultipleLoad.LeastPing)
{ {
@ -30,6 +30,11 @@ public partial class CoreConfigV2rayService
}; };
v2rayConfig.burstObservatory = burstObservatory; v2rayConfig.burstObservatory = burstObservatory;
} }
return await Task.FromResult(0);
}
private async Task<string> GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag)
{
var strategyType = multipleLoad switch var strategyType = multipleLoad switch
{ {
EMultipleLoad.Random => "random", EMultipleLoad.Random => "random",
@ -38,9 +43,10 @@ public partial class CoreConfigV2rayService
EMultipleLoad.LeastLoad => "leastLoad", EMultipleLoad.LeastLoad => "leastLoad",
_ => "roundRobin", _ => "roundRobin",
}; };
var balancerTag = $"{selector}{Global.BalancerTagSuffix}";
var balancer = new BalancersItem4Ray var balancer = new BalancersItem4Ray
{ {
selector = [Global.ProxyTag], selector = [selector],
strategy = new() strategy = new()
{ {
type = strategyType, type = strategyType,
@ -49,9 +55,10 @@ public partial class CoreConfigV2rayService
expected = 1, expected = 1,
}, },
}, },
tag = $"{Global.ProxyTag}-round", tag = balancerTag,
}; };
v2rayConfig.routing.balancers = [balancer]; v2rayConfig.routing.balancers ??= new();
return await Task.FromResult(0); v2rayConfig.routing.balancers.Add(balancer);
return await Task.FromResult(balancerTag);
} }
} }

View file

@ -746,14 +746,15 @@ public partial class CoreConfigV2rayService
} }
else else
{ {
outbound.tag = baseTagName + i.ToString(); // avoid v2ray observe
outbound.tag = "chain-" + baseTagName + i.ToString();
} }
if (i != nodes.Count - 1) if (i != nodes.Count - 1)
{ {
outbound.streamSettings.sockopt = new() outbound.streamSettings.sockopt = new()
{ {
dialerProxy = baseTagName + (i + 1).ToString() dialerProxy = "chain-" + baseTagName + (i + 1).ToString()
}; };
} }

View file

@ -125,6 +125,43 @@ public partial class CoreConfigV2rayService
} }
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return Global.ProxyTag;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
return Global.ProxyTag;
}
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
if (node.ConfigType == EConfigType.PolicyGroup)
{
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, childBaseTagName);
}
if (ret == 0)
{
return childBaseTagName;
}
return Global.ProxyTag;
}
if (node == null if (node == null
|| !Global.XraySupportConfigType.Contains(node.ConfigType)) || !Global.XraySupportConfigType.Contains(node.ConfigType))
{ {

View file

@ -98,7 +98,7 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
private async void BtnSelectProfile_Click(object? sender, RoutedEventArgs e) private async void BtnSelectProfile_Click(object? sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
var result = await selectWindow.ShowDialog<bool?>(this); var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true) if (result == true)
{ {

View file

@ -91,7 +91,7 @@ public partial class RoutingRuleDetailsWindow
private async void BtnSelectProfile_Click(object sender, RoutedEventArgs e) private async void BtnSelectProfile_Click(object sender, RoutedEventArgs e)
{ {
var selectWindow = new ProfilesSelectWindow(); var selectWindow = new ProfilesSelectWindow();
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom, EConfigType.PolicyGroup, EConfigType.ProxyChain }, exclude: true); selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
if (selectWindow.ShowDialog() == true) if (selectWindow.ShowDialog() == true)
{ {
var profile = await selectWindow.ProfileItem; var profile = await selectWindow.ProfileItem;